目录
一、网络基本概念
1.网络
2.互联网
3.ip地址
4.MAC地址
5.端口号Port
6.网络协议
二.网络分层模型
1.数据链路层
2.网络层
3.传输层
4.应用层
三、网络应用程序通信流程
四、socket 网络编程
1.主机字节序列和网络字节序列
2.套接字地址结构
(1)通用 socket 地址结构
(2)专用 socket 地址结构
(3)IP 地址转换函数
3.网络编程接口
一、网络基本概念
1.网络
网络:由若干结点和连接这些结点的链路组成,网络中的结点可以是计算机,交换机、 路由器等设备。
网络设备有:交换机、路由器、集线器
传输介质有:双绞线、同轴电缆、光纤
一个简单的网络示意图
2.互联网
把多个网络连接起来就构成了互联网。目前最大的互联网就是我们常说的因特网。
3.ip地址
IP 地址就是给因特网上的每一个主机(或路由器)的每一个接口分配的一个在全世界 范围内唯一的标识符。IP 地址因其特殊的结构使我们可以在因特网上很方便地进行寻址。
IP地址的目的:资源共享、信息交互
IP 地址有分 IPV4 和 IPV6 两种类别格式,IPV4 是类似”A.B.C.D”的格式,它是 32 位 的,用“.”分成四个段,每个段是 8 个位(值为 0-255),用 10 进制表示。IPV6 地址是 128 位,格式类似”XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX”,用“:“分成 8 个 段,每个段 16 个位,用 4 个 16 进制数表示。
- IPV4 地址示例: “192.168.31.1”。ipv4位数:32。
- IPV6 地址示例:“2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b”。ipv6位数:128。
- IP分为网络号+主机号
- 范围:(给一个ip地址问是哪一类)
-
查看ip命令—— windows:ipconfig ;Linux:ifconfig
-
127.0.0.1 自己电脑的ip,用于测试
- IP地址、MAC物理地址(48位)唯一标识一台主机
4.MAC地址
在局域网中,硬件地址又称为物理地址或者 MAC 地址,长度 48 位,是固化在计算机适配器的 ROM 中的地址。因此假定连接在局域网上的一台计算机的适配器坏了而我们更换了 一个新的适配器,那么这台计算机的局域网的“地址”也就改变了,虽然这台计算机的地理 位置没有发生变化。其次,当我们把一个笔记本从一个城市带到另一个城市时,虽然地理位置改变了,但是电脑在局域网中的“地址”仍然不变。由此可见,局域网上某个主机的“地址”根本不能告诉我们这台主机位于什么地方。在网络上方便的寻找某个主机,还得依靠 ip 地址进行。
- MAC:固化的不变
- IP:动态的,可以反映出加入与退出网络的变化
5.端口号Port
一个ip地址表示一台主机。主机运行一台进程,需要和其他主机联网通信,这就依赖于端口号。
端口可以理解为应用程序代号,是软件层次的,用于表示一个进程/应用程序,目的是方便查找,可以实现不同主机之间的通信。
ip+port->进程 ,想要实现进程间通信需要:自己的ip+端口与对方的ip+端口
6.网络协议
网络协议就是一组网络规则的集合,是我们共同遵守的约定或标准。
常见的协议:
- HTTP:超文本传输协议
- FTP: 文件传输协议
- TELNET : 是 internet 远程登陆服务的标准协议。
- TCP : 传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可 靠的、基于字节流的传输层通信协议
- UDP :用户数据报协议
- IP : Internet Protocol 简称 IP,又译为网际协议或互联网协议
- ICMP :因特网控制报文协议
- ARP : 地址解析协议,是根据 IP 地址获取 MAC 地址的协议
- RARP : 逆地址解析协议
二.网络分层模型
(面试会考:
分层的目的?哪些层?各层次功能?
OSI 的 7 层模型与 tcp/ip 协议族体系 4 层结构
1.数据链路层
主要功能是:通过各种控制协议,将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。 在计算机网络中由于各种干扰的存在,物理链路是不可靠的。因此,这一层的主要功能 是在物理层提供的比特流的基础上,通过差错控制,使有差错的物理线路变为无差错的数据链路,即提供可靠的通过物理介质传输数据的方法。 该层通常又被分为介质访问控制(MAC)和逻辑链路控制(LLC)两个子层。
- MAC 子层的主要任务是解决共享型网络中多用户对信道竞争的问题,完成网络介质的 访问控制;
- LLC 子层的主要任务是建立和维护网络连接,执行差错校验、流量控制和链路控制。
TCP/IP 协议体系结构中,数据链路层的功能描述为实现网卡接口的网络驱动程序,以 处理数据在物理媒介上的传输,不同的物理网络具有不同的电气特性,网络驱动程序隐藏了 这些细节,为上层协议提供了一个统一的接口。这一层主要关注的三个基本问题是:封装成帧,透明传输和差错检测。
2.网络层
网络层实现数据包的选路和转发。广域网或者说互联网通常使用众多分级的路由器来连接分散的主机或者局域网,因此,通信的两台主机一般不是直接相连的,而是通过多个中间 结点(路由器)连接的。网络层的任务就是选择这些中间结点,以确定两台主机之间的通信路径。同时,网络层对上层协议隐藏了网络拓扑连接的细节,使得在传输层和网络应用程序看来,通信的双方是直接相连的。
网络层最核心的协议是 IP 协议(Internet Protocol,因特网协议)。IP 协议根据数据包的目的 IP地址来决定何如投递它。如果数据包不能直接发送给目标主机,那么 IP 协议就是为它寻找一个合适的吓一跳路由器,并将数据包交付给该路由器来转发。多次重复这一过程,数据包最终到达目标主机,或者由于发送失败而被丢弃。可见,IP 协议使用逐跳的方式确定通 信路径。
网络层另外一个重要的协议是 ICMP 协议(因特网控制报文协议)。它是 IP 协议的重要 补充,主要用于检测网络连接。
IP 协议为上层协议提供无状态、无连接、不可靠的服务。
无状态是指通信双方不同步传输数据的状态信息,因此所有 IP 数据报的发送、传输和 接收都是相互独立、没有上下文关系的。这种服务最大的缺点是无法处理乱序和重复的 IP 数据报。虽然 IP 数据报头部提供了一个标识字段用以唯一标识一个 IP 数据报,但它是被用 来处理 IP 分片和重组的,而不是用来指示接收顺序的。无状态的优点是简单、高效。无须 为保持通信状态而分配一些内核资源,也无须再每次通信时携带状态信息。
无连接是指 IP 通信双方都不长久地维持对方的任何信息。这样,上层协议每次发送数 据的时候,都必须明确指定对方的 IP 地址。 不可靠是指 IP 协议不能保证 IP 数据报准确地到达接收端,它只是承诺尽最大努力。
IPV4 头部结构如下:
3.传输层
传输层为两台主机上的应用程序提供端到端的通信。与网络层使用的逐跳通信的方式不 同,传输层只关心通信的起始端和目的端,而不在乎数据包的中转过程。
传输层协议主要有三个:TCP 协议、UDP 协议和 SCTP 协议 TCP 协议(传输控制协议)为应用层提供可靠的、面向连接的和基于流的服务。
TCP 协 议使用超时重传、确认应答等方式来确保数据包被正确的发送至目的端,因此 TCP 服务是 可靠的。使用 TCP 协议通信的双方必须先建立 TCP 连接,并在内核中为该连接维持一些必 要的数据结构,比如连接状态,读写缓冲区等。当通信结束时,双方必须关闭连接以释放这 些内核数据。TCP 服务是基于流的,基于流的数据没有边界(长度)限制,它源源不断地从 通信地一端流入另一端。发送端可以逐个字节地向数据流中写入数据,接收端可以逐个字节地将它们读出。
TCP 协议报头:
UDP 协议(用户数据报协议)则与 TCP 协议完全相反,它为应用层提供不可靠、无连 接、基于数据报地服务。“不可靠”意味着 UDP 协议无法保证数据从发送端正确地传送到目 的端。如果数据在中途丢失,或者目的端通过数据校验发现数据错误而将其丢弃,则 UDP 协议只是简单地通知应用程序发送失败。因此,如果要使 UDP 协议可靠,那么应用程序通 常要自己处理数据确认、超时重传等逻辑。UDP 是无连接的,即通信双发不保持一个长久 的联系,因此应用程序每次发送数据都要明确指定 接收端的地址。基于数据报的服务,是相 对基于流的服务而言的。每次 UDP 数据报都有一个长度,接收端必须以该长度为最小单位 将其所有内容一次性读出,否则数据将被截断。
SCTP 协议(流控制传输协议)是一种相对较新的传输层协议,它是为了再因特网上传 输电话信号而设计的。这里暂时不讨论 SCTP 协议。
4.应用层
负责处理应用程序的逻辑。
三、网络应用程序通信流程
如下图,应用程序 A 要将数据”hello” 传给网络上另外一台主机上的应用程序 B。
- 数据“hello”从应用层发送给传输层
- 传输层在数据前面加上 tcp 协议或 udp 协议的报头, 将整条报文发给网络层
- 网络层添加自己的 IP 报头,再将整条数据发送给数据链路层。
- 数据链路层将数据封装成能在网络中独立传输的数据单元,即数据帧。
- 封装好的数据帧通过网络传输到另一台主机,然后再从下层依次拆包,将数据部分送往应用层。
- 最后,应用程序 B就得到 了数据”hello”。
四、socket 网络编程
1.主机字节序列和网络字节序列
- 主机字节序列分为大端字节序和小端字节序,不同的主机采用的字节序列可能不同。
- 大 端字节序是指一个整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址 处。小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的 低地址处。
- 在两台使用不同字节序的主机之间传递数据时,可能会出现冲突。所以,在将数据发送到网络时规定整形数据使用大端字节序,所以也把大端字节序成为网络字节序列。对方接收到数据后,可以根据自己的字节序进行转换。
- Linux 系统提供如下 4 个函数来完成主机字节序和网络字节序之间的转换:
-
uint32_t htonl(uint32_t hostlong); // 长整型的主机字节序转网络字节序 uint32_t ntohl(uint32_t netlong); // 长整型的网络字节序转主机字节序 uint16_t htons(uint16_t hostshort); // 短整形的主机字节序转网络字节序 uint16_t ntohs(uint16_t netshort); // 短整型的网络字节序转主机字节序
2.套接字地址结构
套接字:具有通过网络收发数据的能力
(1)通用 socket 地址结构
socket 网络编程接口中表示 socket 地址的是结构体 sockaddr,其定义如下:
#include <bits/socket.h>
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};ket.h>
sa_family 成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对 应。常见的协议族和对应的地址族如下图所示:
(2)专用 socket 地址结构
TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用 socket 地址结构体,它们分 别用于 IPV4 和 IPV6:
/*
sin_family: 地址族 AF_INET
sin_port: 端口号,需要用网络字节序表示
sin_addr: IPV4 地址结构:s_addr 以网络字节序表示 IPV4 地址
*/
struct in_addr
{
u_int32_t s_addr;
};
struct sockaddr_in
{
sa_family_t sin_family;
u_int16_t sin_port;
struct in_addr sin_addr;
};
struct in6_addr
{
unsigned char sa_addr[16]; // IPV6 地址,要用网络字节序表示
};
struct sockaddr_in6
{
sa_family_t sin6_family; // 地址族:AF_INET6
u_inet16_t sin6_port; // 端口号:用网络字节序表示
u_int32_t sin6_flowinfo; // 流信息,应设置为 0
struct in6_addr sin6_addr; // IPV6 地址结构体
u_int32_t sin6_scope_id; // scope ID,尚处于试验阶段
};
(3)IP 地址转换函数
通常,人们习惯用点分十进制字符串表示 IPV4 地址,但编程中我们需要先把它们转化 为整数方能使用,下面函数可用于点分十进制字符串表示的 IPV4 地址和网络字节序整数表 示的 IPV4 地址之间的转换:
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp); //字符串表示的 IPV4 地址转化为网络字节序
char* inet_ntoa(struct in_addr in); // IPV4 地址的网络字节序转化为字符串表示
3.网络编程接口
1. #include <sys/types.h>
2. #include <sys/socket.h>
3.
4. /*************************************************************
5. socket()创建套接字,成功返回套接字的文件描述符,失败返回-1
6. domain: 设置套接字的协议簇, AF_UNIX AF_INET AF_INET6
7. type: 设置套接字的服务类型 SOCK_STREAM SOCK_DGRAM
8. protocol: 一般设置为 0,表示使用默认协议
9. *************************************************************/
10. int socket(int domain, int type, int protocol);
11.
12. /*************************************************************
13. bind()将 sockfd 与一个 socket 地址绑定,成功返回 0,失败返回-1
14. sockfd 是网络套接字描述符
15. addr 是地址结构
16. addrlen 是 socket 地址的长度
17. **************************************************************/
18. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
19.
20. /*************************************************************
21. listen()创建一个监听队列以存储待处理的客户连接,成功返回 0,失败返回-1
22. sockfd 是被监听的 socket 套接字
23. backlog 表示处于完全连接状态的 socket 的上限
24. **************************************************************/
25. int listen(int sockfd, int backlog);
26.
27. /*************************************************************
28. accept()从 listen 监听队列中接收一个连接,成功返回一个新的连接 socket,
29. 该 socket 唯一地标识了被接收的这个连接,失败返回-1
30. sockfd 是执行过 listen 系统调用的监听 socket
31. addr 参数用来获取被接受连接的远端 socket 地址
32. addrlen 指定该 socket 地址的长度
33. *************************************************************/
34. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
35.
36. /*************************************************************
37. connect()客户端需要通过此系统调用来主动与服务器建立连接,
38. 成功返回 0,失败返回-1
39. sockfd 参数是由 socket()返回的一个 socket。
40. serv_addr 是服务器监听的 socket 地址
41. addrlen 则指定这个地址的长度
42. *************************************************************/
43. int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
44.
45. /*************************************************************
46. close()关闭一个连接,实际上就是关闭该连接对应的 socket
47. *************************************************************/
48. int close(int sockfd);
49.
50. /**************************************************************
51. TCP 数据读写:
52. recv()读取 sockfd 上的数据,buff 和 len 参数分别指定读缓冲区的位置和大小
53. send()往 socket 上写入数据,buff 和 len 参数分别指定写缓冲区的位置和数据长
度
54. flags 参数为数据收发提供了额外的控制
55. **************************************************************/
56. ssize_t recv(int sockfd, void *buff, size_t len, int flags);
57. ssize_t send(int sockfd, const void *buff, size_t len, int flags);
58.
59. /**************************************************************
60. UDP 数据读写:
61. recvfrom()读取 sockfd 上的数据,buff 和 len 参数分别指定读缓冲区的位置和大
小
62. src_addr 记录发送端的 socket 地址
63. addrlen 指定该地址的长度
64. sendto()往 socket 上写入数据,buff 和 len 参数分别指定写缓冲区的位置和数据长
度
65. dest_addr 指定接收数据端的 socket 地址
66. addrlen 指定该地址的长度
67. **************************************************************/
68. ssize_t recvfrom(int sockfd, void *buff, size_t len, int flags,
69. struct sockaddr* src_addr, socklen_t *addrlen);
70. ssize_t sendto(int sockfd, void *buff, size_t len, int flags,
71. struct sockaddr* dest_addr, socklen_t addrlen);
4.TCP 编程流程
TCP 提供的是面向连接的、可靠的、字节流服务。TCP 的服务器端和客户端编程流程如 下:
- socket()方法是用来创建一个套接字,有了套接字就可以通过网络进行数据的收发。这也 是为什么进行网络通信的程序首先要创建一个套接字。创建套接字时要指定使用的服务类 型,使用 TCP 协议选择流式服务(SOCK_STREAM)。
- bind()方法是用来指定套接字使用的 IP 地址和端口。IP 地址就是自己主机的地址,如果 主机没有接入网络,测试程序时可以使用回环地址“127.0.0.1”。端口是一个 16 位的整形值, 一般 0-1024 为知名端口,如 HTTP 使用的 80 号端口。这类端口一般用户不能随便使用。其 次,1024-4096 为保留端口,用户一般也不使用。4096 以上为临时端口,用户可以使用。在 Linux 上,1024 以内的端口号,只有 root 用户可以使用。
- listen()方法是用来创建监听队列。监听队列有两种,一个是存放未完成三次握手的连接, 一种是存放已完成三次握手的连接。listen()第二个参数就是指定已完成三次握手队列的长度。
- accept()处理存放在 listen 创建的已完成三次握手的队列中的连接。每处理一个连接,则 accept()返回该连接对应的套接字描述符。如果该队列为空,则 accept 阻塞。
- connect()方法一般由客户端程序执行,需要指定连接的服务器端的 IP 地址和端口。该方 法执行后,会进行三次握手, 建立连接。
- send()方法用来向 TCP 连接的对端发送数据。send()执行成功,只能说明将数据成功写入 到发送端的发送缓冲区中,并不能说明数据已经发送到了对端。send()的返回值为实际写入 到发送缓冲区中的数据长度。
- recv()方法用来接收 TCP 连接的对端发送来的数据。recv()从本端的接收缓冲区中读取数 据,如果接收缓冲区中没有数据,则 recv()方法会阻塞。返回值是实际读到的字节数,如果 recv()返回值为 0, 说明对方已经关闭了 TCP 连接。
- close()方法用来关闭 TCP 连接。此时,会进行四次挥手。
TCP 服务端代码 TcpServer.c :
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
//服务器
int main()
{
//创建套接字
int sockfd=socket(AF_INET,SOCK_STREAM,0);//tcp:STREAM流协议
if(sockfd= -1)
{
exit(0);
}
struct sockaddr_in saddr,caddr;//saddr服务器地址,caddr:客户端
memset(&saddr,0,sizeof(saddr));//清空
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);//端口
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//点分十进制转换为无符号整形
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
printf("bind failed\n");
exit(0);
}
res=listen(sockfd,5);
if(res==-1)
{
exit(0);
}
while(1)
{
socklen_t len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
printf("accept c=%d\n",c);
char buff[128]={0};
int n=recv(c,buff,127,0);
printf("recv(%d):%s\n",n,buff);
send(c,"ok",2,0);
close(c);
}
}
TCP 客户端代码 TCPClient.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
//客户端
int main()
{
//创建套接字
int sockfd=socket(AF_INET,SOCK_STREAM,0);//tcp:STREAM流协议
if(sockfd= -1)
{
exit(0);
}
struct sockaddr_in saddr;//服务器地址
//清空
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);//端口
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//点分十进制转换为
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//链接服务器
if(res==-1)
{
printf("connect failed\n");
exit(0);
}
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);
send(sockfd,buff,strlen(buff)-1,0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
close(sockfd);
exit(0);
}