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
),因为子进程已经有自己的描述符副本。 - 父进程继续监听新的连接请求。
- 父进程在创建子进程后关闭子进程使用的套接字描述符(