connect、listen和accept¶
connect
、listen
和 accept
是 Linux 网络编程中用于 TCP 通信的三个关键系统调用,它们的作用和使用场景各不相同。以下是它们的区别、异同以及为什么 connect
不能替代 listen
和 accept
的详细解释。
1. connect
、listen
和 accept
的区别¶
函数 | 作用 | 使用场景 | 调用者 |
---|---|---|---|
connect |
客户端主动连接到服务端。 | 客户端 | 客户端 |
listen |
服务端将套接字设置为监听状态,等待客户端的连接请求。 | 服务端 | 服务端 |
accept |
服务端从监听套接字的连接队列中取出一个已完成的连接请求,并创建新套接字。 | 服务端 | 服务端 |
2. connect
、listen
和 accept
的异同¶
2.1 相同点¶
- 都是用于 TCP 网络通信的系统调用。
- 都需要一个已创建的套接字(通过
socket
创建)。 - 都涉及客户端和服务端之间的连接。
2.2 不同点¶
特性 | connect |
listen |
accept |
---|---|---|---|
调用者 | 客户端 | 服务端 | 服务端 |
作用 | 主动连接到服务端 | 设置套接字为监听状态 | 接受客户端的连接请求 |
参数 | 服务端地址和端口 | 连接队列的最大长度 | 客户端地址信息(可选) |
返回值 | 成功返回 0 ,失败返回 -1 |
成功返回 0 ,失败返回 -1 |
成功返回新套接字,失败返回 -1 |
阻塞行为 | 阻塞直到连接成功或失败 | 非阻塞,仅设置监听状态 | 阻塞直到有连接请求到达 |
使用顺序 | 客户端在 socket 后调用 |
服务端在 bind 后调用 |
服务端在 listen 后调用 |
3. connect
能否替代 listen
和 accept
?¶
不能,原因如下:
3.1 功能不同¶
connect
是客户端主动连接到服务端。listen
和accept
是服务端被动接受客户端的连接请求。
3.2 角色不同¶
connect
是客户端的行为。listen
和accept
是服务端的行为。
3.3 通信方向不同¶
connect
是单向的(客户端 → 服务端)。listen
和accept
是双向的(服务端 ← 客户端)。
3.4 实现机制不同¶
connect
直接发起 TCP 三次握手。listen
设置套接字为监听状态,并创建连接队列。accept
从连接队列中取出已完成的连接请求,并创建新的套接字。
4. 为什么需要 listen
和 accept
?¶
4.1 listen
的作用¶
- 将套接字设置为监听状态,使其能够接受客户端的连接请求。
- 创建连接队列,用于存放未处理的连接请求。
4.2 accept
的作用¶
- 从连接队列中取出一个已完成的连接请求。
- 创建一个新的套接字,用于与客户端进行数据通信。
4.3 如果没有 listen
和 accept
¶
- 服务端无法接受客户端的连接请求。
- 客户端即使调用
connect
,也无法成功连接到服务端。
5. 示例对比¶
5.1 客户端使用 connect
¶
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(8080);
connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
5.2 服务端使用 listen
和 accept
¶
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(8080);
bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(sockfd, 10);
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
6. 总结¶
connect
、listen
和accept
是 TCP 网络编程中不可或缺的三个系统调用,分别用于客户端连接、服务端监听和接受连接。connect
不能替代listen
和accept
,因为它们的功能、角色和使用场景完全不同。- 服务端必须使用
listen
和accept
来接受客户端的连接请求,而客户端必须使用connect
来主动连接到服务端。
什么是已连接的socket¶
在Linux网络编程中,已连接的套接字是指已经成功建立了通信链路(如TCP连接)的套接字。这种套接字可以用于数据的发送和接收。以下是判断套接字是否已连接的几种情况:
1. TCP套接字¶
- 客户端:
- 当客户端调用
connect()
成功连接到服务器后,客户端的套接字就变成了已连接的套接字。 - 此时,客户端可以使用
send()
和recv()
与服务器通信。
- 当客户端调用
- 服务器:
- 服务器调用
accept()
接受客户端的连接请求后,会返回一个新的套接字描述符。 - 这个新的套接字是已连接的套接字,服务器可以通过它与客户端通信。
- 服务器调用
总结:
- 客户端的套接字在 connect()
成功后变为已连接的套接字。
- 服务器的套接字在 accept()
返回后,新的套接字是已连接的套接字。
2. UDP套接字¶
- UDP 是无连接的协议,因此严格来说,UDP 套接字没有“已连接”的概念。
- 但是,UDP 套接字可以通过
connect()
绑定一个默认的对端地址。- 调用
connect()
后,UDP 套接字可以使用send()
和recv()
,而不需要每次指定对端地址。 - 这种套接字可以称为“已连接的UDP套接字”,但它并不建立真正的连接,只是绑定了默认的对端地址。
- 调用
3. 已连接套接字的特征¶
- 可以使用
send()
和recv()
进行数据传输。 - 对于 TCP 套接字,连接是双向的,双方都可以发送和接收数据。
- 对于 UDP 套接字,
connect()
只是绑定了默认的对端地址,数据传输仍然是单向的。
4. 如何判断套接字是否已连接¶
- 对于 TCP 套接字:
- 客户端:调用
connect()
成功后,套接字已连接。 - 服务器:调用
accept()
返回的新套接字是已连接的。
- 客户端:调用
- 对于 UDP 套接字:
- 调用
connect()
后,套接字绑定了默认对端地址,可以视为“已连接”。
- 调用
- 使用
getsockopt()
检查套接字状态:int is_connected; socklen_t len = sizeof(is_connected); getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &is_connected, &len); if (is_connected == 0) { // 套接字已连接 } else { // 套接字未连接 }
5. 已连接套接字的使用场景¶
- TCP:
- 客户端和服务器通过已连接的套接字进行双向通信。
- UDP:
- 已连接的UDP套接字可以简化通信,避免每次调用
sendto()
和recvfrom()
时指定地址。
- 已连接的UDP套接字可以简化通信,避免每次调用
6. 未连接的套接字¶
- 对于 TCP 套接字:
- 客户端未调用
connect()
,或者connect()
失败。 - 服务器未调用
accept()
接受连接。
- 客户端未调用
- 对于 UDP 套接字:
- 未调用
connect()
绑定默认对端地址。
- 未调用
总结¶
- 已连接的套接字是指已经建立通信链路(TCP)或绑定了默认对端地址(UDP)的套接字。
- TCP 套接字通过
connect()
和accept()
变为已连接。 - UDP 套接字通过
connect()
绑定默认对端地址后,可以视为“已连接”。 - 已连接的套接字可以使用
send()
和recv()
进行数据传输。