前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >服务器线程并发和进程并发

服务器线程并发和进程并发

作者头像
Aichen
发布2018-05-18 12:01:49
2.9K0
发布2018-05-18 12:01:49
举报
文章被收录于专栏:白驹过隙白驹过隙

进程和线程的使用在前面博文已经讲述完毕,在完成一个最简单的服务器之后,就是要考虑下如何实现并发服务器了。 要实现服务的并发,只能通过进程和线程两种方式。 之前提到过listen_fd和connect_fd,listen用于监听是否有客户端连接,维护两个fd队列,没完成握手的和完成就绪的。 connect从就绪队列取描述符,这个connect_fd描述符将用于数据通信,所以要实现并发,就是将connect_fd分发到线程或进程上,由他们去独立完成通信。 在实际并发服务器应用场合,在IO层大多通过两个地方来提高代码效率,一个是描述符处理,一个是线程/进程调度处理。

下图简单描述了并发服务器的原理:

image
image

在处理IO时,会用到IO复用技术提高效率,在线程/进程分配时,会先构造线程池或进程池,并以某种方式调度,这些在后续博文详细描述。

下面是并发实现的简单代码,利用线程和进程实现服务器的并发。 进程实现:

代码语言:javascript
复制
/* File Name: server.c */  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/unistd.h>
#include <netinet/in.h>  

const int DEFAULT_PORT = 2500;  
const int BUFFSIZE = 1024;
const int MAXLINK = 10;

class sigOp
{
public:
     void addSigProcess(int sig,void (*func)(int));
    void sendSig(const int sig, const int pid);
};

void sigOp::addSigProcess(int sig,void (*func)(int))
{
    struct sigaction stuSig;
    memset(&stuSig, '\0', sizeof(stuSig));
    stuSig.sa_handler = func;
    stuSig.sa_flags |= SA_RESTART;
    sigfillset(&stuSig.sa_mask);
    sigaction(sig, &stuSig, NULL);
}

void sigOp::sendSig(const int sig, const int pid)
{
    kill(pid, sig);
}

void waitchlid(int sig)
{
    pid_t pid;
    int stat;
    while((pid = waitpid(-1, &stat, WNOHANG)) > 0);
}

int main(int argc, char** argv)  
{  
    int    socket_fd, connect_fd;  
    struct sockaddr_in servaddr;  
    char buff[BUFFSIZE];    
    
    if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {  
        printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);  
        exit(0);  
    } 
    
    memset(&servaddr, 0, sizeof(servaddr));  
    servaddr.sin_family = AF_INET;  
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(DEFAULT_PORT);
  
    if (bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
    {  
        printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);  
        exit(0);  
    }  
    
    if (listen(socket_fd, MAXLINK) == -1)
    {  
        exit(0);  
    }   

    sigOp sig;
    sig.addSigProcess(SIGCHLD, waitchlid);//回收进程

    while(1)
    {   
        if ((connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1)
        {  
            break;  
        } 
        else
        {
            if (0 == fork())
            {
                close(socket_fd); //关闭监听描述符
                while(1)
                {
                    memset(&buff,'\0', sizeof(buff));  
                    recv(connect_fd, buff, BUFFSIZE - 1, 0);  
                    send(connect_fd, buff, BUFFSIZE - 1, 0); 
                    printf("recv msg from client: %s\n", buff);
                }
                close(connect_fd);
                exit(0);
            }          
            close(connect_fd);//父进程关闭连接描述符        
        }
    }  
    close(connect_fd);    
    close(socket_fd);  
}

之前提到过listen描述符和connect描述符是完全独立的,关闭都不会影响另一个描述符工作。所以在代码中,父子进程都会关闭不需要的描述符。 测试结果如下:

ps -aux查看系统进程,可以看到三个进程,一个是主进程用于listen监听,两个子进程进行通信。

下面是线程并发实现:

代码语言:javascript
复制
/* File Name: server.c */  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <errno.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/unistd.h>
#include <netinet/in.h>  

const int DEFAULT_PORT = 2500;  
const int BUFFSIZE = 1024;
const int MAXLINK = 10;

void* run(void* pConnect_fd)
{
    char buff[BUFFSIZE]; 
    int *connect_fd = reinterpret_cast<int *>(pConnect_fd);
    pthread_detach(pthread_self());
    while(1)
    {
        memset(&buff,'\0', sizeof(buff));  
        recv(*connect_fd, buff, BUFFSIZE - 1, 0);  
        send(*connect_fd, buff, BUFFSIZE - 1, 0); 
        printf("recv msg from client: %s\n", buff);
    }    
}

int main(int argc, char** argv)  
{  
    int    socket_fd, connect_fd;  
    struct sockaddr_in servaddr;  
    
    if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {  
        printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);  
        exit(0);  
    } 
    
    memset(&servaddr, 0, sizeof(servaddr));  
    servaddr.sin_family = AF_INET;  
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(DEFAULT_PORT);
  
    if (bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
    {  
        printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);  
        exit(0);  
    }  
    
    if (listen(socket_fd, MAXLINK) == -1)
    {  
        exit(0);  
    }   

    while(1)
    {   
        if ((connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1)
        {  
            break;  
        } 
        else    //accept获取到描述符创建线程
        {
            pthread_t _Tread;
            pthread_create(&_Tread, NULL, run, &connect_fd);//传参为连接描述符
        }
    }  
    close(connect_fd);    
    close(socket_fd);  
}

测试结果如下:

效果和进程一样,执行netstat查看tcp状态

两组连接相互通信。

线程并发和进程并发各有优劣,目前大多服务器还是用线程进行并发的,进程要对父进程进行拷贝,资源消耗大,但相互直接资源互不影响,线程效率高但是要注意锁的使用,一个线程可能会影响整个服务器的运行。 详细优缺点详细可参考:http://blog.chinaunix.net/uid-20556054-id-3067672.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-05-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档