有好多年没有build server from scrash,一般都用现成的lib或者直接用nginx+php。学习网络服务器开发,首推两本书
- APUE:UNIX环境高级编程
- Windows网络编程
最近突然有兴趣,研究了一番
写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,不用自己写各种同步代码
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
1
转载请注明:爱开源 » linux网络服务器IO模型:prefork和惊群