有好多年没有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模式优点很多:
是的,当然可以!因为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