这几天学习了TCP通信。它和UDP通信是网络通信的重点。
TCP是面向连接的协议,正式收发数据之前,要通过三次握手建立连接,通信结束使用四次挥手断开连接。
三次握手
- 客户端(主动发起连接)发送SYN报文
- 服务器返回 ACK+SYN
- 客户端回复ACK,连接建立完成。
四次挥手
用来结束TCP连接,保证数据完整传输。
如果双方没有待传输数据,四次挥手也会简化成三次挥手。
主动关闭端发送FIN
被动关闭端回复ACK;如果数据全部发送完毕,会继续发送ACK+FIN
被动关闭端再发送FIN
主动关闭端回复ACK
TCP依靠序号、确认序号保证数据完整可靠传输。
序号:代表当前数据包起始字节编号
确认序号:接收方告诉发送方,下一次希望收到的字节编号
还有滑动窗口做流量控制,拥塞控制应对网络拥堵,掉线检测机制维护连接状态。
TCP粘包问题
TCP是流式协议,没有消息边界。连续发送多份数据,接收端有可能把多条数据包粘在一起收到。
常见解决办法:
- 发送之间增加延时
- 设置消息边界,比如用\r\n做分隔符
- 使用定长发送
TCP报文头部
TCP头部固定20字节。里面有多个标志位:
SYN:请求建立连接
ACK:确认应答
FIN:结束通信
RST:重置连接
PSH:推送数据
URG:紧急数据
ACK标志置1的时候,32位确认序号才会生效。
TCP socket核心函数
客户端函数
-
socket() 创建套接字
int tcpsocket = socket(AF_INET, SOCK_STREAM, 0);
AF_INET代表IPv4,SOCK_STREAM代表TCP流套接字。
-
connect():客户端主动发起连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket文件描述符
addr:目标服务器地址
addrlen:地址结构体长度
返回0成功,‑1失败。
-
send():向套接字发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
buf:发送缓冲区首地址;len:发送长度;flags一般填0
返回:成功返回发送字节数;失败返回‑1。
-
recv():从套接字接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
buf:接收缓冲区;len:最大接收字节;flags默认0
返回:成功返回收到字节;失败‑1;返回0代表对方关闭连接。
服务器端函数
-
listen():开启监听,等待客户端连接请求
int listen(int sockfd, int backlog);
backlog:内核中未处理连接请求队列的最大个数。成功返回0,失败‑1。
-
accept():取出监听队列第一个客户端连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:监听socket
addr:保存客户端地址信息
addrlen:传入地址结构体大小,调用后输出实际地址长度
返回值:返回新的文件描述符,后续和客户端通信就使用这个新fd。
TCP与UDP对比
TCP UDP
连接 面向连接 无连接
可靠性 可靠安全 不可靠
资源开销 开销大 开销小
实现逻辑 复杂 简单
UDP不用建立连接,速度快,但是会丢包,不保证顺序。
小结
- TCP要先三次握手建连接,结束四次挥手;存在粘包,要自己处理消息边界。
- 客户端:socket → connect → send/recv
- 服务器:socket → listen → accept → send/recv
- recv返回0代表对端断开,这点写代码一定要留意。