拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Linux网络编程02-TCP协议

一、TCP协议介绍①TCPTransmission Control Protocol传输控制协议流式套接字位于传输层②TCP特点面向数据流面向连接安全可靠的传输协议TCP机制复杂③三次握手1目的TCP 建立连接时需要进行三次握手以确保通信安全可靠并确认通信双方都已准备就绪。该过程必须由客户端发起。2过程三次握手由客户端发起。首先客户端发送一个带有 SYN 标志请求建立连接的数据包随后服务端回复一个同时带有 ACK响应报文和 SYN 标志的数据包最后客户端再回复一个带有 ACK 标志的数据包完成连接建立。④四次挥手1目的断开连接确保通信双方在断开前已完成数据的收发。断开可由客户端或服务端发起。如果双方通信结束(不传输数据)此时四次挥手也可能变成三次挥手。2过程以客户端发起为例客户端发送一个带 FIN 标志的报文请求断开连接FIN 标志位于 TCP 头部。服务端回复带 ACK 标志的报文确认若还有数据要发送则以带 PSH 标志的报文继续发送直至数据发送完毕。数据发送完毕后服务端向客户端发送带 FIN 标志的报文请求断开连接。客户端回复带 ACK 标志的报文连接断开完成。注意TCP在四次挥手之前默认保持TCP通信连接的状态二、TCP函数接口2.1 socket创建套接字int socket(int domain, int type, int protocol); 功能创建一个套接字 参数 domain使用的协议族 AF_INET: IPv4 AF_INET6IPv6 type传输方式 SOCK_STREAM 流式套接字TCP SOCK_DGRAM:数据报套接字UDP SOCK_RAW 原始套接字 protocol: 0 默认套接字类型对应的协议 返回值 成功返回新的文件描述符 失败返回-1 tcp_socket socket(AF_INET, SOCK_STREAM, 0); udp_socket socket(AF_INET, SOCK_DGRAM, 0); raw_socket socket(AF_INET, SOCK_RAW, protocol);2.2 connect建立连接int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 功能发送一个链接请求建立连接 参数 sockfd:建立连接的套接字文件描述符 addr:目的空间地址服务端的地址信息 addrlen:目的地址长度服务端地址大小 返回值 成功返回0 失败返回-1注意有可能会发生阻塞2.3 send发送数据ssize_t send(int sockfd, const void *buf, size_t len, int flags); 功能向套接字中发送数据 参数 sockfd:文件描述符 buf:发送数据空间的首地址 len:发送数据的长度 flags:属性 默认为0 返回值 成功返回发送字节个数 失败返回-12.4 recv接收数据ssize_t recv(int sockfd, void *buf, size_t len, int flags); 功能从套接字中接收数据 参数 sockfd:套接字文件描述符 buf:存放接收到的数据空间的首地址 len:最多接收元素个数 flags:属性默认为0 返回值 成功返回接收字节的个数 失败返回-1 如果对方关闭连接 则返回02.5 listen监听int listen(int sockfd, int backlog); 功能监听链接请求监听三次握手 参数 sockfd:监听套接字文件描述符 backlog:尚未处理的请求的最大的个数允许同时监听的客户端的最大个数上限128 返回值: 成功返回0 失败返回-12.6 accept接收客户端int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 功能处理监听队列中的第一个连接请求接收完成三次握手的客户端 参数 sockfd:监听套接字文件描述符 addr:存放连接端的地址信息空间首地址 addrlen:想要接收的地址的长度 调用后 存放实际接收到的地址的长度 返回值 成功返回新的文件描述符通讯套接字 失败返回-1注意阻塞等待客户端建立连接练习1使用TCP实现全双工聊天的功能//clientA.c #include head.h int sockfd 0; pthread_t tid_send; pthread_t tid_recv; void *sendfun(void *arg) { char tmpbuff[4096] {0}; ssize_t nret 0; while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); gets(tmpbuff); nret send(sockfd, tmpbuff, strlen(tmpbuff), 0); if (-1 nret) { perror(fail to send); return NULL; } if (0 strcmp(tmpbuff, .quit)) { break; } } pthread_cancel(tid_recv); return NULL; } void *recvfun(void *arg) { char tmpbuff[4096] {0}; ssize_t nret 0; while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); nret recv(sockfd, tmpbuff, sizeof(tmpbuff), 0); if (-1 nret) { perror(fail to recv); return NULL; } else if (0 nret) { break; } if (0 strcmp(tmpbuff, .quit)) { break; } printf(RECV:%s\n, tmpbuff); } pthread_cancel(tid_send); return NULL; } int CreateTcpConnection(const char *pIp, int Port) { int ret 0; struct sockaddr_in seraddr; sockfd socket(AF_INET, SOCK_STREAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } seraddr.sin_family AF_INET; seraddr.sin_port htons(Port); seraddr.sin_addr.s_addr inet_addr(pIp); ret connect(sockfd, (struct sockaddr *)seraddr, sizeof(seraddr)); if (-1 ret) { perror(fail to connect); return -1; } return sockfd; } int main(void) { int sockfd 0; sockfd CreateTcpConnection(192.168.0.138, 50000); pthread_create(tid_send, NULL, sendfun, NULL); pthread_create(tid_recv, NULL, recvfun, NULL); pthread_join(tid_send, NULL); pthread_join(tid_recv, NULL); close(sockfd); return 0; }//clientB.c #include head.h pthread_t tid_send; pthread_t tid_recv; int confd 0; void *sendfun(void *arg) { char tmpbuff[4096] {0}; ssize_t nret 0; while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); gets(tmpbuff); nret send(confd, tmpbuff, strlen(tmpbuff), 0); if (-1 nret) { perror(fail to send); return NULL; } if (0 strcmp(tmpbuff, .quit)) { break; } } pthread_cancel(tid_recv); return NULL; } void *recvfun(void *arg) { char tmpbuff[4096] {0}; ssize_t nret 0; while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); nret recv(confd, tmpbuff, sizeof(tmpbuff), 0); if (-1 nret) { perror(fail to recv); return NULL; } else if (0 nret) { break; } if (0 strcmp(tmpbuff, .quit)) { break; } printf(RECV:%s\n, tmpbuff); } pthread_cancel(tid_send); return NULL; } int CreateListenSocket(const char *pIp, int Port) { int ret 0; int sockfd 0; struct sockaddr_in seraddr; sockfd socket(AF_INET, SOCK_STREAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } seraddr.sin_family AF_INET; seraddr.sin_port htons(Port); seraddr.sin_addr.s_addr inet_addr(pIp); ret bind(sockfd, (struct sockaddr *)seraddr, sizeof(seraddr)); if (-1 ret) { perror(fail to bind); return -1; } ret listen(sockfd, 10); if (-1 ret) { perror(fail to listen); return -1; } return sockfd; } int main(void) { int sockfd 0; sockfd CreateListenSocket(192.168.0.138, 50000); confd accept(sockfd, NULL, NULL); if (-1 confd) { perror(fail to accept); return -1; } pthread_create(tid_send, NULL, sendfun, NULL); pthread_create(tid_recv, NULL, recvfun, NULL); pthread_join(tid_send, NULL); pthread_join(tid_recv, NULL); close(confd); close(sockfd); return 0; }练习2使用TCP编写发送端和接收端实现文件的发送//send.c #include head.h int CreateTcpConnection(const char *pIp, int Port) { int sockfd 0; int ret 0; struct sockaddr_in seraddr; sockfd socket(AF_INET, SOCK_STREAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } seraddr.sin_family AF_INET; seraddr.sin_port htons(Port); seraddr.sin_addr.s_addr inet_addr(pIp); ret connect(sockfd, (struct sockaddr *)seraddr, sizeof(seraddr)); if (-1 ret) { perror(fail to connect); return -1; } return sockfd; } int main(int argc, char *argv[]) { int sockfd 0; ssize_t nret 0; int fd 0; char tmpbuff[4096] {0}; if (argc ! 2) { fprintf(stderr, Usage:./a.out filename\n); return -1; } sockfd CreateTcpConnection(192.168.0.138, 50000); sprintf(tmpbuff, %s\r\n, argv[1]); nret send(sockfd, tmpbuff, strlen(tmpbuff), 0); if (-1 nret) { perror(fail to send); return -1; } fd open(argv[1], O_RDONLY); if (-1 fd) { perror(fail to open); return -1; } while (1) { nret read(fd, tmpbuff, sizeof(tmpbuff)); if (nret 0) { break; } nret send(sockfd, tmpbuff, nret, 0); if (-1 nret) { perror(fail to send); return -1; } } close(fd); close(sockfd); return 0; }//recv.c #include head.h int CreateListenSocket(const char *pIp, int Port) { int ret 0; int sockfd 0; struct sockaddr_in seraddr; sockfd socket(AF_INET, SOCK_STREAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } seraddr.sin_family AF_INET; seraddr.sin_port htons(Port); seraddr.sin_addr.s_addr inet_addr(pIp); ret bind(sockfd, (struct sockaddr *)seraddr, sizeof(seraddr)); if (-1 ret) { perror(fail to bind); return -1; } ret listen(sockfd, 10); if (-1 ret) { perror(fail to listen); return -1; } return sockfd; } int main(void) { int sockfd 0; int confd 0; char tmpbuff[4096] {0}; ssize_t nret 0; int fd 0; char *ptmp NULL; sockfd CreateListenSocket(192.168.0.138, 50000); confd accept(sockfd, NULL, NULL); if (-1 confd) { perror(fail to accept); return -1; } nret recv(confd, tmpbuff, sizeof(tmpbuff), 0); if (-1 nret) { perror(fail to recv); return -1; } ptmp strstr(tmpbuff, \r\n); *ptmp \0; fd open(tmpbuff, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (-1 fd) { perror(fail to open); return -1; } ptmp 2; write(fd, ptmp, nret - (ptmp - tmpbuff)); while (1) { nret recv(confd, tmpbuff, sizeof(tmpbuff), 0); if (-1 nret) { perror(fail to recv); return -1; } else if (0 nret) { break; } write(fd, tmpbuff, nret); } close(fd); close(confd); close(sockfd); return 0; }//head.h #ifndef __HEAD_H__ #define __HEAD_H__ #include stdio.h #include stdlib.h #include string.h #include sys/types.h #include sys/stat.h #include sys/wait.h #include unistd.h #include fcntl.h #include time.h #include dirent.h #include pthread.h #include semaphore.h #include signal.h #include sys/ipc.h #include sys/msg.h #include sys/shm.h #include sys/sem.h #include sys/socket.h #include netinet/in.h #include netinet/ip.h #include arpa/inet.h #endif三、TCP粘包问题① 粘包接收端应用层收到的数据多包数据粘连在一起。② 原因由于发送和接收速率不匹配导致接收缓冲区缓存了多包数据应用层读取时一次性读出TCP发送端发送数据时将多包数据重新组包数据会发生粘连一起发送给接收端③ 解决粘包问题1 加延时2设置数据边界每包数据增加分隔符应用层根据分隔符解析文件名\r\n3定长发送4带有帧头帧尾的自定义协议应用层根据协议精准解析数据帧结构帧头 有效数据长度 数据 校验和 帧尾常用帧头和帧尾AA/BB 7E/ 5A与A5 EB90(航天常用)常用校验方式8位和校验(将有效数据累加只保留低八位)16和校验(将有效数据累加保留高低八位两字节)CRC校验四、TCP报文头部① 当ACK置1时 32位确认序号才能正常使用② 标志位介绍FIN结束通信SYN请求应答RST重新建立连接PSH发送数据ACK确认应答URG数据加急③ 发送过程中保证数据传输TCP通过序号和确认号保证数据传输的完整性。序号该包数据起始字节的编号本次发送的序号一般为上次接收到的确认号。确认序号接收方希望发送方下次发送数据首字节的序号本次发送的确认序号一般为上次接收到的序号 实际接收的长度。④ 滑动窗口机制保证传输过程中对流量进行控制。通过调节TCP包头中的窗口大小来设置发送窗口和接口窗口大小完成对流量及发送速率的控制。⑤ 拥塞控制机制如果网络拥堵带宽占用较大 TCP自带拥塞控制机制来实现数据可靠通信。⑥ 掉线检测机制TCP三次握手建立后四次挥手结束前整个过程通信双方保持连接可以通过掉线检测机制检测TCP连接是否保持。五、TCP和UDP的区别对比维度TCP (传输控制协议)UDP (用户数据报协议)连接性面向连接通信前必须通过三次握手建立连接通信结束后四次挥手释放连接。无连接无需建立连接直接发送数据类似发短信或寄信。可靠性安全可靠具备确认应答、超时重传、序号排序、流量控制和拥塞控制机制保证数据无差错、不丢失、不重复、按序到达。不安全不可靠尽最大努力交付不保证数据是否到达、是否按序到达。若网络丢包或出错UDP 本身不纠正。机制复杂度实现机制复杂需要维护连接状态、计算校验和、管理缓冲区、实现复杂的拥塞控制算法如慢启动、快重传。实现机制简单仅包含端口号、长度和校验和字段甚至校验和也可选无复杂的逻辑控制。资源开销资源开销大每个连接需占用内核内存存储状态信息首部开销大20字节且需维持连接的心跳包。资源开销小无需维护连接状态首部开销小仅8字节CPU 和内存占用极低。数据形式字节流数据无边界发送方写入的多次数据可能在接收方合并为一次读取即“粘包”现象。数据报数据有边界应用层交下来的报文UDP 直接打包发送接收方一次接收一个完整报文。传输效率相对较慢由于握手、确认机制和拥塞控制的介入实时性较差头部开销大传输效率低。高效快速无握手、无确认无拥塞控制即使网络拥塞也不降低发送速率实时性极强。首部大小20 字节 (最少)8 字节 (固定)适用场景文件传输 (FTP)、网页浏览 (HTTP/HTTPS)、邮件 (SMTP/POP3)、远程登录 (SSH/Telnet) ——对数据完整性要求极高的场景。视频直播、语音通话 (VoIP)、在线游戏、DNS 查询、广播/组播 ——对实时性敏感允许少量丢包的场景。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门