一、TCP协议介绍
① TCP(Transmission 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_INET6:IPv6
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
返回值:
成功返回发送字节个数
失败返回-1
2.4 recv(接收数据)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:从套接字中接收数据
参数:
sockfd:套接字文件描述符
buf:存放接收到的数据空间的首地址
len:最多接收元素个数
flags:属性默认为0
返回值:
成功返回接收字节的个数
失败返回-1
如果对方关闭连接, 则返回0
2.5 listen(监听)
int listen(int sockfd, int backlog);
功能:监听链接请求,监听三次握手
参数:
sockfd:监听套接字文件描述符
backlog:尚未处理的请求的最大的个数,允许同时监听的客户端的最大个数,上限128
返回值:
成功返回0
失败返回-1
2.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\\n) (3)定长发送
(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的区别
| 连接性 | 面向连接 通信前必须通过三次握手建立连接,通信结束后四次挥手释放连接。 | 无连接 无需建立连接,直接发送数据,类似发短信或寄信。 |
| 可靠性 | 安全可靠 具备确认应答、超时重传、序号排序、流量控制和拥塞控制机制,保证数据无差错、不丢失、不重复、按序到达。 | 不安全不可靠 尽最大努力交付,不保证数据是否到达、是否按序到达。若网络丢包或出错,UDP 本身不纠正。 |
| 机制复杂度 | 实现机制复杂 需要维护连接状态、计算校验和、管理缓冲区、实现复杂的拥塞控制算法(如慢启动、快重传)。 | 实现机制简单 仅包含端口号、长度和校验和字段,甚至校验和也可选,无复杂的逻辑控制。 |
| 资源开销 | 资源开销大 每个连接需占用内核内存存储状态信息,首部开销大(20字节),且需维持连接的心跳包。 | 资源开销小 无需维护连接状态,首部开销小(仅8字节),CPU 和内存占用极低。 |
| 数据形式 | 字节流 数据无边界,发送方写入的多次数据可能在接收方合并为一次读取,即“粘包”现象。 | 数据报 数据有边界,应用层交下来的报文,UDP 直接打包发送,接收方一次接收一个完整报文。 |
| 传输效率 | 相对较慢 由于握手、确认机制和拥塞控制的介入,实时性较差,头部开销大,传输效率低。 | 高效快速 无握手、无确认,无拥塞控制,即使网络拥塞也不降低发送速率,实时性极强。 |
| 首部大小 | 20 字节 (最少) | 8 字节 (固定) |
| 适用场景 | 文件传输 (FTP)、网页浏览 (HTTP/HTTPS)、邮件 (SMTP/POP3)、远程登录 (SSH/Telnet) —— 对数据完整性要求极高的场景。 | 视频直播、语音通话 (VoIP)、在线游戏、DNS 查询、广播/组播 —— 对实时性敏感,允许少量丢包的场景。 |






