UNIX网络编程——经常使用服务器模型总结

下面有9种服务器模型分别是:linux

  • 迭代服务器。
  • 并发服务器,为每一个客户fork一个进程。
  • 预先派生子进程,每一个子进程都调用accept,accept无上锁保护。
  • 预先派生子进程,以文件锁的方式保护accept。 
  • 预先派生子进程,以线程互斥锁上锁的方式保护accept。
  • 预先派生子进程,由父进程向子进程传递套接口描述字。 
  • 并发服务器,为每一个客户请求建立一个线程。
  • 预先建立线程,以互斥锁上锁方式保护accept。 
  • 预先建立线程,由主线程调用accept,并把每一个客户链接传递给线程池中某个可用线程。

一、迭代服务器nginx

       典型代码:
socket
bind
listen
for(;;)
{
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无上锁保护
     缺点:
  • 服务器必须在启动的时候判断须要预先派生多少子进程
  • 惊群现象(一个链接到来唤醒全部监听进程),不过较新版本的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);
}

void
my_lock_release()
{
pthread_mutex_unlock(mptr);
}

int
main(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_t
child_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上锁保护
     劣势:
  • 编码复杂—父进程必须跟踪子进程的忙闲状态,以便给空闲子进程传递新的套接口。在前述的预先派生子进程的例子中,父进程无需关心由哪个子进程接收一个客户链接,操做系统会根据调度算法处理这些细节。采用这种模型的结果是这些进程没法均衡的处理链接。
  • 父进程经过字节流管道把描述子传递到各个子进程,而且各个子进程经过字节流管道写回单个字节,比起不管是使用共享内存区中的互斥锁仍是使用文件锁实施的上锁和解锁都更费时。

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
    优点:
  • 编程简介,易于理解
  • 线程池的方式避免了现场建立线程的开销
  • OS线程调度算法保证了线程负载的均衡性
       这就是leader-follower模式一个线程等待链接到来,其余线程休眠;新链接到来后leader去处理链接,释 放listenfd,其余线程竞抢监听套接口listenfd(可能有惊群的问题)。leader在处理完链接之后成为follower。

9.预先建立线程,由主线程调用accept,并把每一个客户链接传递给线程池中某个可用线程
       劣势:相对于模型8,该模型不只须要使用pthread mutex额外还须要使用pthread cond。
相关文章
相关标签/搜索