写一个服务器端用多进程处理并发,使两个以上的客户端可以同时连接服务器端得到响应。每当接受一个新的连接就fork产生一个子进程,让子进程去处理这个连接,父进程只用来接受连接。
与多线程相比的不同点:多线程如果其中一个线程操作不当,产生了一个信号,会导致整个进程都终止。对于多进程来讲,是产生的子进程去处理客户端的连接,如果子进程终止了,不会影响到其他进程。创建线程的开销比产生新的子进程的开销要小,多线程可以共享整个进程的资源。
服务器端代码ser.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<signal.h>
int socket_init();//封装一个套接字,套接字的声明
void do_run(int c)//子进程中处理客户端的方法的实现
{
while(1)
{
char buff[128]={0};
int num=recv(c,buff,127,0);//相当于read
if(num<=0)
{
break;
}
printf("子进程接收到的信息:%s\n",buff);
send(c,"ok",2,0);//相当于write
}
}
int main()
{
int sockfd=socket_init();//调用封装好的套接字方法,创建套接字
if(sockfd==-1)
{
printf("创建失败\n");
exit(1);
}
while(1)
{
struct sockaddr_in caddr;//定义客户端的套接字地址
int len = sizeof(caddr);
//4.接收客户端连接
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)//失败
{
continue;
}
printf("c=%d\n",c);
//5.产生一个子进程来处理客户端的连接,子进程会复制父进程打开的所有文件,上面接收的客户端的连接c也算是文件描述符,这个c也会被复制到子进程中
pid_t pid=fork();
if(pid==-1)
{
printf("子进程产生失败\n");
close(c);//关闭c这个连接
continue;//重新等待新的连接
}
else if(pid==0)//子进程产生成功
{
close(sockfd);//因为子进程不用sockfd
do_run(c);//处理客户端,传入的参数是c这个描述符
close(c);//子进程在退出之前关闭c
printf("子进程退出,pid=%d\n",getpid());
exit(0);//处理完所连接的客户端,子进程必须要退出
}
else
{
close(c);//父进程关闭c连接,对子进程没有影响,因为在fork之后,描述符的引用计数加1,这时候父进程关闭,子进程也关闭,最终才能认为c被关闭
//如果父进程不关闭c连接,c的值会一直增长
}
}
exit(0);
}
int socket_init()//套接字的具体实现
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//1.创建套接字
if(sockfd==-1)
{
printf("创建失败\n");
return -1;
}
struct sockaddr_in saddr;//定义服务器端的套接字地址
memset(&saddr,0,sizeof(saddr));//清空
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
//2.绑定,指定套接字的ip和端口
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
printf("绑定失败\n");//ip写错或者端口被占用
return -1;
}
//3.创建监听队列
res=listen(sockfd,5);
if(res==-1)
{
return -1;
}
return sockfd;
}
客户端代码cli.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
//1.创建套接字
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
exit(1);
}
//要连接服务器端就要知道服务器端的ip和端口,把ip和端口存到下面定义的saddr中
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));//必须清空
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
//2.连接服务器端
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//会随机填充客户端的ip和一个临时端口
if(res==-1)//网络没联通,服务器端没启动
{
printf("连接服务器端失败\n");
exit(1);
}
while(1)
{
char buff[128]={0};
printf("输入:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
//3.向服务器端发送消息
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
//4.接收服务器端回复的消息
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
}
//5.关闭客户端
printf("客户端关闭\n");
close(sockfd);
}
运行结果:
此时,虽然两个客户端可以同时与服务器端通信,但是存在一个问题,就是僵死进程的问题,如下图:
之所以出现僵死进程是因为在子进程结束之后并没有处理子进程,在Linux系统中处理僵死进程的方法有两种,wait和忽略信号。
使用忽略信号的方式,服务器端的代码ser.c如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<signal.h>
int socket_init();//封装一个套接字,套接字的声明
void do_run(int c)//子进程中处理客户端的方法的实现
{
while(1)
{
char buff[128]={0};
int num=recv(c,buff,127,0);//相当于read
if(num<=0)
{
break;
}
printf("子进程接收到的信息:%s\n",buff);
send(c,"ok",2,0);//相当于write
}
}
int main()
{
signal(SIGCHLD,SIG_IGN);
int sockfd=socket_init();//调用封装好的套接字方法,创建套接字
if(sockfd==-1)
{
printf("创建失败\n");
exit(1);
}
while(1)
{
struct sockaddr_in caddr;//定义客户端的套接字地址
int len = sizeof(caddr);
//4.接收客户端连接
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)//失败
{
continue;
}
printf("c=%d\n",c);
//5.产生一个子进程来处理客户端的连接,子进程会复制父进程打开的所有文件,上面接收的客户端的连接c也算是文件描述符,这个c也会被复制到子进程中
pid_t pid=fork();
if(pid==-1)
{
printf("子进程产生失败\n");
close(c);//关闭c这个连接
continue;//重新等待新的连接
}
else if(pid==0)//子进程产生成功
{
close(sockfd);//因为子进程不用sockfd
do_run(c);//处理客户端,传入的参数是c这个描述符
close(c);//子进程在退出之前关闭c
printf("子进程退出,pid=%d\n",getpid());
exit(0);//处理完所连接的客户端,子进程必须要退出
}
else
{
close(c);//父进程关闭c连接,对子进程没有影响,因为在fork之后,描述符的引用计数加1,这时候父进程关闭,子进程也关闭,最终才能认为c被关闭
//如果父进程不关闭c连接,c的值会一直增长
}
}
exit(0);
}
int socket_init()//套接字的具体实现
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//1.创建套接字
if(sockfd==-1)
{
printf("创建失败\n");
return -1;
}
struct sockaddr_in saddr;//定义服务器端的套接字地址
memset(&saddr,0,sizeof(saddr));//清空
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
//2.绑定,指定套接字的ip和端口
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
printf("绑定失败\n");//ip写错或者端口被占用
return -1;
}
//3.创建监听队列
res=listen(sockfd,5);
if(res==-1)
{
return -1;
}
return sockfd;
}
或者使用wait方法,服务器端的代码ser.c如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<signal.h>
int socket_init();//封装一个套接字,套接字的声明
void do_run(int c)//子进程中处理客户端的方法的实现
{
while(1)
{
char buff[128]={0};
int num=recv(c,buff,127,0);//相当于read
if(num<=0)
{
break;
}
printf("子进程接收到的信息:%s\n",buff);
send(c,"ok",2,0);//相当于write
}
}
void fun(int sig)
{
wait(NULL);
}
int main()
{
signal(SIGCHLD,fun);
int sockfd=socket_init();//调用封装好的套接字方法,创建套接字
if(sockfd==-1)
{
printf("创建失败\n");
exit(1);
}
while(1)
{
struct sockaddr_in caddr;//定义客户端的套接字地址
int len = sizeof(caddr);
//4.接收客户端连接
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)//失败
{
continue;
}
printf("c=%d\n",c);
//5.产生一个子进程来处理客户端的连接,子进程会复制父进程打开的所有文件,上面接收的客户端的连接c也算是文件描述符,这个c也会被复制到子进程中
pid_t pid=fork();
if(pid==-1)
{
printf("子进程产生失败\n");
close(c);//关闭c这个连接
continue;//重新等待新的连接
}
else if(pid==0)//子进程产生成功
{
close(sockfd);//因为子进程不用sockfd
do_run(c);//处理客户端,传入的参数是c这个描述符
close(c);//子进程在退出之前关闭c
printf("子进程退出,pid=%d\n",getpid());
exit(0);//处理完所连接的客户端,子进程必须要退出
}
else
{
close(c);//父进程关闭c连接,对子进程没有影响,因为在fork之后,描述符的引用计数加1,这时候父进程关闭,子进程也关闭,最终才能认为c被关闭
//如果父进程不关闭c连接,c的值会一直增长
}
}
exit(0);
}
int socket_init()//套接字的具体实现
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//1.创建套接字
if(sockfd==-1)
{
printf("创建失败\n");
return -1;
}
struct sockaddr_in saddr;//定义服务器端的套接字地址
memset(&saddr,0,sizeof(saddr));//清空
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
//2.绑定,指定套接字的ip和端口
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
printf("绑定失败\n");//ip写错或者端口被占用
return -1;
}
//3.创建监听队列
res=listen(sockfd,5);
if(res==-1)
{
return -1;
}
return sockfd;
}
运行结果:
这次僵死进程就不存在了。