首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

惯用服务器模型总结

2012-09-08 
常用服务器模型总结UNPv3上一共总结了9种服务器模型分别是:1.迭代服务器2.并发服务器,为每个客户fork一个

常用服务器模型总结

UNPv3上一共总结了9种服务器模型分别是:

1.迭代服务器  2.并发服务器,为每个客户fork一个进程  3.预先派生子进程,每个子进程都调用accept,accept无上锁保护 4.预先派生子进程,以文件锁的方式保护accept   5.预先派生子进程,以线程互斥锁上锁的方式保护accept 6.预先派生子进程,由父进程向子进程传递套接口描述字  7.并发服务器,为每个客户请求创建一个线程 8.预先创建线程,以互斥锁上锁方式保护accept  9.预先创建线程,由主线程调用accept,并把每个客户连接传递给线程池中某个  可用线程 

1.迭代服务器

典型代码:socketbindlistenfor(;;){    connfd = accept(listenfd, (SA*)&cliaddr, &clilen);    process_connection(connfd);    close(connfd);}优点:主要是编程简单缺点:处理完一个连接之后才能处理下一个连接,无并发可言,应用很少2.并发服务器,为每个客户fork一个进程典型代码:init_address(server_addr)listenfd = socket(AF_INET,SOCKET_STREAM,0);bind(listenfd, (SA*)server_addr, sizeof(serveraddr));listen(listenfd,BACKLOG);for(;;){    connfd = accept(listenfd, (SA*)&cliaddr, &clilen);    if(connfd < 0)    {        if(EINTR == errno)            continue;         else             //error    }
    if(fork() == 0)    {        close(c=listenfd);        process_connection(connfd); //child process         exit(0);    }    close(connfd);//parent, close connected socket}早起的网络服务器每天处理几百或者几千个客户连接时,这种服务器模型还是可以应付的。但是随着互联网业务的迅猛发展,繁忙的web服务器每天可能需要处理千万以上的连接,并发服务器的问题在于为每个客户现场fork一个子进程比较消耗cpu时间。
3.预先派生子进程,每个子进程都调用accept,accept无上锁保护缺点:1.服务器必须在启动的时候判断需要预先派生多少子进程         2.惊群现象(一个连接到来唤醒所有监听进程),不过较新版本的linux貌似修正了这个问题优点:无须引入父进程执行fork的开销就能处理新到的客户
4.预先派生子进程,以文件锁的方式保护accept本模型与3的区别仅仅是对accept(listenfd)使用了文件锁.这种模型是为了解决以库函数的形式实现的accept不能在多个进程中引用同一个监听套接口的问题(源自BSD的unix,在内核中实现的accept可以引用)。使用文件锁保证了每个连接到来,只有一个进程阻塞在accept调用上。对于已经在内核中实现了accept的系统来说,这种模型至少增加了加锁解锁的开销,所以相对于第3种模型性能较低(特别是在消除了惊群问题的系统上)
5.预先派生子进程,以线程互斥锁上锁的方式保护accept典型代码:static pthread_mutex_t *mptr;void my_lock_init(char *pathname){    int   fd;    pthread_mutexattr_t mattr;        //因为是相关进程所以可以使用/dev/zero设备创建共享内存    //优势是调用mmap创建共享存储区之前无需一个实际的文件    //映射/dev/zero自动创建一个指定长度的映射区    fd = open("/dev/zero", O_RDWR, 0,);    // 将mptr映射到共享存储区    mptr = mmap(0, sizeof(pthread_mutex_t), PORT_READ | PORT_WRITE, MAP_SHARED, fd, 0);    close(fd);    pthread_mutexattr_init(&mattr);    pthread_mutexattr_setpshared(&mptr, PTHREAD_PROCESS_SHARED);    pthread_mutex_init(mptr, &mattr);}
void my_lock_wait(){    pthread_mutex_lock(mptr);}
voidmy_lock_release(){    pthread_mutex_unlock(mptr);}
intmain(int argc, char **argv){   //init socket and address    my_lock_init(pathname);    for(i = 0; i < nchildren; ++i)    {        pids[i] = child_make(i, listenfd, addrlen);    }
    for(;;)        pause();}
pid_tchild_make(int i, int listenfd, int addrlen){    pid_t pid;
    if((pid = fork) > 0)        return pid;
    child_main(i, listenfd, addrlen);}
void child_main(){    for(;;)    {        my_lock_wait();        connfd = accept(listenfd, chiladdr, &clilen);        my_lock_release();        web_child(connfd);        close(connfd);    }} 这种模型在模型4上做出了进一步的改进,由于以文件锁的方式实现保护会涉及文件系统,这样可能比较耗时,所以改进的办法是以pthread mutex互斥量代替文件锁。使用线程上锁保护accept不仅适用于同一进程内各线程上锁,也适用于不同进程间上锁在多进程环境下使用线程互斥锁实现同步有两点要求:1.互斥锁必须放在由所有进程共享的内存区2.必须告知线程库这是在不同的进程间共享的互斥锁注意:目前很火的高性能web服务器的代表nginx就是采用的这种模型,网络上对nginx的研究很多。
6.预先派生子进程,由父进程向子进程传递套接口描述字优势:不需要对accept上锁保护劣势:1.编码复杂—父进程必须跟踪子进程的忙闲状态,以便给空闲子进程传递新的套接口。在前述的预先派生子进程的例子中,父进程无需关心由哪一个子进程接收一个客户连接,操作系统会根据调度算法处理这些细节。采用这种模型的结果是这些进程无法均衡的处理连接。          2.父进程通过字节流管道把描述子传递到各个子进程,并且各个子进程通过字节流管道写回单个字节,比起无论是使用共享内存区中的互斥锁还是使用文件锁实施的上锁和解锁都更费时。
7.并发服务器,为每个客户请求创建一个线程典型代码:for(;;){    connfd = accept(listenfd, cliaddr, &clilen,);    pthread_create(&tid, NULL, &doit, (void *)connfd);}
void *doit(void *arg){    pthread_detach(pthread_self());    web_child((int)arg);    close((int)arg)    return (NULL);}优点:编码简单缺点:现场为每个连接创建线程相对于预先派生线程池来说比较耗时
8.预先创建线程,以互斥锁上锁方式保护accept优势:1.编程简介,易于理解         2.线程池的方式避免了现场创建线程的开销         3.OS线程调度算法保证了线程负载的均衡性这就是leader-follower模式一个线程等待连接到来,其他线程休眠;新连接到来后leader去处理连接,释放listenfd,其他线程竞抢监听套接口listenfd(可能有惊群的问题)。leader在处理完连接以后成为follower
9.预先创建线程,由主线程调用accept,并把每个客户连接传递给线程池中某个  可用线程 劣势:相对于模型8,该模型不仅需要使用pthread mutex额外还需要使用pthread cond

热点排行