Socket 是 Unix/Linux 下网络编程的核心接口。本文将详细介绍 TCP 与 UDP 编程模型,从基础 socket 创建到并发服务器实现,帮助你掌握网络通信技能。
Socket 编程基础
Socket(套接字)是进程间通信的一种方式,支持本地和跨主机通信。常见的 socket 类型有:
- TCP (SOCK_STREAM) - 面向连接、可靠传输
- UDP (SOCK_DGRAM) - 无连接、不可靠但高效
- 原始 Socket (SOCK_RAW) - 访问底层协议
TCP 客户端与服务器
TCP 编程遵循「创建 socket → 绑定地址 → 监听 → 接受连接 → 通信」流程:
tcp_server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define PORT 8080 int main(void) { /* 1. 创建 socket */ int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket failed"); exit(EXIT_FAILURE); } /* 2. 绑定地址 */ struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; /* 监听所有网卡 */ addr.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } /* 3. 监听连接 */ if (listen(server_fd, 10) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Server listening on port %d\n", PORT); /* 4. 接受客户端连接 */ struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); char buffer[1024]; read(client_fd, buffer, sizeof(buffer)); printf("Received: %s\n", buffer); char *msg = "Hello from server!"; write(client_fd, msg, strlen(msg)); close(client_fd); close(server_fd); return 0; }
tcp_client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define SERVER_IP "127.0.0.1" #define PORT 8080 int main(void) { int sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)); char *msg = "Hello from client!"; write(sock, msg, strlen(msg)); char buffer[1024]; read(sock, buffer, sizeof(buffer)); printf("Server: %s\n", buffer); close(sock); return 0; }
UDP 编程
UDP 是无连接协议,通信更简单但不可靠:
udp_server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> int main(void) { int sock = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(9000); bind(sock, (struct sockaddr *)&addr, sizeof(addr)); char buffer[1024]; struct sockaddr_in client_addr; socklen_t len = sizeof(client_addr); recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &len); printf("Received: %s\n", buffer); sendto(sock, "ACK", 3, 0, (struct sockaddr *)&client_addr, len); close(sock); return 0; }
并发服务器
处理多客户端时,常用 fork 或线程池实现并发:
fork_server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <signal.h> void handle_client(int client_fd) { char buffer[1024]; read(client_fd, buffer, sizeof(buffer)); printf("Client: %s\n", buffer); write(client_fd, "OK", 2); close(client_fd); } int main(void) { signal(SIGCHLD, SIG_IGN); /* 避免僵尸进程 */ int server_fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr = {0}; addr.sin_port = htons(8080); bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)); listen(server_fd, 10); while (1) { int client_fd = accept(server_fd, NULL, NULL); if (fork() == 0) { /* 子进程处理客户端 */ close(server_fd); handle_client(client_fd); exit(0); } close(client_fd); /* 父进程关闭客户端套接字 */ } return 0; }
网络编程注意事项
- 字节序转换 - 使用 htons/ntohs、htonl/ntohl 处理网络字节序
- 地址复用 - 设置 SO_REUSEADDR 避免端口绑定失败
- 错误处理 - 注意处理 EINTR 等中断错误
- 缓冲区大小 - 合理设置 socket 缓冲区避免丢包
- 资源释放 - 确保 close() 在适当时机调用
总结
Socket 编程是 C 语言网络开发的基础。TCP 提供可靠连接,UDP 更适合实时性要求高的场景。掌握基本的服务器和客户端编写,理解并发处理方式,才能完成实际的网络应用开发。