多进程服务器
采用多进程实现并发服务器是一种常见的有效方法。
当连接建立后,服务器调用fork函数产生新的子进程,子进程处理客户请求,同时父进程关闭连接套接字,然后等待另一客户的连接。子进程首先关闭监听套接字,然后处理客户请求,最后关闭连接套接字并退出进程。
产生新的子进程后,父进程要关闭连接套接字,而子进程关闭监听套接字。
这么做基于两点考虑:
1、由于父进程只负责接收客户请求,而子进程只负责处理客户请求,关闭不需要的套接字可节省系统资源。并且,父进程和子进程共享这么套接字,如果它们都对这些套接字进行操作,会产生不可预计的后果。
2、另一个原因,也是最重要的,是为了正确地关闭连接,和文件描述符一样,每个套接字描述符都有一个“引用计数”,“引用计数”,是指同时打开一描述符的次数。
例如一个文件被同时打开两次,则“引用计数”为2,根据以上模板,当socket函数返回后,listenfd的“引用计数”变为2,而系统只有在某描述符的“引用计数”为0时,才真正关闭该描述符,对于连接套接字而言,系统此时才真正关闭连接。当父进程关闭connfd时,connfd的“引用计数”减1,当子进程关闭connfd时,connfd的“引用计数”减为0,此时系统才会关闭该连接。
当服务器调用accept函数,阻塞并等待客户连接时,其状态如下
当accept函数返回后,服务器接收了客户连接并产生新的连接套接字connfd,此时状态如下。
然后服务器调用fork函数,产生新的子进程,此时,父进程和子进程共享listenfd和connfd,其状态如下。
最后,父进程关闭连接套接字,子进程关闭监听套接字,状态如上,此时子进程可通过该连接处理客户请求,而父进程继续监听新的客户请求。
多进程并发服务器实例
采用多进程并发服务器算法来实现,通过实例理解多进程并发服务器如何处理多客户,以及其性能情况。
该实例包括服务器和客户端程序。完成的功能与重复性服务器类似,具体功能如下:
1) 服务器等候客户连接请求,一旦连接成功则显示客户的地址,接着接收该客户的名字并显示。然后接收来自该客户的信息(字符串)。每当收到一个字符串,则显示该字符串,并将字符串反转,再将反转的字符发回客户端,之后继续等待接收该客户的信息直到该客户关闭连接。服务器有同时处理多个客户的能力。
2) 客户端首先与相应服务器连接。接着接收用户输入的客户名字,再将名字发送给服务器。然后接收用户输入的字符串,再将字符串发送给服务器,接收服务器发回的信息并显示。之后,继续等待用户输入直至用户输入Ctrl+D,客户关闭连接并退出。
服务器代码:
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 1234
#define BACKLOG 2
#define MAXDATASIZE 1000
void process_cli(int connectfd, struct sockaddr_in client);
main()
{
int sockfd, connectfd;
pid_t pid;
struct sockaddr_in server;
struct sockaddr_in client;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
perror("Bind error.");
exit(1);
}
if(listen(sockfd,BACKLOG) == -1){
perror("listen() error\n");
exit(1);
}
int sin_size=sizeof(struct sockaddr_in);
while(1)
{
if ((connectfd = accept(sockfd,(struct sockaddr *)&client,&sin_size))==-1) {
perror("accept() error\n");
exit(1);
}
if ((pid=fork())>0) {
/* 父进程 */
close(connectfd);
continue;
}
else if (pid==0) {
/*子进程*/
close(sockfd);
process_cli(connectfd, client);
exit(0);
}
else {
printf("fork error\n");
exit(0);
}
}
close(sockfd); /* 关闭监听的套接字 */
}
void process_cli(int connectfd, struct sockaddr_in client)
{
int num ,i;
char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];
printf("You got a connection from %s. ",inet_ntoa(client.sin_addr) ); /* 打印客户端ip地址 */
/*获取客户端名字信息 */
num = recv(connectfd, cli_name, MAXDATASIZE,0);
if (num == 0) {
close(connectfd);
printf("Client disconnected.\n");
return;
}
cli_name[num - 1] = '\0';
printf("Client's name is %s.\n",cli_name);
while (num = recv(connectfd, recvbuf, MAXDATASIZE,0)) {
recvbuf[num] = '\0';
printf("Received client( %s ) message: %s",cli_name, recvbuf);
for (i = 0; i < num - 1; i++) {
sendbuf[i] = recvbuf[num - i -2];
}
sendbuf[num - 1] = '\0';
send(connectfd,sendbuf,strlen(sendbuf),0);
}
close(connectfd); /*关闭连接文件描述符*/
}
客户端代码:
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 1234
#define MAXDATASIZE 100
void process(FILE *fp, int sockfd);
char* getMessage(char* sendline,int len, FILE* fp);
int main(int argc, char *argv[])
{
int fd;
struct sockaddr_in server;
if (argc !=2) {
printf("Usage: %s <IP Address>\n",argv[0]);
exit(1);
}
if ((fd=socket(AF_INET, SOCK_STREAM, 0))==-1){
printf("socket() error\n");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = inet_addr("127.0.0.1");
if(connect(fd, (struct sockaddr *)&server,sizeof(struct sockaddr))==-1){
printf("connect() error\n");
exit(1);
}
process(stdin,fd);
close(fd);
}
void process(FILE *fp, int sockfd)
{
char sendline[MAXDATASIZE], recvline[MAXDATASIZE];
int numbytes;
printf("Connected to server. \n");
printf("Input name : ");
if ( fgets(sendline, MAXDATASIZE, fp) == NULL) {
printf("\nExit.\n");
return;
}
send(sockfd, sendline, strlen(sendline),0);
while (getMessage(sendline, MAXDATASIZE, fp) != NULL) {
send(sockfd, sendline, strlen(sendline),0);
if ((numbytes = recv(sockfd, recvline, MAXDATASIZE,0)) == 0) {
printf("Server terminated.\n");
return;
}
recvline[numbytes]='\0';
printf("Server Message: %s\n",recvline);
}
printf("\nExit.\n");
}
char* getMessage(char* sendline,int len, FILE* fp)
{
printf("Input string to server:");
return(fgets(sendline, MAXDATASIZE, fp));
}
执行结果:
服务器:
客户端1:
客户端2:
这里关于多进程服务端往往容易出现的问题——僵死进程
僵死进程(僵尸进程)
进程已运行结束,但进程的占用的资源未被回收,这样的进程称为僵死进程
在多进程并发服务器中需要额外注意。由于服务器长期运行并服务大量客户,而且每个客户都会产生多次连接,如果不处理僵死进程或处理不当,必将耗尽系统资源而导致系统崩溃。在多进程并发服务器实例中,我们首先启动服务器程序,然后同时运行客户1和2。
结果感觉很正确,但用命令ps -el查看过程的运行情况,会发现由于服务器未进行子进程的终止处理而导致了僵死进程的产生。
为了处理僵死进程,父进程必须调用wait或者waitid函数,对对进程并发服务器进行修改,由于当一个进程终止时,父进程将收到SIGCHLD信号,因此我们增加一个信号处理器,每当捕获到该信号时,信号处理便调用waitid函数
waitpid函数
等待制定进程的子进程返回,并修改状态。
1、原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
2、描述:
当pid等于-1,option等于0时,该函数等同于wait()函数。否则该函数的行为由参数pid和options决定。
pid制定了父进程需要知道哪些子进程的状态,其中:
= -1 要求知道任何一个子进程的返回状态
> 0 要求知道进程号为pid的子进程的状态
< -1 要求知道进程组号为pid的绝对值的子进程状态
option参数为位方式表示的标志,它是各个标志以“或”运算组成的位图,每一个标志以字节中某个位置1表示:
1)WUNTRACED 报告任何未知但已停止运行的指定进程的子进程状态,该子进程的状态自停止运行时起就没有被报告过。
2)WCONTINUED 报告任何继续运行的指定进程的子进程状态,该子进程的状态自继续运行起就没有被报告过。
3)WNOHANG 若调用本函数时,指定进程的子进程状态,目前并不是立即有效的(即可被立即读取的),调用进程不被挂起执行。
4)WNOWAIT 保持那些将其状态设置在stat_loc()函数的进程处于可等待状态。该进程将等待直到下一次被要求其返回状态值。
3、返回值:
当一个子进程返回时,返回值为该子进程号,否则返回值为-1,同stat_loc()函数返回子进程的返回值。
信号处理僵死进程的核心代码
/*主函数中需要注册信号*/
struct sigaction act,oact;
act.sa_handler=sig_handler;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
if(sigaction(SIGCHLD,&act,&oact)<0){
perror("sigaction failed");
exit(1);
}
if ((connectfd = accept(sockfd,(struct sockaddr *)&client,&sin_size))==-1) {
/*表示中断,处理,这里需要识别客户端的断开连接,方便为后续处理僵死进程*/
if(errno==EINTR) continue;
perror("accept error");
exit(1);
}
/*信号的处理函数*/
void sig_handler(int s){
pid_t pid;
int stat;
while((pid=waitpid(-1,&stat,WNOHANG))>0){
printf("child %d terminated\n,pid");
}
return;
}
完整代码:
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>
#define PORT 1234
#define BACKLOG 2
#define MAXDATASIZE 1000
void process_cli(int connectfd, sockaddr_in client);
void sig_handler(int s);
main()
{
int listenfd, connectfd;
pid_t pid;
struct sockaddr_in server;
struct sockaddr_in client;
struct sigaction act, oact;
act.sa_handler = sig_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(SIGCHLD, &act, &oact) < 0) {
perror("Sigaction failed!");
exit(1);
}
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
perror("Bind error.");
exit(1);
}
if(listen(listenfd,BACKLOG) == -1){
perror("listen() error\n");
exit(1);
}
int sin_size=sizeof(struct sockaddr_in);
while(1)
{
if ((connectfd = accept(listenfd,(struct sockaddr *)
&client,&sin_size))==-1) {
if (errno == EINTR) continue;
perror("accept() error\n");
exit(1);
}
if ((pid=fork())>0) {
close(connectfd);
continue;
}
else if (pid==0) {
close(listenfd);
process_cli(connectfd, client);
exit(0);
}
else {
printf("fork error\n");
exit(0);
}
}
close(listenfd);
}
void process_cli(int connectfd, sockaddr_in client)
{
int num;
char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name
[MAXDATASIZE];
printf("You got a connection from %s. ",inet_ntoa(client.sin_addr)
);
num = recv(connectfd, cli_name, MAXDATASIZE,0);
if (num == 0) {
close(connectfd);
printf("Client disconnected.\n");
return;
}
cli_name[num - 1] = '\0';
printf("Client's name is %s.\n",cli_name);
while (num = recv(connectfd, recvbuf, MAXDATASIZE,0)) {
recvbuf[num] = '\0';
printf("Received client( %s ) message: %s",cli_name, recvbuf);
for (int i = 0; i < num - 1; i++) {
sendbuf[i] = recvbuf[num - i -2];
}
sendbuf[num - 1] = '\0';
send(connectfd,sendbuf,strlen(sendbuf),0);
}
close(connectfd);
}
void sig_handler(int s)
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0){
printf("child %d terminated\n", pid);
}
return;
}