socket通讯服务器模型.有多种类型,多进程只是其中比较少用的一种,原因是它太“重”。
在生产环境中,使用多进程模型可能不是最高效的选择,特别是在处理大量并发连接时。多线程模型或IO多路复用模型可能是更好的选择。
父子进程:

多进程并发服务器原型
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main() {pid_t pid;// 创建子进程pid = fork();if (pid < 0) { // 创建子进程失败fprintf(stderr, "Fork failed.\n");return 1;} else if (pid == 0) { // 子进程printf("Hello from child process!\n");} else { // 父进程printf("Hello from parent process!\n");}return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // 正确的头文件
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h> // 为了使用 strerror#define N 64// 回收进程的资源
void handler(int signum)
{wait(NULL);
}int main(int argc, char const *argv[])
{int sockfd;if (argc < 3){printf("usage: %s <ip> <port>\n", argv[0]);return -1;}sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(atoi(argv[2]));// 自动绑定所有的本机网卡的地址addr.sin_addr.s_addr = INADDR_ANY; // 或者使用 inet_addr(argv[1]) 来绑定特定 IPint addrlen = sizeof(addr);if (bind(sockfd, (struct sockaddr *)&addr, addrlen) < 0){perror("bind err");close(sockfd);return -1;}if (listen(sockfd, 5) < 0){perror("listen err");close(sockfd);return -1;}printf("wait client connect\n");int clifd;struct sockaddr_in cliaddr;struct sigaction sa;memset(&sa, 0, sizeof(sa));sa.sa_handler = handler;sigaction(SIGCHLD, &sa, NULL);while (1){clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &addrlen);if (clifd < 0){perror("accept err");continue;}pid_t pid = fork();if (pid < 0){perror("fork err");close(clifd);continue;}else if (pid == 0) // 子进程{int ret;char buf[N] = {0};while (1){ret = recv(clifd, buf, N, 0);if (ret < 0){perror("recv err");continue;}else if (ret == 0){printf("peer exit\n");break;}else{printf("ip = %s, port = %d, data = %s\n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buf);}}close(clifd);exit(0);}else // 父进程{close(clifd); // 关闭子进程已使用的套接字描述符}}close(sockfd); // 这行代码实际上不会被执行,因为上面的循环是无限循环return 0;
}
这段代码实现了一个简单的多进程服务器,它能够监听来自客户端的连接请求,并在接受到连接后,通过创建一个新的子进程来处理每个客户端的通信。
包含的头文件
stdio.h:用于基本的输入输出函数,如printf。sys/types.h、sys/socket.h、netinet/in.h、arpa/inet.h:这些头文件提供了网络编程所需的类型和函数,包括套接字创建、地址转换等。unistd.h:提供对POSIX操作系统API的访问,如close、fork、exit等。stdlib.h:包含了一些常用的库函数,如atoi(字符串转整数)。sys/stat.h、fcntl.h:虽然在这段代码中没有直接使用,但通常用于文件操作。signal.h:提供了信号处理的功能。sys/wait.h:提供了等待进程结束的功能。string.h:提供了字符串处理的函数,如memset和strerror(尽管strerror在这段代码中未使用)。
宏定义
#define N 64:定义了缓冲区的大小为64字节。
函数
handler(int signum):这是一个信号处理函数,用于处理子进程结束的信号(SIGCHLD)。它调用wait(NULL)来回收结束的子进程的资源。
主函数 main
-
参数检查:程序需要两个参数:IP地址和端口号。如果参数不足,程序将打印用法信息并退出。
-
创建套接字:使用
socket函数创建一个TCP套接字。 -
配置地址结构:将服务器的IP地址(错误地设置为
INADDR_ANY,这通常用于绑定到所有可用接口,应该使用inet_addr(argv[1])来指定特定IP)和端口号配置到sockaddr_in结构中。 -
绑定套接字:使用
bind函数将套接字与指定的IP地址和端口号绑定。 -
监听连接:使用
listen函数使套接字进入监听状态,准备接受连接请求。 -
设置信号处理:使用
sigaction函数设置SIGCHLD信号的处理函数为handler。 -
接受连接:在一个无限循环中,使用
accept函数接受来自客户端的连接请求。每当接受到一个连接时,都会创建一个新的子进程来处理这个连接。 -
子进程处理:
- 在子进程中,使用
recv函数从客户端接收数据。 - 打印接收到的数据以及客户端的IP地址和端口号。
- 当客户端关闭连接(
recv返回0)时,子进程也关闭套接字并退出。
- 在子进程中,使用
-
父进程处理:
- 父进程在创建子进程后关闭子进程使用的套接字描述符(
clifd),因为子进程已经有自己的描述符副本。 - 父进程继续监听新的连接请求。
- 父进程在创建子进程后关闭子进程使用的套接字描述符(
