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]) 来绑定特定 IP
int 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
),因为子进程已经有自己的描述符副本。 - 父进程继续监听新的连接请求。
- 父进程在创建子进程后关闭子进程使用的套接字描述符(
子进程退出的时候,经常需要清理一下“现场”
我们这里通过下面的方式实时:
struct sigaction sa; //struct sigaction是一个结构体,用于指定如何处理信号
memset(&sa, 0, sizeof(sa));
//下面将sa结构体中的sa_handler成员设置为handler。handler是一个函数指针,指向一个用户定义的函数,该函数将在接收到特定信号时被调用
sa.sa_handler = handler;
sigaction(SIGCHLD, &sa, NULL); //将SIGCHLD信号的处理方式设置为sa结构体中指定的方式
这是进程间通信和进程管理中的一个常见做法,特别是在需要处理子进程退出状态的情况下。