linux下的网络编程:TCP(传输控制协议)编程
一、TCP概述
(一)特点
1. 面向数据包
2. 无连接
3. 尽最大努力交付,不安全不可靠(数据丢包、数据乱序)
4. 机制简单,资源开销小,数据实时性高
5. 可实现一对一、一对多的通信
(二)TCP机制 ————“三次握手和四次挥手”
各标志位
- SYN : 请求建立连接标志位
- ACK :响应报文标志位
- FIN : 请求断开连接标志位
1)三次握手:TCP建立连接时,需要进行三次握手,为了确保收发双方通信之前都已准备就绪。
2)TCP四次挥手:TCP断开连接时,需要进行四次挥手,确保断开连接前双方都已通信结束。
二、TCP的编程过程
(一)TCP编程流程图
(二)核心函数
1.连接函数
连接函数 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
功能 | 请求与服务端建立连接 |
参数 | sockfd:套接字 addr:要连接的服务端的地址信息 addrlen:服务端地址大小 |
返回值 | 成功:0 失败:-1 |
2.发送函数
发送函数 | ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
功能 | 发送网络数据 |
参数 | sockfd:网络套接字 buf:要发送的数据首地址 len:发送的字节数 flags:0 :按照默认方式发送 |
返回值 | 成功:实际发送的字节数 失败:-1 |
3.监听函数
int listen(int sockfd, int backlog); | |
功能 | 监听建立三次握手的客户端 |
参数 | sockfd:监听套接字 backlog:最大允许监听的客户端个数 |
返回值 | 成功:0 失败:-1 |
4.接收函数
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len); | |
功能 | 接收建立三次握手的客户端,并产生一个通讯套接字 |
参数 | socket:监听套接字 address:客户端的地址信息 address_len:客户端地址长的指针 |
返回值 | 成功:通讯套接字 失败:-1 |
5.从网络套接字上收取数据函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags); | |
功能 | 从网络套接字上接收数据 |
参数 | sockfd:通讯套接字 buf:存放接收数据的首地址 len:期望接收到的字节数 flag : 0:默认方式接收(阻塞) |
返回值 | 成功:实际接收到的字节数 失败:-1 对方断开连接:0 |
(三)示例程序
实现功能:客户端不断从终端接收数据,使用TCP发送给服务端,服务端输出。
1.客户端
#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h> /* See NOTES */
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>int main(int argc,const char *argv[])
{//创建套接字int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){perror("socket error");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50000);seraddr.sin_addr.s_addr = inet_addr("192.168.100.102");//请求建立连接int ret = connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));if(ret < 0){perror("connect error");return -1;}while(1){char buff[1024] = {0};fgets(buff,sizeof(buff),stdin); //从终端读取数据ssize_t cnt = send(sockfd,buff,strlen(buff),0); //发送数据if(cnt < 0){perror("send error");return -1;}printf("cnt = %ld\n",cnt);}close(sockfd); //关闭套接字return 0;
}
2.服务端
#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h> /* See NOTES */
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>int main(int argc,const char *argv[])
{//创建监听套接字——sockfdint sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){perror("socket error");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50000);seraddr.sin_addr.s_addr = inet_addr("192.168.100.102");//绑定IP地址int ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));if(ret < 0){perror("bind error");return -1;}//监听和服务端建立连接的客户端ret = listen(sockfd,10);if(ret < 0){perror("listen error");return -1;}//接收完成三次握手的客户端并产生一个通信套接字——connfdint connfd = accept(sockfd,NULL,NULL);if(connfd < 0){perror("accept error");return -1;}while(1){char buff[1024] = {0};ssize_t cnt = recv(connfd,buff,sizeof(buff),0); //接收数据if(cnt < 0){perror("recv error");return -1;}if(0 == cnt){break;}printf("cnt = %ld,buff = %s\n",cnt,buff);}close(sockfd); //关闭监听套接字close(connfd); //关闭通信套接字return 0;
}
三、TCP粘包问题
TCP粘包问题:发送方应用层发送的多包数据,将来在接收方可能一次读到,多包数据产生了粘连。
1.产生原因
- 发送方速度较快,TCP底层可能对多包数据进行重新组帧;
- 接收方数据处理速度较慢,导致多包数据在接收缓冲区缓存,应用层读时,一次将多包数据读出。
2.解决方法
- 调整发送速率
- 发送指定大小,将来接收方也接受指定大小(即,使用结构体)。
注意:
1. 跨平台之间的数据传输时,注意结构体对齐问题。
struct a
{
char a;
int b;
long c;
};
32bits平台和64位平台所占用的字节不同。
- 应用层位发送的数据增加分隔符,利用分隔符解析(eg: hello world\nhow are you\n)
- 封装自定义数据帧格式进行发送(协议),严格根据协议进行解析。
示例:
AA C0 00 00 00 F0 00 BB 10 A0 00 00 00 10 校验 BB AA C0 00 00 00 F0 00 BB 10 A0 00 00 00 10 校验 BB AA C0 00 00 00 F0 00 BB 10 A0 00 00 00 10 校验 BB
帧头:AA
帧尾:BB
有效数据长度:C0
有效数据:00 00 00 F0 00 BB 10 A0 00 00 00 10
校验:
8位和校验
16位和校验
CRC校验
四、TCP的其他机制
(一)TCP的头部标志
SYN:请求建立连接标志位
ACK:响应报文标志位
PSH:携带数据标志位,通知接收方该从缓冲区读数据
FIN: 请求断开连接标志位
RST:复位标志位
URG: 紧急数据标志位
(二)其他机制
1)确保其安全可靠
1. 三次握手和四次挥手机制
2. 应答机制:TCP对于每一包数据都会给出相应的应答。发送数据时序列号表示这包数据的起始编号,响应报文中的确认号是接收方收到的最后一个字节编号+1。
3. 超时重传机制:当数据发送出去等待指定时间没有收到响应,此时认为这包数据丢失则进行冲传。
4. 滑动窗口机制:一段缓冲区,缓存TCP已发送未收到响应,准备发送等数据
2)提高效率
1. 延迟应答机制:发送数据的同时可以等待应答。
2. 流量控制机制:结合TCP头部的窗口大小,动态调整发送速率。
3. 捎带应答机制:ACK报文可能和应用层的数据同时发送。