Linux socket通信——并发服务器(fork)

来源:互联网 发布:淘宝上的折扣店 编辑:程序博客网 时间:2024/05/26 09:56

一、迭代服务器和并发服务器

迭代服务器会依次处理客户端的连接 ,只要当前连接的任务没有完成,服务器的进程就会一直被占用,直到任务完成后,服务器关闭这个socket,释放连接。

它的原型可以描述成:

while(1)
{
new_fd = 服务器accept客户端的连接(new_fd = accept(listenfd, XX, XX))
逻辑处理
在这个new_fd上给客户端发送消息
关闭new_fd
}

并发服务器则是每有一个客户端的连接请求,则克隆一个自己,去处理请求,自身一直处于监听状态而不会被阻塞。并发服务器的一个简单操作是通过fork函数,建立一个子进程分别服务多个客户端。当一个连接建立时,用于阻塞进程的aceept返回,服务器接着调用fork来创建一个子进程,该子进程通过已连接套接口connfd服务客户,同时需要关闭监听套接口listenfd;而父进程就可以通过监听套接口listenfd来等待另外一个连接,同时该关闭已连接套接口。
1while(1)
2 {
3 connfd服务器accept客户端的连接
4 if(是子进程)
5 {
6 首先关闭掉监听listenfd// 因为子进程并不需要监听,它只负责处理逻辑并发消息给客户端
7 处理逻辑发送消息
8 关闭connfd
9 关闭进程
10 }
11else if(是父进程)
12关闭connfd
13 }
值得注意的是,在上述过程中,有几个关闭fd的操作,这对初学者来说很难理解。
fork一个子线程后,貌似是复制了两个socket描述符,其实父子线程是共享connfd和listenfd的。只不过connfd和listenfd的引用计数增加了而已。第6行关闭了listenfd,只是将listenfd的引用计数减1,第12行关闭connfd,同样只是将connfd引用计数减1,并没有断开和客户端的连接。
如果没有第12行,会发生什么??第一,因为可分配的socket描述符是有限的,如果分配了以后不释放,也就是不能回收再利用,也就是总有描述符耗尽的一天。第二,本来把和客户端连接的任务交给子进程以后父进程就可以继续监听并accept下个连接了,但如果父进程不关闭自己跟客户的连接,意思就是这个连接居然永远存在!


   如下是具体连接过程:

(1)服务器阻塞于accept调用且来自客户的连接请求到达时的客户端与服务器的状态。

(2)从accept返回后,连接已经在内核中注册,并且新的套接口connfd被创建。这是一个已建起连接的套接口,可以进行数据的读写。

(3)并发服务器在调用fork之后,listenfd和connfd这两个描述字在父进程以及子进程之间共享(实际为其中一份为copy),各自的引用计数为变成2.

(4)接下来是由父进程关闭已连接套接口(connfd),由子进程关闭监听套接口(listenfd)。进行这个操作之后,那么就可以让子进程来处理与客户的连接,而父进程可以在监听套接口上再次调用accept来处理下一个客户的连接。


二、并发服务器编程实现

<span style="font-size:18px;">#include <sys/socket.h>#include <sys/types.h>#include <sys/wait.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <iostream>#include <signal.h>#include <memory.h>#include <errno.h>#include <stdlib.h>const int BUF_SIZE = 1024;using namespace std;void sig_chld(int sig){    pid_t pid;    int stat;    while((pid = waitpid(-1,&stat,WNOHANG))>0)        cout << "child " << pid << " termination" << endl;    return;}int main(){    int serv_sockfd;    int client_sockfd;    char buf[BUF_SIZE];    memset(buf,0,sizeof(buf));    sockaddr_in serv_addr;    sockaddr_in client_addr;    memset(&serv_addr,0,sizeof(serv_addr));    serv_addr.sin_family = AF_INET;    serv_addr.sin_addr.s_addr = INADDR_ANY;    serv_addr.sin_port = htons(8000);    if((serv_sockfd = socket(PF_INET,SOCK_STREAM,0))<0)    {        cout << "socket error" << endl;        return -1;    }    if((bind(serv_sockfd,(sockaddr *)&serv_addr,sizeof(serv_addr)))<0)    {        cout << "bind error" << endl;        return -2;    }    if((listen(serv_sockfd,5))<0)    {        cout << "listen error" << endl;        return -3;    }    cout << "listening..." << endl;    signal(SIGCHLD,sig_chld);    socklen_t sin_size;    int read_len=0;    int child_pid;    while(1)    {        sin_size = sizeof(client_addr);        memset(&client_addr,0,sizeof(client_addr));        if((client_sockfd = accept(serv_sockfd,(sockaddr*)&client_addr,&sin_size))<0)        {            if(errno == EINTR || errno == ECONNABORTED)                continue;            else            {                cout << "accept error" << endl;                return -4;            }        }        if((child_pid = fork())==0)        {            cout << "client " <<inet_ntoa(client_addr.sin_addr) << " 进程号:"<< getpid() << endl;            write(client_sockfd,"Welcome to my server",21);            close(serv_sockfd);            while(1)            {                memset(buf,0,sizeof(buf));                if((read_len = read(client_sockfd,buf,BUF_SIZE))<=0)                {                    close(client_sockfd);                    cout << "read error" << endl;                    exit(-5);                }                cout << "Client"<< getpid()  << " say:"<<buf << endl;                write(client_sockfd,buf,read_len);            }            close(client_sockfd);            exit(-6);        }        else if(child_pid>0)            close(client_sockfd);    }    return 0;}</span>
上面代码中,使用到了signal,是用来在 子线程结束时,做出提示。关于信号的相关知识,可以参考我的另一篇博客《Linux进程间通信——信号》

0 0
原创粉丝点击