嵌入式LINUX——————网络TCP
一、TCP连接
1.TCP特点:
(1)面向链接
(2)面向字节流
(3)安全可靠的传输协议,因为会先建立连接
(4)占用资源开销大,效率低,实时性不佳,机制复杂
2.安全可靠机制
(1)三次握手:【客户端发起】
指建立tcp连接时,需要客户端和服务端总共发送三次报文确认连接。(确保双方都已经 做好收发数据的准备)
ACK:响应报文
STN:请求建立一个连接
(2)四次挥手:【两端都可发起】
断开一个tcp连接,需要客户端和服务端发送四个报文以确认断开。(确保断开前双方都已经收发完毕)
FIN:finish标志,表示释放连接
(3) 应答机制
每一个tcp头部有个序列号和确认应答号,TCP给每一抱会有一个序列号,发送方会把第一个编号给序号,在接受方应答的时候,会把确认序列号置最后一个编号的下一个编号回应
通过序列号和确定序列号来确定收发的
(4)超时重传机制
3.编程
(1)socket
(2)connect
(1)功能: 发送三次握手链接请求
(2)参数:
sockfd:套接字文件描述符
addr:存放目的地址空间首地址
addrlen:目的地址长度
(3)返回值:
成功返回0
失败返回-1
(3)send
(1)功能:发送数据
(2)参数:
sockfd:套接字文件描述符
buf:存放数据空间首地址
len:数据长度
(3)返回值:
成功返回发送字节数
失败返回-1
(4)recv
(1)功能:接收数据
(2)参数:
sockfd:套接字文件描述符
buf:存放数据空间首地址
len:最多接收数据长度
flags:接收属性默认为0
(3)返回值:
成功返回实际接收字节数
失败返回-1
对方连接断开,直接返回,不在阻塞,没有数据返回0;
(5)bind
(6)listen
(1)功能:监听三次握手链接请求
(2)参数:
sockfd:套接字文件描述符
backlog:最多允许等待尚未处理的三次握手链接个数
(3)返回值:
成功返回0
失败返回-1
(7)accept
(1)功能::处理三次握手等待队列中的第一个请求并建立一个用来通信的新套接字
(2)参数:
sockfd:套接字文件描述符
addr:存放发送端IP地址空间首地址
addrlen:想要接收的IP地址的长度
(3) 返回值:
成功返通讯套接字
失败返回-1
4.提高效率
(1)延迟应答
(2)捎带应答
(3)流量控制机制
发送端根据窗口的数据大小,去动态控制发送端的数据;0--65535,接收端根据自己的能力,去调整窗口的大小,65535的时候,接受端处理能力最强,发送端可以发快一点。
(4)滑动窗口
滑动窗口大小:是TCP流量控制得一个手段。目的是告诉对方, 本端得TCP接受缓冲区还能容纳多少字节得数据,这样对方就可以控制发送数据的速度,从而达到流量控制,16bit,因而窗口最大65535.
本质是一段缓冲区:通过指针把缓冲区分为几个部分:
已发送并且收到应答的数据、已发送未收到应答的数据、未发送但在对方处理能力内的数据;未发送但不在对方处理能力内的数据;
慢慢滑动,已发送并且收到的数据滑出。
二、 TCP粘包问题【高频面试题】
TCP协议是面向字节流的协议,接收方不知道消息的界限,不知道一次提取多少数据,这就造成了粘包问题。
1. 粘包问题出现的原因:
(1)发送端:需要等缓冲区满时才发送出去,造成粘包;发送数据太快
(2)接收端:不及时的接收缓冲区内的包,造成多个包接收。处理数据太慢导致数据在缓冲区缓存
2.避免粘包问题的方法:
(1)对于定长的包,保证每次都按固定大小发送和读取即可;// 结构体
1)问题:
结构体对齐问题:假设发送方和接收方分别32字节、64字节;在不同字节平台结构体对齐长度不一样;所以要确保双方结构体对齐方式一样;或者指定按照1个字节对齐,注意,结构体不可放指针;
在双方通信时,发送方的发送数据类型不一样,接收很难确认和区分接受的大小;所以不适用于数据类型多样化;
(2) 对于变长的包,还可以在包和包之间使用明确的分隔符,这个分隔符是由程序员自己来定的,只要保证分隔符不和正文冲突即可。应用层根据分隔符进行解析
(3)自定义一个应用层的数据协议帧;
如果数据里面有帧头或者帧尾 ====》在帧头后面加入一个长度,这个长度规定,向后多少个
首先找帧头和有效字节长度,向后读有效字节长度并且读完了看最后是不是帧尾
在后面加个校验
所以包含:帧头(AA)、帧尾(BB)、有效数据长度(len)、校验(例如8位和校验()、16位和校验、CRC校验、和其他复杂校验算法)

三、TCP和UDP区别
都是网络中传输层的传输协议
UDP叫做,无需建立链接、直接发送给接收方,需要对方的地址,面向数据包;存在丢包,尽最大努力叫覅,不安全不可靠;由于没有很多机制,头部小,资源开销打;要求实时性、不要求数据;
TCP需要建立TXP链接,面向字节流,有一系列机制可以确保安全机制,三次握手四次挥手、应答机制、流量控制、流动窗口、超时重传;由于很多机制。头部大。资源开销打,要求数据传输安全性高
四、代码练习
(1)使用TCP实现全双工(进程)
客户端
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include<unistd.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include<pthread.h>
#include <errno.h>
int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50001);seraddr.sin_addr.s_addr = inet_addr("192.168.245.128");char buf[1024] = {0};int ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("connect fail");return -1;}pid_t pid = fork();if(pid < 0){perror("fork fail");return -1;}if(pid > 0){while(1){memset(buf, 0, sizeof(buf));fgets(buf, sizeof(buf), stdin);ssize_t size = send(sockfd, buf, strlen(buf)-1, 0);if(size < 0){perror("send fail");return -1;}}}if(pid == 0){while(1){size_t size = recv(sockfd, buf, sizeof(buf), 0);if(size < 0 ){perror("recv fail");return -1;}if(size == 0){break;}buf[strlen(buf)] = '\0';printf("ser buf = %s\n",buf);}}close(sockfd);
}
服务端
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include<unistd.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include<pthread.h>
#include <errno.h>
int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50001);seraddr.sin_addr.s_addr = inet_addr("192.168.245.128");char buf[1024] = {0};int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind fail");return -1;}ret = listen(sockfd, 10);if(ret < 0){perror("listen fail");return -1;}int connfd = accept(sockfd, NULL, NULL);if(connfd < 0){perror("connfd fail");return -1;}pid_t pid = fork();if(pid < 0){perror("fork fail");return -1;}if(pid > 0){while(1){ssize_t size = recv(connfd, buf, sizeof(buf), 0);if(size < 0){perror("size fail");return -1;}if(size == 0){break;}buf[strlen(buf)] = '\0';printf("buf = %s\n",buf);}}if(pid == 0){while(1){memset(buf, 0, sizeof(buf));fgets(buf, sizeof(buf), stdin);size_t size = send(connfd, buf, strlen(buf)-1, 0);if(size < 0){perror("send fail");return -1;}}}close(connfd);return 0;
}
(2)使用TCP实现文件传输(传输过来的文件名不变)
客户端
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include<unistd.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include<pthread.h>
#include <errno.h>
int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50025);seraddr.sin_addr.s_addr = inet_addr("192.168.1.193");char buf[1024] = {0};char buffer[1024] = {0};int ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("connect fail");return -1;}int fd = open("../1.txt", O_RDWR);int fp = open("2.txt", O_RDWR);if(fd < 0){perror("open fail");return -1;}if(fp < 0){perror("open fail");return -1;}pid_t pid = fork();if(pid < 0){perror("fork fail");return -1;}if(pid > 0){while(1){memset(buf, 0, sizeof(buf));memset(buffer, 0, sizeof(buffer));fgets(buf, sizeof(buf), stdin);if(strncmp(buf, "1.txt", 5) == 0){int ret1 =read(fd, buffer, sizeof(buffer));printf("ret1 = %d\n",ret1);// ssize_t size = send(sockfd, buffer, strlen(buffer)-1, 0);ssize_t size = send(sockfd, buffer, ret1, 0);if(size < 0){perror("send fail");return -1;}memset(buffer, 0, sizeof(buffer));}}}if(pid == 0){while(1){memset(buffer, 0, sizeof(buffer));size_t size = recv(sockfd, buffer, sizeof(buffer), 0);if(size < 0 ){perror("recv fail");return -1;}buffer[strlen(buffer)] = '\0';write(fp, buffer,strlen(buffer));//buf[strlen(buf)] = '\0';printf("ser buffer = %s\n",buffer);memset(buffer, 0, sizeof(buffer));}}close(sockfd);
}
服务端
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include<unistd.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include<pthread.h>
#include <errno.h>
int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50025);seraddr.sin_addr.s_addr = inet_addr("192.168.1.193");char buf[1024] = {0};char buffer[1024] = {0};int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind fail");return -1;}ret = listen(sockfd, 10);if(ret < 0){perror("listen fail");return -1;}int connfd = accept(sockfd, NULL, NULL);if(connfd < 0){perror("connfd fail");return -1;}int fd = open("1.txt", O_RDWR);int fp = open("../2.txt", O_RDWR);pid_t pid = fork();if(pid < 0){perror("fork fail");return -1;}if(pid > 0){while(1){memset(buffer, 0, sizeof(buffer));ssize_t size = recv(connfd, buffer, sizeof(buffer), 0);if(size < 0){perror("size fail");return -1;}buffer[strlen(buffer)] = '\0';write(fd, buffer,strlen(buffer));//buf[strlen(buf)] = '\0';printf("buffer = %s\n",buffer);memset(buffer, 0, sizeof(buffer));}}if(pid == 0){while(1){memset(buffer, 0, sizeof(buffer));memset(buf, 0, sizeof(buf));fgets(buf, sizeof(buf), stdin);if(strncmp(buf, "2.txt", 5) == 0){int ret1 =read(fp, buffer, sizeof(buffer));printf("ret1 = %d\n",ret1);size_t size = send(connfd, buffer, strlen(buffer)-1, 0);if(size < 0){perror("send fail");return -1;}memset(buffer, 0, sizeof(buffer));}}}close(connfd);return 0;
}