TCP的服务监听步骤(等待客户端连接前)
TCP 服务器通过以下步骤完成从初始化到等待客户端连接,为后续的数据传输(send()
/recv()
)奠定了基础
一、创建套接字(Socket)
- 作用:套接字是网络通信的端点,用于标识通信中的双方(IP 地址和端口号)。服务器首先需要创建一个套接字,作为后续监听和通信的基础。
- 实现:应用程序通过系统调用(如
socket()
)创建套接字,指定地址族(如 IPv4 用AF_INET
)、传输层协议(TCP 用SOCK_STREAM
)等参数。 - 示例:在 Linux 中,
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
会创建一个 TCP 套接字,返回套接字描述符sockfd
。
二、绑定套接字到本地地址和端口(Bind)
- 作用:将创建的套接字与服务器的本地 IP 地址和指定端口号绑定,确保客户端能通过该地址和端口找到服务器。
- 实现:
- 应用程序定义一个包含本地 IP 地址和端口号的结构体(如
sockaddr_in
)。 - 通过系统调用(如
bind()
)将套接字与该结构体绑定。
- 应用程序定义一个包含本地 IP 地址和端口号的结构体(如
- 注意:
- 端口号通常选择 1024 以上的非特权端口(避免与系统服务冲突)。
- 若 IP 地址设为
INADDR_ANY
(通配地址),表示服务器监听所有可用的本地网络接口。
- 示例:
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
三、设置监听状态(Listen)
- 作用:将绑定后的套接字转换为监听套接字,使其能够接收客户端的连接请求。同时,操作系统会为该套接字维护一个连接请求队列(未完成三次握手的客户端请求)。
- 实现:通过系统调用(如
listen()
)设置监听,参数包括监听套接字和队列的最大长度(backlog
,即最多能同时等待处理的连接请求数)。 - 注意:
backlog
的值需根据服务器性能和预期并发量设置,若队列满,新的连接请求会被拒绝(客户端可能收到 “连接超时” 错误)。 - 示例:
listen(sockfd, 5);
(表示最多允许 5 个连接请求在队列中等待)。
四、等待并接受客户端连接(Accept)
- 作用:监听套接字进入阻塞状态(默认情况),等待客户端的连接请求。当有客户端发起连接时,服务器通过
accept()
系统调用接受连接,生成一个新的连接套接字用于与该客户端通信。 - 过程:
- 客户端通过
connect()
发起 TCP 连接请求,与服务器进行三次握手。 - 三次握手完成后,连接请求从 “未完成队列” 移至 “已完成队列”。
- 服务器调用
accept()
从 “已完成队列” 中取出一个连接请求,创建新的连接套接字(与监听套接字的 IP 和端口相同,但用于单独的客户端通信)。
- 客户端通过
- 注意:
- 监听套接字始终保持监听状态,用于接收新的连接请求;连接套接字则用于与特定客户端的数据传输。
- 若需处理并发连接,服务器通常会通过多线程、多进程或 I/O 复用(如
select
、epoll
)来同时管理多个连接套接字。
- 示例:
int new_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len);
(new_fd
即为新的连接套接字)。
总结:TCP 服务监听的完整流程
- 创建套接字(
socket()
)→ 2. 绑定地址和端口(bind()
)→ 3. 设置监听(listen()
)→ 4. 接受连接(accept()
)。
服务端与客户端通信(3握手4挥手)
TCP 通信遵循 "三次握手建立连接、数据传输、四次挥手断开连接" 的经典模式,这种模式确保了通信的可靠性和有序性。
1. 连接建立:三次握手
TCP 是面向连接的协议,在进行实际数据传输前,通信双方必须先建立连接,这个过程被形象地称为 "三次握手"(Three-way Handshake)。
- 第一次握手:客户端发送 SYN(同步序列编号)报文段,告知服务器客户端的初始序列号(ISN),并请求建立连接。
- 第二次次握手:服务器收到 SYN 后,返回一个 SYN+ACK(确认)报文段,包含服务器的初始序列号,并确认收到客户端的 SYN(ACK 值 = 客户端 ISN+1)。
- 第三次握手:客户端收到服务器的 SYN+ACK 后,发送一个 ACK 报文段,确认收到服务器的 SYN(ACK 值 = 服务器 ISN+1)。
三次握手完成后,TCP 连接正式建立,双方可以开始数据传输。这种设计有效防止了因网络延迟导致的 "已失效的连接请求报文段" 被服务器接收,从而避免资源浪费。
2. 数据传输阶段
连接建立后,进入数据传输阶段。TCP 通过以下机制保证数据传输的可靠性:
- 序列号与确认机制:每个数据字节都有一个序列号,接收方收到数据后会返回确认信息,告知发送方已成功接收的数据量。
- 超时重传:发送方设置超时计时器,若在规定时间内未收到确认,则重传数据。
- 流量控制:通过滑动窗口机制,控制发送方的发送速率,避免接收方缓冲区溢出。
- 拥塞控制:检测网络拥塞状态并调整发送速率,避免网络过载。
3. 连接终止:四次挥手
当通信结束需要断开连接时,TCP 使用 "四次挥手"(Four-way Wavehand)过程:
- 第一次挥手:主动关闭方发送 FIN(结束)报文段,告知对方要关闭连接。
- 第二次挥手:被动关闭方收到 FIN 后,返回 ACK 确认,此时主动关闭方到被动关闭方的连接半关闭。
- 第三次挥手:被动关闭方准备好关闭连接后,也发送一个 FIN 报文段。
- 第四次挥手:主动关闭方收到 FIN 后,返回 ACK 确认,被动关闭方到主动关闭方的连接也半关闭。等待一段时间确保确认报文送达后,连接完全关闭。
四次挥手的设计是因为 TCP 连接是全双工的,允许双方独立关闭各自的发送通道。
服务端的全流程
1. 调用 socket 函数创建 socket(监听socket)
2. 调用 bind 函数 将 socket绑定到某个ip和端口的二元组上
3. 调用 listen 函数 开启侦听
4. 当有客户端请求连接上来后,调用 accept 函数接受连接,产生一个新的 socket(客户端 socket)
5. 基于新产生的 socket 调用 send 或 recv 函数开始与客户端进行数据交流
6. 通信结束后,调用 close 函数关闭监听 socket
1. 三次握手(建立连接)发生在第 4 步:accept()
函数执行期间
- 当服务器调用
listen()
后,进入 “监听” 状态,内核会维护两个队列:- 未完成连接队列:客户端已发送 SYN 但三次握手未完成的请求。
- 已完成连接队列:三次握手已完成、等待服务器处理的连接。
- 当客户端调用
connect()
发起连接时,内核会自动完成三次握手:- 客户端发送 SYN → 服务器内核接收并放入未完成队列,返回 SYN+ACK(第二次握手)。
- 客户端返回 ACK(第三次握手)→ 服务器内核将连接从 “未完成队列” 移至 “已完成队列”。
- 此时服务器调用
accept()
函数,只是从 “已完成队列” 中取出一个已建立的连接,并创建新的客户端 socket。三次握手的整个过程由内核在listen()
之后、accept()
返回之前自动完成,应用程序无需干预。
2. 四次挥手(断开连接)发生在数据传输结束后,由 close()
函数触发
- 当通信双方(客户端或服务器)决定结束连接时,调用
close()
函数会触发内核执行四次挥手:- 主动关闭方调用
close()
→ 内核发送 FIN 报文(第一次挥手)。 - 被动关闭方收到 FIN 后,内核自动返回 ACK(第二次挥手),此时主动关闭方向被动关闭方的连接 “半关闭”。
- 被动关闭方处理完剩余数据后,调用
close()
→ 内核发送 FIN 报文(第三次挥手)。 - 主动关闭方收到 FIN 后,内核自动返回 ACK(第四次挥手),等待超时后连接完全关闭。
- 主动关闭方调用
- 注意:通常服务器会先关闭 “客户端 socket”(与单个客户端的连接),最后再关闭 “监听 socket”(停止接受新连接)。四次挥手由内核在
close()
调用后自动完成,应用程序只需调用close()
触发这个过程。