目录
1. 多进程并发服务器思路分析
使用多进程并发服务器时要考虑以下几点:
- 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
- 系统内创建进程个数(与内存大小相关)
- 进程创建过多是否降低整体服务性能(进程调度)
0.注册信号捕捉函数: SIGCHLD
1. Socket(); 创建 监听套接字 lfd
2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
3. Listen();
4. while (1) {
cfd = Accpet(); 接收客户端连接请求。
pid = fork();
if (pid == 0){ 子进程
close(lfd) 关闭用于建立连接的套接字sfd
break;
} else if (pid > 0) { 父进程:( 子进程用信号来回收)
close(cfd); 关闭用于与客户端通信的套接字 cfd
contiue;
}
}
5. 子进程
read()
执行需求
write()
6.在回调函数中, 完成子进程回收
while (waitpid());
2. 第一个版本的代码如下:
myServer.c
#include "wrap.h"
#define SER_PORT 8888 //本地端口
#define SER_ADDR_IP "127.0.0.1" //本地IP
int main()
{
int s_fd = 0 , c_fd = 0 , ret = 0; //文件描述符
struct sockaddr_in ser_sockaddr = {0}; //服务器 sockaddr数据结构
struct sockaddr_in cli_sockaddr = {0}; //客户端 sockaddr数据结构
socklen_t cli_addr_len = sizeof(cli_sockaddr); //客户端地址结构大小
char cli_addr_ip[1024] = {0}; //客户端IP地址
char buf[1024] = {0};
pid_t pid;
//第1步: 创建socket , 得到监听fd
s_fd = Socket(AF_INET , SOCK_STREAM , 0);
//第2步: bind绑定s_fd和当前电脑的ip地址和端口号
ser_sockaddr.sin_family = AF_INET; // 设置地址族为IPv4
ser_sockaddr.sin_port = htons(SER_PORT); // 设置地址的端口号信息:本地--》网络 (IP)
ser_sockaddr.sin_addr.s_addr = inet_addr(SER_ADDR_IP); // 设置IP地址
ret = Bind(s_fd , (const struct sockaddr *)&ser_sockaddr, sizeof(ser_sockaddr));
//第三步:listen监听端口
ret = Listen(s_fd , 100);
while(1)
{
// 第四步:accept阻塞等待客户端接入
c_fd = accept(s_fd , (struct sockaddr *)&cli_sockaddr , &cli_addr_len);
//网络字节序 ---> 本地字节序(string IP)
printf("client ip:%s port:%d\n",
inet_ntop(AF_INET, &cli_sockaddr.sin_addr.s_addr, cli_addr_ip, sizeof(cli_addr_ip)),
ntohs(cli_sockaddr.sin_port)); // 根据accept传出参数,获取客户端 ip 和 port
//创建子进程
pid = fork();
if(pid < 0)
{
exit(1);
}
else if(pid == 0) //子进程
{
close(s_fd);
break;
}
else if(pid > 0) //父进程
{
close(c_fd);
continue;
}
}
//子进程运行代码
if(pid == 0)
{
//小写 --> 大写 or 大写 --> 小写
while(1)
{
memset(buf , 0 , sizeof(buf));
ret = read(c_fd , buf , sizeof(buf)); //阻塞读取指定字节数
if(ret == 0)
{
Close(c_fd);
exit(1); //客户端断开连接
}
write(STDOUT_FILENO , buf , ret);
for(int i = 0 ; i < ret ; i++)
{
if(buf[i] >= 'a' && buf[i] <= 'z')
{
buf[i] -= 32 ;
}
else if(buf[i] >= 'A' && buf[i] <= 'Z')
{
buf[i] += 32 ;
}
}
printf("\n");
write(c_fd , buf , ret);
}
}
return 0;
}
编译运行,结果如下:
这个代码,有问题。我们Ctrl+C终止一个连接进程,会发现,有僵尸进程。
如上图所示,有个僵尸进程。这是因为父进程在阻塞等待,没来得及去回收这个子进程。
所以需要修改代码,增加子进程回收,用信号捕捉来实现。
3. 完成代码:
#include "wrap.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#define SER_PORT 8888 //本地端口
#define SER_ADDR_IP "127.0.0.1" //本地IP
void catch_child(int signum)
{
while ((waitpid(0, NULL, WNOHANG)) > 0); //就要这样写
}
int main()
{
int s_fd = 0 , c_fd = 0 , ret = 0; //文件描述符
struct sockaddr_in ser_sockaddr = {0}; //服务器 sockaddr数据结构
struct sockaddr_in cli_sockaddr = {0}; //客户端 sockaddr数据结构
socklen_t cli_addr_len = sizeof(cli_sockaddr); //客户端地址结构大小
char cli_addr_ip[1024] = {0}; //客户端IP地址
char buf[1024] = {0};
pid_t pid;
//创建信号捕抓函数回收子进程
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD , &act , NULL);
if(ret != 0)
{
perror("sigaction error");
}
//第1步: 创建socket , 得到监听fd
s_fd = Socket(AF_INET , SOCK_STREAM , 0);
//第2步: bind绑定s_fd和当前电脑的ip地址和端口号
ser_sockaddr.sin_family = AF_INET; // 设置地址族为IPv4
ser_sockaddr.sin_port = htons(SER_PORT); // 设置地址的端口号信息:本地--》网络 (IP)
ser_sockaddr.sin_addr.s_addr = inet_addr(SER_ADDR_IP); // 设置IP地址
ret = Bind(s_fd , (const struct sockaddr *)&ser_sockaddr, sizeof(ser_sockaddr));
//第三步:listen监听端口
ret = Listen(s_fd , 100);
while(1)
{
// 第四步:accept阻塞等待客户端接入
c_fd = Accept(s_fd , (struct sockaddr *)&cli_sockaddr , &cli_addr_len);
//网络字节序 ---> 本地字节序(string IP)
printf("client ip:%s port:%d\n",
inet_ntop(AF_INET, &cli_sockaddr.sin_addr.s_addr, cli_addr_ip, sizeof(cli_addr_ip)),
ntohs(cli_sockaddr.sin_port)); // 根据accept传出参数,获取客户端 ip 和 port
//创建子进程
pid = fork();
if(pid < 0)
{
perror("fork error");
exit(1);
}
else if(pid == 0) //子进程
{
close(s_fd);
break;
}
else if(pid > 0) //父进程
{
close(c_fd);
continue;
}
}
//子进程运行代码
if(pid == 0)
{
//小写 --> 大写 or 大写 --> 小写
while(1)
{
memset(buf , 0 , sizeof(buf));
ret = read(c_fd , buf , sizeof(buf)); //阻塞读取指定字节数
if(ret == 0) //客户端断开连接
{
Close(c_fd);
exit(1);
}
write(STDOUT_FILENO , buf , ret);
for(int i = 0 ; i < ret ; i++)
{
if(buf[i] >= 'a' && buf[i] <= 'z')
{
buf[i] -= 32 ;
}
else if(buf[i] >= 'A' && buf[i] <= 'Z')
{
buf[i] += 32 ;
}
}
printf("\n");
write(c_fd , buf , ret);
}
}
return 0;
}