1、网络编程
1.1 进程间的通信方式
linux内核的五大功能
内存管理:内存的分配和回收
进程管理:时间片轮转,上下文切换
文件管理:将一堆0、1转化为人类识别的字符
设备管理:linux下一切皆文件
网络管理:网络协议栈的管理
(协议:发送方和接收方都遵循的,数据发送及数据解析的一组准则)
内核提供三种:无名管道pipie、有名管道fifo、信号sem
systemV提供三种:消息队列msgset、共享内存shm、信号灯集semset
1.2 上述通信的弊端
上述六种只是实现了同一个主机,多个进程之间的数据通信,并不能完成跨主机的通信方式
所有,出现了 socket套接字通信,既能处理同一主机,多进程之间,还能完成跨主机的进程间通信
1.3跨主机通信方式
1.3.1 基于CS模型
客户端服务器模型,要求客户端有一个客户端软件,客户端将数据传输给服务器,由服务器转发给其他客户端
1.3.2 基于BS模型
浏览器服务器模型,由网页上的相关组件跟服务器进行交互,无需安装客户端软件
2、网络体系结构
2.1 TCP/IP协议分成了两个不同的协议
检测网络传输中差错的传输控制协议TCP
专门负责对不同网络进行互联的互联网协议IP
2.2 网络体系结构及OSI开放系统系统互联模型
2.2.1 网络体系结构概念
每一层都有自己独立的功能
通常把功能相近的协议组织在一起放在一层,协议栈。所以每层其实有多个协议
分层的好处:
1、各层之间独立,每层不需要知道下一层如何实现,只需要知道,层与层之间的接口所提供的服务
2、稳定,灵活性好,当任何一层发生变化时,只需要层间接口关系保持不变,而这层上,下的层级不受影响
3、易于实现和维护(针对功能查找特定层)
4、促进标准化工作,每层功能和所提供的服务都有了明确说明
5、结构上不可分割开,每层都可以采用最合适的技术来实现
2.3.2 OSI开放系统互联模型
2.3.3 TCP/IP协议族(簇)的体系结
TCP/IP协议簇是Internet事实上的工业标准
分为四层:应用层-----传输层-----网络层-----链路层(网络接口和物理层)
TCP/ip四层体系结构和OSI七层互联模型的对应关系
2.3.4 数据封包和拆包的过程
一帧数据:
大小:64--1518字节
如果数据大于MTU(最大传输单元,linux最大默认是1500),需分成多次传输
2.3.5 TCP/IP四层结构中常见的 协议
应用层:
HTTP (Hypertext Transfer Protocol) 超文本传输协议
万维网的数据通信的基础
FTP (File Transfer Protocol) 文件传输协议
用于在网络上进行文件传输的一套标准协议,使用TCP传输
TFTP (Trivial File Transfer Protocol) 简单文件传输协议
用于在网络上进行文件传输的一套标准协议,使用UDP传输
SMTP (Simple Mail Transfer Protocol) 简单邮件传输协议
一种提供可靠且有效的电子邮件传输的协议
传输层:
TCP(Transport Control Protocol) 传输控制协议
是一种面向连接的、可靠的、基于字节流的传输层通信协议
UDP(User Datagram Protocol) 用户数据报协议
是一种无连接、不可靠、快速传输的传输层通信协议
网络层:
IP(Internetworking Protocol) 网际互连协议
是指能够在多个不同网络间实现信息传输的协议
ICMP(Internet Control Message Protocol) 互联网控制信息协议
用于在IP主机、路由器之间传递控制消息、ping命令使用的协议
IGMP(Internet Group Management Protocol) 互联网组管理
是一个组播协议,用于主机和组播路由器之间通信
链路层:
ARP(Address Resolution Protocol) 地址解析协议
通过IP地址获取对方mac地址
RARP(Reverse Address Resolution Protocol) 逆向地址解析协议
通过mac地址获取ip地址
每层使用的协议,是由下层决定,并不能乱用
3、TCP和UDP
共同点:同属于传输层协议
区别:
TCP:
1、面向连接的,可靠的字节流服务
2、传输过程中,数据无误、数据无丢失、数据无失序、数据无重复
2.1 TCP会给每个数据包编上编号,该编号称之为序列号
2.2 每个序列号都需要应答包应答,如果没有应答,则会将上面的包重复发送直到正确为止
3、数据传输效率低,耗费资源多
4、数据收发是不同步的
4.1 为了提高效率,TCP会将多个较小,并且发送间隔短的数据包,沾成一个包发送,该现象称为沾包现象
4.2 该沾包算法称之为Nagle算法
5、TCP的使用场景:对传输质量比较高的以及传输大量数据的通信,在需要可靠通信的传输场合,一般使用TCP协议
三次握手和四次挥手原因
在TCP连接中,服务器端的SYN和ACK向客户端发送是一次性发送的
在断开连接的过程中, B端向A端发送的ACK和FIN是分两次发送的。因为在B端接收到A端的FIN后, B端可能还有数据要传输,所以先发送ACK,等B端处理完自己的事情后就可以发送FIN断开连接了
三次握手:
握手过程中使用了 TCP 的标志(flag) —— SYN(synchronize) 和ACK(acknowledgement)。大致流程如下:
SYN(同步序号,表示此报文是一个连接请求或者连接接收报文),ACK(确认位,对接收到报文的确认),FIN(表示发送方发送完数据,用来释放一个连接)
第一次握手:客户端向服务器端发送一个SYN J,表示客户端向服务器端发送一个连接请求报文,该报文的初始序列号为J。客户端进入SYN_SENT状态,等待服务器端确认。
第二次握手:服务器端向客户端响应一个SYN K, 表示服务器端向客户端发送一个连接请求报文,该报文的初始序列号为K。并对SYN J进行确认ACK J+1,服务器端进入SYN_REVD状态。
第三次握手:客户端再向服务器端发送一个确认ACK K+1。客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端和服务器端就可以开始传送数据了。四次挥手:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。先进行关闭的一方将执行主动关闭,而另一方被动关闭。
第一次挥手:客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
第二次挥手:服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
第三次挥手:服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
第四次挥手:客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。TCP粘包问题如何解决
tcp粘包问题处理的方法:
1、定长发送法,发送端在发送数据时都以len为长度进行分包;
2、尾部标记序列法,在每个要发送的数据包的尾部设置一个特殊的字节序列;
3、头部标记分步接收法,定义一个用户报头,在报头中注明每次发送的数据包大小。
UDP
1、面向无连接的,不保证数据可靠的,尽最大努力传输的协议
2、数据传输过程中,可能出现数据丢失、重复、失序现象
3、数据传输效率高,实时性高
4、限制每次传输的数据大小,多出部分直接忽略删除
5、收发是同步的,不会沾包
6、适用场景:发送小尺寸的,在接收到数据给出应答比较困难的情况下
总结
1) TCP提供面向连接的传输,通信前要先建立连接(三次握手机制); UDP提供无连接的传输,通信前不需要建立连接。
2) TCP提供可靠的传输(有序,无差错,不丢失,不重复); UDP提供不可靠的传输。
3) TCP面向字节流的传输,因此它能将信息分割成组,并在接收端将其重组; UDP是面向数据报的传输,没有分组开销。
4) TCP提供拥塞控制和流量控制机制; UDP不提供拥塞控制和流量控制机制。(参考链接:https://blog.csdn.net/striveb/article/details/84063712)
练习:简易TCP和UDP链接
TCP:练习
/* * @Description: TCP 服务器端 */ #include "../header.h" #define SER_PORT 8888 #define SER_IP "192.168.xxx.xxx" int main(int argc, char const *argv[]) { // 1 创建套接字 int sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) { perror("socket"); exit(EXIT_FAILURE); } printf("sfd: %d\n", sfd); // 2 绑定ip和端口号 struct sockaddr_in addr; addr.sin_family = AF_INET; // 通信域 addr.sin_port = htons(SER_PORT); // 端口号 addr.sin_addr.s_addr = inet_addr(SER_IP); // ip地址 if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { // 绑定 perror("bind"); return -1; } printf("bind success\n"); // 3 监听 if (listen(sfd, 128) == -1) { perror("listen"); return -1; } printf("listen success\n"); // 4 等待客户端连接 struct sockaddr_in naddr_in = {0}; socklen_t naddr_len = sizeof(naddr_in); int new_fd = -1; if ((new_fd = accept(sfd, (struct sockaddr *)&naddr_in, &naddr_len)) == -1) { perror("accept"); return -1; } // 如果没有连接进入,会阻塞在这里 printf("对方ip地址:%s, 端口号:%d\n", inet_ntoa(naddr_in.sin_addr), ntohs(naddr_in.sin_port)); printf("accept success\n"); char addr_buff[1024] = {0}; while (1) { memset(addr_buff, 0, sizeof(addr_buff)); int r_num = recv(new_fd, addr_buff, sizeof(addr_buff), 0); if (r_num == 0) { printf("对方已经下线"); break; } printf("%s:%d--%s\n", inet_ntoa(naddr_in.sin_addr), naddr_in.sin_port, addr_buff); strcat(addr_buff, "*-* "); send(new_fd, addr_buff, sizeof(addr_buff), 0); } // 5 关闭套接字 close(new_fd); close(sfd); printf("close success\n"); return 0; }
/* * @Description: TCP客户端 */ #include "../header.h" #define S_PORT 8888 #define S_IP "192.168.xxx.xxx" #define C_PORT 7772 #define C_IP "192.168.xxx.xxx" int main(int argc, char const *argv[]) { int sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) { perror("socket"); return -1; } struct sockaddr_in bind_address = {0}; bind_address.sin_family = AF_INET; bind_address.sin_port = htons(C_PORT); bind_address.sin_addr.s_addr = inet_addr(C_IP); if (bind(sfd, (struct sockaddr *)&bind_address, sizeof(bind_address)) == -1) { perror("bind"); return -1; } printf("bind success\n"); struct sockaddr_in connect_address = {0}; connect_address.sin_family = AF_INET; connect_address.sin_port = htons(S_PORT); connect_address.sin_addr.s_addr = inet_addr(S_IP); if (connect(sfd, (struct sockaddr *)&connect_address, sizeof(connect_address)) == -1) { perror("connect"); return -1; } printf("connect success\n"); char send_buf[1024] = {0}; char recv_buf[1024] = {0}; while (1) { printf("请输入内容:"); bzero(send_buf, sizeof(send_buf)); fgets(send_buf, sizeof(send_buf), stdin); if (strlen(send_buf) > 1) { send_buf[strlen(send_buf) - 1] = 0; } if (send(sfd, send_buf, sizeof(send_buf), 0) == -1) { perror("send"); printf("发送失败"); } bzero(recv_buf, sizeof(recv_buf)); recv(sfd, recv_buf, sizeof(recv_buf), 0); printf("返回值:%s\n", recv_buf); } close(sfd); return 0; }
UDP练习
/* * @Description: UDP 服务器端 */ #include "../header.h" #define S_IP "192.168.xxx.xxx" #define S_PORT 8888 int main(int argc, char const *argv[]) { int server_fd = socket(AF_INET, SOCK_DGRAM, 0); if (server_fd == -1) { perror("socket"); return -1; } struct sockaddr_in ser_address = {0}; ser_address.sin_family = AF_INET; ser_address.sin_port = htons(S_PORT); ser_address.sin_addr.s_addr = inet_addr(S_IP); if (bind(server_fd, (struct sockaddr *)&ser_address, sizeof(ser_address)) == -1) { perror("bind"); return -1; } struct sockaddr_in rev_address = {0}; socklen_t rev_len = sizeof(rev_address); char recv_buf[1024] = {0}; while (1) { bzero(recv_buf, sizeof(recv_buf)); recvfrom(server_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&rev_address, &rev_len); printf("收到消息:%s\n", recv_buf); strcat(recv_buf, "--------"); if (sendto(server_fd, recv_buf, strlen(recv_buf), 0, (struct sockaddr *)&rev_address, sizeof(rev_address)) == -1) { perror("sendto"); return -1; } printf("回复消息发送成功\n"); } close(server_fd); return 0; }
/* * @Description: UDP 客户端 */ #include "../header.h" #define S_IP "192.168.xxx.xxx" #define S_PORT 8888 #define C_IP "192.168.xxx.xxx" #define C_PORT 6666 int main(int argc, char const *argv[]) { int cli_fd = socket(AF_INET, SOCK_DGRAM, 0); if (cli_fd == -1) { perror("socket"); return -1; } struct sockaddr_in cli_address = {0}; cli_address.sin_family = AF_INET; cli_address.sin_port = htons(C_PORT); cli_address.sin_addr.s_addr = inet_addr(C_IP); if (bind(cli_fd, (struct sockaddr *)&cli_address, sizeof(cli_address)) == -1) { perror("bind"); return -1; } struct sockaddr_in send_address = {0}; send_address.sin_family = AF_INET; send_address.sin_port = htons(S_PORT); send_address.sin_addr.s_addr = inet_addr(S_IP); char send_buf[1024] = {0}; int send_res = 0; while (1) { printf("请输入内容:"); bzero(send_buf, sizeof(send_buf)); fgets(send_buf, sizeof(send_buf), stdin); if (strlen(send_buf) > 1) { send_buf[strlen(send_buf) - 1] = 0; } send_res = sendto(cli_fd, send_buf, sizeof(send_buf), 0, (struct sockaddr *)&send_address, sizeof(send_address)); if (send_res == -1) { perror("sendto"); return -1; } bzero(send_buf, sizeof(send_buf)); recvfrom(cli_fd, send_buf, sizeof(send_buf), 0, NULL, NULL); printf("返回值:%s\n", send_buf); } close(cli_fd); return 0; }