目录
一、网络基本概念
1.网络
2.互联网
3.ip地址
4.MAC地址
5.端口号Port
6.网络协议
二.网络分层模型
1.数据链路层
2.网络层
3.传输层
4.应用层
三、网络应用程序通信流程
四、socket 网络编程
1.主机字节序列和网络字节序列
2.套接字地址结构
(1)通用 socket 地址结构
(2)专用 socket 地址结构
(3)IP 地址转换函数
3.网络编程接口
网络:由若干结点和连接这些结点的链路组成,网络中的结点可以是计算机,交换机、 路由器等设备。
网络设备有:交换机、路由器、集线器
传输介质有:双绞线、同轴电缆、光纤
一个简单的网络示意图
把多个网络连接起来就构成了互联网。目前最大的互联网就是我们常说的因特网。
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 进制数表示。
查看ip命令—— windows:ipconfig ;Linux:ifconfig
127.0.0.1 自己电脑的ip,用于测试
在局域网中,硬件地址又称为物理地址或者 MAC 地址,长度 48 位,是固化在计算机适配器的 ROM 中的地址。因此假定连接在局域网上的一台计算机的适配器坏了而我们更换了 一个新的适配器,那么这台计算机的局域网的“地址”也就改变了,虽然这台计算机的地理 位置没有发生变化。其次,当我们把一个笔记本从一个城市带到另一个城市时,虽然地理位置改变了,但是电脑在局域网中的“地址”仍然不变。由此可见,局域网上某个主机的“地址”根本不能告诉我们这台主机位于什么地方。在网络上方便的寻找某个主机,还得依靠 ip 地址进行。
一个ip地址表示一台主机。主机运行一台进程,需要和其他主机联网通信,这就依赖于端口号。
端口可以理解为应用程序代号,是软件层次的,用于表示一个进程/应用程序,目的是方便查找,可以实现不同主机之间的通信。
ip+port->进程 ,想要实现进程间通信需要:自己的ip+端口与对方的ip+端口
网络协议就是一组网络规则的集合,是我们共同遵守的约定或标准。
常见的协议:
(面试会考:
分层的目的?哪些层?各层次功能?
OSI 的 7 层模型与 tcp/ip 协议族体系 4 层结构
主要功能是:通过各种控制协议,将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。 在计算机网络中由于各种干扰的存在,物理链路是不可靠的。因此,这一层的主要功能 是在物理层提供的比特流的基础上,通过差错控制,使有差错的物理线路变为无差错的数据链路,即提供可靠的通过物理介质传输数据的方法。 该层通常又被分为介质访问控制(MAC)和逻辑链路控制(LLC)两个子层。
TCP/IP 协议体系结构中,数据链路层的功能描述为实现网卡接口的网络驱动程序,以 处理数据在物理媒介上的传输,不同的物理网络具有不同的电气特性,网络驱动程序隐藏了 这些细节,为上层协议提供了一个统一的接口。这一层主要关注的三个基本问题是:封装成帧,透明传输和差错检测。
网络层实现数据包的选路和转发。广域网或者说互联网通常使用众多分级的路由器来连接分散的主机或者局域网,因此,通信的两台主机一般不是直接相连的,而是通过多个中间 结点(路由器)连接的。网络层的任务就是选择这些中间结点,以确定两台主机之间的通信路径。同时,网络层对上层协议隐藏了网络拓扑连接的细节,使得在传输层和网络应用程序看来,通信的双方是直接相连的。
网络层最核心的协议是 IP 协议(Internet Protocol,因特网协议)。IP 协议根据数据包的目的 IP地址来决定何如投递它。如果数据包不能直接发送给目标主机,那么 IP 协议就是为它寻找一个合适的吓一跳路由器,并将数据包交付给该路由器来转发。多次重复这一过程,数据包最终到达目标主机,或者由于发送失败而被丢弃。可见,IP 协议使用逐跳的方式确定通 信路径。
网络层另外一个重要的协议是 ICMP 协议(因特网控制报文协议)。它是 IP 协议的重要 补充,主要用于检测网络连接。
IP 协议为上层协议提供无状态、无连接、不可靠的服务。
无状态是指通信双方不同步传输数据的状态信息,因此所有 IP 数据报的发送、传输和 接收都是相互独立、没有上下文关系的。这种服务最大的缺点是无法处理乱序和重复的 IP 数据报。虽然 IP 数据报头部提供了一个标识字段用以唯一标识一个 IP 数据报,但它是被用 来处理 IP 分片和重组的,而不是用来指示接收顺序的。无状态的优点是简单、高效。无须 为保持通信状态而分配一些内核资源,也无须再每次通信时携带状态信息。
无连接是指 IP 通信双方都不长久地维持对方的任何信息。这样,上层协议每次发送数 据的时候,都必须明确指定对方的 IP 地址。 不可靠是指 IP 协议不能保证 IP 数据报准确地到达接收端,它只是承诺尽最大努力。
IPV4 头部结构如下:
传输层为两台主机上的应用程序提供端到端的通信。与网络层使用的逐跳通信的方式不 同,传输层只关心通信的起始端和目的端,而不在乎数据包的中转过程。
传输层协议主要有三个:TCP 协议、UDP 协议和 SCTP 协议 TCP 协议(传输控制协议)为应用层提供可靠的、面向连接的和基于流的服务。
TCP 协 议使用超时重传、确认应答等方式来确保数据包被正确的发送至目的端,因此 TCP 服务是 可靠的。使用 TCP 协议通信的双方必须先建立 TCP 连接,并在内核中为该连接维持一些必 要的数据结构,比如连接状态,读写缓冲区等。当通信结束时,双方必须关闭连接以释放这 些内核数据。TCP 服务是基于流的,基于流的数据没有边界(长度)限制,它源源不断地从 通信地一端流入另一端。发送端可以逐个字节地向数据流中写入数据,接收端可以逐个字节地将它们读出。
TCP 协议报头:
UDP 协议(用户数据报协议)则与 TCP 协议完全相反,它为应用层提供不可靠、无连 接、基于数据报地服务。“不可靠”意味着 UDP 协议无法保证数据从发送端正确地传送到目 的端。如果数据在中途丢失,或者目的端通过数据校验发现数据错误而将其丢弃,则 UDP 协议只是简单地通知应用程序发送失败。因此,如果要使 UDP 协议可靠,那么应用程序通 常要自己处理数据确认、超时重传等逻辑。UDP 是无连接的,即通信双发不保持一个长久 的联系,因此应用程序每次发送数据都要明确指定 接收端的地址。基于数据报的服务,是相 对基于流的服务而言的。每次 UDP 数据报都有一个长度,接收端必须以该长度为最小单位 将其所有内容一次性读出,否则数据将被截断。
SCTP 协议(流控制传输协议)是一种相对较新的传输层协议,它是为了再因特网上传 输电话信号而设计的。这里暂时不讨论 SCTP 协议。
负责处理应用程序的逻辑。
如下图,应用程序 A 要将数据”hello” 传给网络上另外一台主机上的应用程序 B。
uint32_t htonl(uint32_t hostlong); // 长整型的主机字节序转网络字节序
uint32_t ntohl(uint32_t netlong); // 长整型的网络字节序转主机字节序
uint16_t htons(uint16_t hostshort); // 短整形的主机字节序转网络字节序
uint16_t ntohs(uint16_t netshort); // 短整型的网络字节序转主机字节序
套接字:具有通过网络收发数据的能力
socket 网络编程接口中表示 socket 地址的是结构体 sockaddr,其定义如下:
#include
struct sockaddr
{sa_family_t sa_family;char sa_data[14];
};ket.h>
sa_family 成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对 应。常见的协议族和对应的地址族如下图所示:
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_INET6u_inet16_t sin6_port; // 端口号:用网络字节序表示u_int32_t sin6_flowinfo; // 流信息,应设置为 0struct in6_addr sin6_addr; // IPV6 地址结构体u_int32_t sin6_scope_id; // scope ID,尚处于试验阶段
};
通常,人们习惯用点分十进制字符串表示 IPV4 地址,但编程中我们需要先把它们转化 为整数方能使用,下面函数可用于点分十进制字符串表示的 IPV4 地址和网络字节序整数表 示的 IPV4 地址之间的转换:
#include
in_addr_t inet_addr(const char *cp); //字符串表示的 IPV4 地址转化为网络字节序
char* inet_ntoa(struct in_addr in); // IPV4 地址的网络字节序转化为字符串表示
1. #include
2. #include
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 的服务器端和客户端编程流程如 下:
TCP 服务端代码 TcpServer.c :
#include
#include
#include
#include
#include
#include
#include//服务器
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
#include
#include
#include
#include
#include
#include//客户端
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);
}