prefork

linux网络服务器IO模型:prefork和惊群

鲁塔弗的博客  2014-07-02  101 阅读

有好多年没有build server from scrash,一般都用现成的lib或者直接用nginx+php。学习网络服务器开发,首推两本书

最近突然有兴趣,研究了一番
写socket server程序, 老3步:create/bind/listen,然后就用accept等待客户端连接,代码如下

import sys
import socket
import select
import os

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#方便调试:让端口释放后立即就可以被再次使用,否则要等2分钟
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

try:
    s.bind(('localhost',9000) )
    s.listen(1)
except Exception, e:
    raise e

while 1:
    client,address = s.accept()
    print "%s get a client[%s] from %s" % (os.getpid(),str(client),address)
    client.close()

这段代码一次只能处理一个连接,要提高服务器的并发处理能力,有一个模式叫做:pre-fork,代码如下

import sys
import socket
import select
import os

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(('localhost',9000) )
    s.listen(1)
except Exception, e:
    raise e

for i in range(1,10):
    pid = os.fork()
    if pid <0:
        print 'fork error'
        sys.exit(-1)
    elif pid >0:
        print 'fork process %d'  % pid
    else:
        pass

while 1:
    client,address = s.accept()
    print "%s get a client[%s] from %s" % (os.getpid(),str(client),address)
    client.close()

一次启动10个子进程,监听同一个端口,所以prefork模式就是

create/bind/listen -> fork ->accept

这段代码非常可疑,多个进程accept了同一个socket,一般人都会认为可能会出错,但是linux从操作系统层面支持了这种做法。

Linux是这样实现accept调用的:

把当前进程插入这个fd的等待队列然后阻塞
当新连接进来的时候,操作系统会唤醒这个fd的等待队列的第一个进程,只唤醒一个进程
这是Linux kernel 2.4引入的功能. 相关论文:Accept scalability on Linux

prefork模式优点很多:

  • 没有锁,os来负责调度,效率很高
  • 单一进程,资源独占,对于这个用来accept的fd,随后可以随便操作,效果很好,因为子进程copy父进程全部的句柄,且copy且write(cow:copy on write)

 多线程能使用pre-fork么?

是的,当然可以!因为linux的常见的pthread 是通过进程实现的,一个thread对应一个内核轻量级进程。 N个thread accept同一个fd
一次也会只唤醒一个thread,不用自己写各种同步代码

惊群

惊群Thundering herd problem是指上述情况下,一个新连接唤醒了所有被accept阻塞的进程。

由于目前linux确保每次只唤醒一个进程,如果你还要看惊群效果,可以如下操作

s.setblocking(0)

_r = [s]
_w = []

while 1:
    reads,writes,errs = select.select(_r,_w,[])
    for sock in reads:
        if sock == s:
            try:
                client,address = sock.accept()
                print "%s get a client[%s] from %s" % (os.getpid(),str(client),address)
                client.send("goodluck!")
                client.close()
            except Exception,e:
                print '[%d]:%s' %(os.getpid(),str(e))

把socket设置为非阻塞模式,然后丢进select来等待可读信号到达,当新connection产生的时候,所以进程都会被唤醒。

但是随后调用用accept会出现异常,因为事实上产生了一个新连接,第一个进程accept可以成功返回,其他进程accept都会失败

pre-fork是一个重大的改善,极大的简化了网络server的编程,Linux可能会走得更远,Linux Kernel 3.9会引入一个新的socket option,只要设置socket的SO_REUSEPORT属性,那么不同的进程和线程都可以同时bind这个ip和port

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值