描述符的本质
socket 描述符(在类 Unix 系统中是一个非负整数,在 Windows 系统中是 SOCKET
句柄)本质上是操作系统为了管理网络连接而分配的一个索引值,它用于标识一个打开的网络套接字,方便后续对该套接字进行读写、关闭等操作。
socket 描述符(socket descriptor)是可以用于和服务器建立连接的。
客户端的 socket 描述符
在客户端,通常会使用一个 socket 描述符来完成与服务器的连接和通信,其创建和使用过程如下:
1. 创建 socket 描述符
客户端首先使用 socket
函数创建一个 socket 描述符,这个描述符代表了客户端用于网络通信的端点。示例代码如下:
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8888
#define SERVER_IP "127.0.0.1"
int main() {
int client_socket;
// 创建客户端 socket 描述符
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1) {
perror("socket creation failed");
return -1;
}
// 后续代码...
close(client_socket);
return 0;
}
这里 client_socket
就是客户端创建的 socket 描述符,AF_INET
表示使用 IPv4 地址族,SOCK_STREAM
表示使用面向连接的 TCP 协议。
2. 连接到服务器
客户端使用 connect
函数,通过这个 socket 描述符向服务器发起连接请求:
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("Invalid address/ Address not supported");
return -1;
}
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connection failed");
close(client_socket);
return -1;
}
此时,客户端的 client_socket
描述符就与服务器建立了连接,可以用于后续的数据收发。
3. 数据收发和关闭连接
客户端可以使用 send
和 recv
函数通过 client_socket
描述符进行数据的发送和接收,通信结束后使用 close
函数关闭该描述符:
char message[] = "Hello, server!";
if (send(client_socket, message, sizeof(message), 0) == -1) {
perror("send failed");
close(client_socket);
return -1;
}
char buffer[1024];
ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received == -1) {
perror("recv failed");
close(client_socket);
return -1;
}
close(client_socket);
服务器端的 socket 描述符
在服务器端,通常会使用至少两个 socket 描述符来完成与客户端的通信,分别是监听 socket 描述符和连接 socket 描述符。
1. 监听 socket 描述符
服务器首先创建一个监听 socket 描述符,用于监听客户端的连接请求:
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8888
#define BACKLOG 5
int main() {
int listen_socket;
// 创建监听 socket 描述符
listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket == -1) {
perror("socket creation failed");
return -1;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定地址和端口
if (bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(listen_socket);
return -1;
}
// 开始监听
if (listen(listen_socket, BACKLOG) == -1) {
perror("listen failed");
close(listen_socket);
return -1;
}
// 后续代码...
close(listen_socket);
return 0;
}
这里的 listen_socket
就是监听 socket 描述符,它的作用是绑定服务器的地址和端口,并开始监听客户端的连接请求。BACKLOG
表示连接请求队列的最大长度。
2. 连接 socket 描述符
当有客户端发起连接请求时,服务器使用 accept
函数从监听队列中取出一个连接请求,并创建一个新的连接 socket 描述符来与该客户端进行通信:
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_socket;
// 接受客户端连接
client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_socket == -1) {
perror("accept failed");
close(listen_socket);
return -1;
}
// 后续代码...
close(client_socket);
client_socket
就是连接 socket 描述符,它代表了服务器与特定客户端之间的连接。服务器可以使用 send
和 recv
函数通过这个描述符与客户端进行数据的收发。
- 客户端的 socket 描述符:用于创建与服务器的连接,以及后续与服务器之间的数据收发和关闭连接操作。
- 服务器的监听 socket 描述符:用于绑定服务器的地址和端口,监听客户端的连接请求。
- 服务器的连接 socket 描述符:当有客户端连接请求到达时,服务器创建该描述符来与特定客户端进行一对一的数据通信。