套接字(socket)是应用层与传输层之间的编程接口,通过 IP 地址与端口号标识通信端点。服务器在端口上监听,客户端发起连接后双方即可收发字节流。下列示例基于 POSIX 套接字 API(Linux 等);与错误处理、多线程结合可构建并发网络服务。
套接字的概念
套接字可视为网络通信的句柄。按协议划分常见两类:
- TCP(流式套接字):面向连接、可靠、按序传输字节流。
- UDP(数据报套接字):无连接,不保证送达与顺序,开销较低。
以下以 TCP 为例:客户端发送 Hello from client,服务器回复 Hello from server。
服务器先 socket→bind→listen→accept 等着,客户端 socket→connect 连上来,之后两边互发数据,最后都 close。
完整示例:TCP 服务器与客户端
server.c:建服务器
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080
int main(int argc, char const* argv[]) {
int server_fd, new_socket;
ssize_t valread;
struct sockaddr_in address;
int opt = 1;
socklen_t addrlen = sizeof(address);
char buffer[1024] = { 0 };
char* hello = "Hello from server";
/* 1. 创建 socket 文件描述符 */
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
/* 2. 允许地址端口复用,避免重启时报 address already in use */
if (setsockopt(server_fd, SOL_SOCKET,
SO_REUSEADDR | SO_REUSEPORT, &opt,
sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
/* 3. 绑定地址和端口 */
if (bind(server_fd, (struct sockaddr*)&address,
sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
/* 4. 进入监听状态,等待连接 */
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
/* 5. 接受一个客户端的连接 */
if ((new_socket = accept(server_fd, (struct sockaddr*)&address,
&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
/* 6. 读客户端发来的数据,再回一句 */
valread = read(new_socket, buffer, 1024 - 1);
printf("%s\n", buffer);
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
/* 7. 关闭连接和监听 socket */
close(new_socket);
close(server_fd);
return 0;
}client.c:连服务器
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080
int main(int argc, char const* argv[]) {
int status, valread, client_fd;
struct sockaddr_in serv_addr;
char* hello = "Hello from client";
char buffer[1024] = { 0 };
/* 1. 创建 socket */
if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
/* 2. 把字符串形式的 IP 转成二进制 */
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
/* 3. 主动连接服务器 */
if ((status = connect(client_fd, (struct sockaddr*)&serv_addr,
sizeof(serv_addr))) < 0) {
printf("\nConnection Failed \n");
return -1;
}
/* 4. 发数据、收回复 */
send(client_fd, hello, strlen(hello), 0);
printf("Hello message sent\n");
valread = read(client_fd, buffer, 1024 - 1);
printf("%s\n", buffer);
/* 5. 关闭连接 */
close(client_fd);
return 0;
}编译并运行:
gcc server.c -o server
gcc client.c -o client
./server & # 终端 1 先跑服务器
./client # 终端 2 跑客户端输出:
客户端:Hello message sent
Hello from server
服务器:Hello from client
Hello message sent服务器端步骤
- socket():创建套接字,获得文件描述符。
- setsockopt()(可选):如
SO_REUSEADDR便于快速重启服务。 - bind():绑定本地 IP 与端口。
- listen():标记为被动监听;
backlog为等待队列长度。 - accept():接受连接,返回用于通信的新描述符。
- send() / recv()(或
read/write):在新描述符上收发数据。 - close():关闭描述符,释放资源。
相关原型:
sockfd = socket(domain, type, protocol);
setsockopt(sockfd, level, optname, optval, optlen);
bind(sockfd, (struct sockaddr*)&addr, addrlen);
listen(sockfd, backlog);
new_socket = accept(sockfd, (struct sockaddr*)&addr, &addrlen);
send(sockfd, buf, len, flags);
recv(sockfd, buf, len, flags);
close(fd);domain 常用 AF_INET(IPv4);type 为 SOCK_STREAM(TCP)或 SOCK_DGRAM(UDP);protocol 常传 0 由系统选择默认协议。
客户端步骤
- socket():创建套接字。
- connect():向服务器地址发起连接。
- send() / recv():交换数据。
- close():关闭连接。
UDP 与 TCP 对比
| 对比项 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠、有序 | 可能丢包、乱序 |
| 开销 | 握手与确认带来额外延迟 | 通常更低 |
| 典型场景 | HTTP、文件传输 | 实时音视频、DNS 查询 |
UDP 常用 sendto / recvfrom 并在每次调用中指定对端地址,无需 listen / accept。
常见问题
- 连接失败:确认客户端目标 IP、端口与服务器
bind一致,防火墙未阻断。 - address already in use:端口仍被占用,可等待释放或设置
SO_REUSEADDR。 - 阻塞:默认套接字为阻塞模式,
accept、recv无数据时会等待;可按需设为非阻塞或配合select/poll。
本节介绍了 TCP 套接字的基本流程。继续深入时可结合C11 泛型选择等语言特性组织协议解析与工具库;多连接场景宜结合线程或事件驱动模型设计。