wx:嵌入式工程师成长日记
https://mp.weixin.qq.com/s/_eqFaiID2kzFuk3zejFptg?token=382885458&lang=zh_CN
https://mp.weixin.qq.com/s/_eqFaiID2kzFuk3zejFptg?token=382885458&lang=zh_CN

TCP是一个面向连接的,安全的,流式传输协议,这个协议是一个传输层协议。
①面向连接:是一个双向连接,通过三次握手完成,断开连接需要通过四次挥手完成。
②安全:TCP通信过程中,会对发送的每一数据包都会进行校验, 如果发现数据丢失, 会自动重传。
③流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致。
(一)TCP三次握手&四次挥手
三次握手具体过程如下:

第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN。此时客户端处于SYN_SENT 状态。首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。
第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号ISN(s)。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_RCVD 的状态。在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。
第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。
四次挥手具体过程如下:

第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。
第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。
第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。
第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
(二)C语言实现TCP通信

服务端:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>int main(){// 1. 创建监听的套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket");exit(0);}// 2. 将socket()返回值和本地的IP端口绑定到一起struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(10000); // 大端端口addr.sin_addr.s_addr = INADDR_ANY;int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));if(ret == -1){perror("bind");exit(0);}// 3. 设置监听ret = listen(lfd, 128);if(ret == -1){perror("listen");exit(0);}// 4. 阻塞等待并接受客户端连接struct sockaddr_in cliaddr;int clilen = sizeof(cliaddr);int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &clilen);if(cfd == -1){perror("accept");exit(0);}// 5. 和客户端通信while(1){// 接收数据char buf[1024];memset(buf, 0, sizeof(buf));int len = read(cfd, buf, sizeof(buf));if(len > 0){printf("客户端: %s\n", buf);write(cfd, buf, len);}else if(len == 0){printf("客户端断开了连接...\n");break;}else{perror("read");break;}}close(cfd);close(lfd);return 0;}
客户端:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>int main(){// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd == -1){perror("socket");exit(0);}// 2. 连接服务器struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(10000); // 大端端口inet_pton(AF_INET, "192.168.2.3", &addr.sin_addr.s_addr);int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));if(ret == -1){perror("connect");exit(0);}// 3. 和服务器端通信int number = 0;while(1){// 发送数据char buf[1024];sprintf(buf, "hello,server...%d\n", number++);write(fd, buf, strlen(buf)+1);// 接收数据memset(buf, 0, sizeof(buf));int len = read(fd, buf, sizeof(buf));if(len > 0){printf("服务器: %s\n", buf);}else if(len == 0){printf("服务器断开了连接...\n");break;}else{perror("read");break;}sleep(1);}close(fd);return 0;}
UDP是一个面向无连接的,不安全的,报式传输层协议,udp的通信过程默认也是阻塞的。
①UDP通信不需要建立连接 ,因此不需要进行connect()操作
②UDP通信过程中,每次都需要指定数据接收端的IP和端口,和发快递差不多
③UDP不对收到的数据进行排序,在UDP报文的首部中并没有关于数据顺序的信息
UDP对接收到的数据报不回复确认信息,发送端不知道数据是否被正确接收,也不会重发数据。如果发生了数据丢失,不存在丢一半的情况,如果丢当前这个数据包就全部丢失了.

(一)C语言实现UDP通信
服务端:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>int main(){// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_DGRAM, 0);if(fd == -1){perror("socket");exit(0);}// 2. 通信的套接字和本地的IP与端口绑定struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(9999); // 大端addr.sin_addr.s_addr = INADDR_ANY;int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if(ret == -1){perror("bind");exit(0);}char buf[1024];char ipbuf[64];struct sockaddr_in cliaddr;int len = sizeof(cliaddr);// 3. 通信while(1){// 接收数据memset(buf, 0, sizeof(buf));int rlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &len);printf("客户端的IP地址: %s, 端口: %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),ntohs(cliaddr.sin_port));printf("客户端: %s\n", buf);// 数据回复给了发送数据的客户端sendto(fd, buf, rlen, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));}close(fd);return 0;}
客户端:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>int main(){// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_DGRAM, 0);if(fd == -1){perror("socket");exit(0);}// 初始化服务器地址信息struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(9999); // 大端inet_pton(AF_INET, "192.168.2.3", &seraddr.sin_addr.s_addr);char buf[1024];char ipbuf[64];struct sockaddr_in cliaddr;int len = sizeof(cliaddr);int num = 0;// 2. 通信while(1){sprintf(buf, "hello, udp %d....\n", num++);// 发送数据, 数据发送给了服务器sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&seraddr, sizeof(seraddr));// 接收数据memset(buf, 0, sizeof(buf));recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);printf("服务器: %s\n", buf);sleep(1);}close(fd);return 0;}
(二)TCP/UDP应用场景
TCP使用场景
对数据安全性要求高的时:
1.登录数据的传输 (比如用户名密码)
2.文件传输
UDP使用场景
效率高且实时性要求比较高
1.视频聊天、直播
2.通话
