青山相待

            白云相爱

            梦不到紫罗袍共黄金带

            一茅斋

            野花开

            管甚谁家兴废谁成败

            陋巷单瓢亦乐哉

            贫,气不改!

            达,志不改!

多路复用构建高性能服务器

通过多路复用构建高性能服务器是一种常见的模型,单个I/O多路复用线程+一组工作线程,I/O线程负责协调分配任务,而实际工作交给工作线程处理。这种模型的好处在于高效并发和充分利用多线程的处理能力。

 

以memcached的构架图为例

memcached的主线程用epoll监听到EPOLLIN事件,并且触发该事件的fd是服务器listen的fd,就accept该连接请求,返回的fd主线程并不处理,而是通过CQ队列发送给工作线程去处理,工作线程又维护了一个epoll多路复用队列,子线程的epoll轮询和响应请求。 

 

  这种架构包含两大块:

1.多路复用

 #include<iostream>

 #include<stdlib.h>

#include<sys/epoll.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<fcntl.h>

using namespace std;
const int PORT = 8888;
const int MAX_CLIENT_NUM = 10000;
const int MAX_LEN = 2000;

bool setfdnoblock(int fd)
{
    int flg = fcntl(fd, F_GETFL);
    if(flg < 0)
    {
        cout << "get fd flag failed" << endl;
        return false;
    }
    if(fcntl(fd, F_SETFL, O_NONBLOCK | flg) < 0)
    {
        return false;
    }
    return true;
}

int CreateTcpServer(int port, int listennum)
{
    int fd;
    fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    sockaddr_in TcpServer;
    bzero(&TcpServer, sizeof(TcpServer));
    TcpServer.sin_family = AF_INET;
    TcpServer.sin_port = htons(8888);
    TcpServer.sin_addr.s_addr = htonl(INADDR_ANY);

    int iRet = bind(fd, (struct sockaddr*)&TcpServer, sizeof(TcpServer));
    if(-1 == iRet)
    {
    cout << "server bind error!" << endl;
    return -1;
    }
    if(listen(fd, listennum) == -1)
    {
        cout << "server listen error" << endl;
        return -1;
    }
    return fd;
}

int main()
{
    int Serverfd = CreateTcpServer(PORT, MAX_CLIENT_NUM);
    if(Serverfd == -1)
    {
        cout << "server create failed" << endl;
    }
    else
    {
       cout << "serverfd is :" << Serverfd << endl;
    }

    int Epollfd = epoll_create(MAX_CLIENT_NUM);
    if(Epollfd == -1)
    {
        cout << "epoll_create failed" << endl;
    }
    epoll_event ev, events[MAX_CLIENT_NUM];
    int nfds = 0;
    int client = 0;
    char buff[MAX_LEN];
    sockaddr_in CliAddr;
    unsigned int iCliSize = sizeof(CliAddr);
    ev.events = EPOLLIN|EPOLLOUT;
    ev.data.fd = Serverfd;
    if(!setfdnoblock(Serverfd))
    {
        cout << "set serverfd no_block failed" << endl;
    }
    if(epoll_ctl(Epollfd, EPOLL_CTL_ADD, Serverfd, &ev))
    {
        cout << "epoll add serverfd error" << endl;
    }
    while(1)
    {
        nfds = epoll_wait(Epollfd, events, MAX_CLIENT_NUM, 100000);
        if(nfds == -1)
        {
            cout << "error occur, exit" << endl;
            return -1;
        }
        else if( nfds == 0)
        {
            cout << "epoll_wait return zero" << endl;
        }
        else
        {
            for(int i = 0; i < nfds; i++)
            {
                cout << "events[i].data.fd is :" << events[i].data.fd << endl;
                if(events[i].data.fd == Serverfd)
                {
                    cout << " Serverfd received event" << endl;
                    client = accept(Serverfd, (struct sockaddr*)&CliAddr, &iCliSize);
                    if(client == -1)
                    {
                        cout << "accept error" << endl;
                        return -1;
                    }
                    ev.data.fd = client;
                    if(!setfdnoblock(client))
                    {
                        cout << "set client fd no_block error" << endl;
                    }
                    if(epoll_ctl(Epollfd, EPOLL_CTL_ADD, client, &ev))
                    {
                        cout << "epoll add client error" << endl;
                    }
                    else
                    {
                        cout << "success add client" << endl;
                    }
                }
                else if(events[i].events&EPOLLIN)
                {
                    cout << "recv client msg" << endl;
                    if(events[i].data.fd < 0)
                    {
                        cout << " event[i].data.fd is smaller than zero" << endl;
                        continue;
                    }
                    if(read(events[i].data.fd, buff, MAX_LEN) == -1)
                    {
                        perror("clifd read");
                    }
                    else
                    {
                        cout << "read client msg suc" << endl;
                        printf("%s",buff);
                    }
                    char resp[] = "recv a client msg, this is resp msg";
                    write(events[i].data.fd, resp, strlen(resp)+1);
                    //read and mod
                }
                else if(events[i].events&EPOLLOUT)
                {
                    //send and mod
                }
            }
        }
    }
}

 

 

例子中epoll listen和accept新连接,并响应新连接的请求。

2.工作线程or线程池

进程的线程数量并不是越多越好,也不是越少越好,需要根据机器逐步调优。

工作线程的工作原理, 

1.I/O线程把收到的请求放入队列,并通知工作线程处理,队列和通知机制可以是传统的加锁消息队列、信号量,也可以是memcached+libevent的实现:CQ队列装消息,线程管道通知工作线程。

2.I/O线程没有新的任务分配,工作线程阻塞或等待一段时间。 

线程池用到的比较少,不做评价。 

posted @ 2016-03-30 22:03  Leo.Z  阅读(384)  评论(0编辑  收藏  举报