一、TCP概述
(一)特点
-
面向数据包
-
无连接
-
尽最大努力交付,不安全不可靠(数据丢包、数据乱序)
-
机制简单,资源开销小,数据实时性高
-
可实现一对一、一对多的通信
(二)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[])
{
//创建监听套接字------sockfd
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");
//绑定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;
}
//接收完成三次握手的客户端并产生一个通信套接字------connfd
int 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.解决方法
- 调整发送速率
- 发送指定大小,将来接收方也接受指定大小(即,使用结构体)。
注意:
- 跨平台之间的数据传输时,注意结构体对齐问题。
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)确保其安全可靠
-
三次握手和四次挥手机制
-
应答机制:TCP对于每一包数据都会给出相应的应答。发送数据时序列号表示这包数据的起始编号,响应报文中的确认号是接收方收到的最后一个字节编号+1。

-
超时重传机制:当数据发送出去等待指定时间没有收到响应,此时认为这包数据丢失则进行冲传。
-
滑动窗口机制:一段缓冲区,缓存TCP已发送未收到响应,准备发送等数据

2)提高效率
- 延迟应答机制:发送数据的同时可以等待应答。

-
流量控制机制:结合TCP头部的窗口大小,动态调整发送速率。
-
捎带应答机制:ACK报文可能和应用层的数据同时发送。