python 网络编程学习 非阻塞soc
主要学习服务器的异步使用
SocketServer简化了网络服务器的编写。它有4个类:tcpServer,UDPServer,UnixStreamServer,UnixDatagramServer。这4个类是同步进行处理的,另外通过ForkingMixIn和ThreadingMixIn类来支持异步。
创建服务器的步骤
- 创建一个请求处理类,它是BaseRequestHandler的子类并重载其handle()方法。
- 实例化一个服务器类,传入服务器的地址和请求处理程序类。
- 调用handle_request()(一般是调用其他事件循环或者使用select())或serve_forever()。
集成ThreadingMixIn类时需要处理异常关闭。daemon_threads指示服务器是否要等待线程终止,要是线程互相独立,必须要设置为True,默认是False。
使用基于多进程,ForkingMinIn 支持异步
import os
import socket
import threading
import SocketServer
# 构造客户端
class ForkinGClient():
def __init__(self, ip, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.connect((ip, port))
def run(self):
crtpid = os.getpid()
print "PID: %s " % crtpid
send_length = self.sock.send("hello, world!")
print "sent %d characters..." % send_length
res = self.sock.recv(2048)
print "PID %s received: %s" % (crtpid, res)
def shutdown(self):
self.sock.close()
# 写服务端处理函数
class ForkingServerRequestHandler(SocketServer.BaseRequestHandler):
def handle(self):
data = self.request.recv(2048)
crtpid = os.getpid()
res = "%s: %s" % (crtpid, data)
print res
self.request.send(res)
return
# 继承
class ForkingServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
pass
def main():
server = ForkingServer(('localhost', 8989), ForkingServerRequestHandler)
ip, port = server.server_address
server_thread = threading.Thread(target=server.serve_forever)
server_thread.setDaemon(True)
server_thread.start()
print 'Server loop running PID: %s' % os.getpid()
client_1 = ForkingClient(ip, port)
client_2 = ForkingClient(ip, port)
client_1.run()
client_2.run()
client_1.shutdown()
client_2.shutdown()
server.socket.close()
if __name__ == '__main__':
main()
执行可以看到
duck@duck:~/sockdir/chapter_2$ python forkser.py
Server loop running PID: 22649
PID: %s 22649
sent 13 characters...
22651: hello, world!
PID 22649 received: 22651: hello, world!
PID: %s 22649
sent 13 characters...
22652: hello, world!
PID 22649 received: 22652: hello, world!
现在用ThreadingMixIn
优势:线程之间共享应用状态容易,避免进程间通信的复杂操作。
import os
import socket
import threading
import SocketServer
class ForkingClient():
def __init__(self, ip, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.connect((ip, port))
def run(self):
crtpid = os.getpid()
print "PID: %s " % crtpid
send_length = self.sock.send("hello, world!")
print "sent %d characters..." % send_length
res = self.sock.recv(2048)
print "PID %s received: %s" % (crtpid, res)
def shutdown(self):
self.sock.close()
class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
def handle(self):
data = self.request.recv(2048)
crtpid = threading.current_thread()
res = "%s: %s" % (crtpid, data)
print res
self.request.sendall("hello, client!")
return
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
def main():
server = ThreadedTCPServer(('localhost', 8989),ThreadedTCPRequestHandler)
ip, port = server.server_address
server_thread = threading.Thread(target=server.serve_forever)
# 线程互相独立,必须要设置为True
server_thread.setDaemon(True)
server_thread.start()
print 'Server loop running PID: %s' % server_thread.name
client_1 = ForkingClient(ip, port)
client_2 = ForkingClient(ip, port)
client_1.run()
client_2.run()
server.socket.close()
if __name__ == '__main__':
main()
可以看到基本套路都差不多,就是替换了一些处理类
而在大型网络服务器应用中,存在几百上千的并发连接时,为每个客户端建立单独的线程和进程不太实际。内存和主机cpu都有限,需要一种更好的办法来处理,那就是select模块。
这里可以了解一下,select,poll ,epoll三个模块
link
先用select的select模块编写一个聊天室服务器。分了三个模块
先写通讯模块
import cPickle
import struct
import socket
def send(channel, *args):
buf = cPickle.dumps(args)
value = socket.htonl(len(buf))
size = struct.pack("L", value)
channel.send(size)
channel.send(buf)
def receive(channel):
size = struct.calcsize("L")
size = channel.recv(size)
try:
size = socket.ntohl(struct.unpack("L", size)[0])
except struct.error, e:
return ''
buf = ""
while len(buf) < size:
buf += channel.recv(size - len(buf))
return cPickle.loads(buf)[0]
服务模块
from com_model import send, receive
import select
import socket
import sys
import signal
class ChatServer(object):
def __init__(self, port, backlog=5):
self.clients = 0
self.clientmap = {}
self.outputs = []
self.server = socket.socket(socket.AF_INET, socket.SOL_SOCKET)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server.bind(('localhost', port))
self.server.listen(backlog)
signal.signal(signal.SIGINT, self.sighandler)
def sighandler(self, signum, frame):
print "Shutting down server...."
for output in self.outputs:
output.close()
self.server.close()
def get_client_name(self, client):
info = self.clientmap[client]
host, name = info[0][0], info[1]
return '@'.join((name, host))
def run(self):
inputs = [self.server, sys.stdin]
self.outputs = []
running = True
while running:
try:
readable, writeable, exceptional = select.select(inputs, self.outputs, [])
except select.error, e:
break
for sock in readable:
if sock == self.server:
client, address = self.server.accept()
print "Chat server: Got connection %d from %s" % (client.fileno(), address)
cname = receive(client).split('NAME: ')[1]
self.clients += 1
send(client, "CLIENT: " + str(address[0]))
inputs.append(client)
self.clientmap[client] = (address, cname)
msg = "\n(Connected: New client (%d) from %s" % (self.clients, self.get_client_name(client))
for output in self.outputs:
elif sock == sys.stdin:
junk = sys.stdin.readline()
running = False
else:
try:
data = receive(sock)
if data:
msg = '\n#[' + self.get_client_name(sock) + ']>>' + data
for output in self.outputs:
if output != sock:
send(output, msg)
else:
print "Chat server: %d hung up" % sock.fileno()
self.clients -= 1
sock.close()
inputs.remove(sock)
self.outputs.remove(sock)
msg = '\n(Now hung up: Client from %s)' % self.get_client_name(sock)
for output in self.outputs:
send(output, msg)
except socket.error, e:
inputs.remove(sock)
self.outputs.remove(sock)
self.server.close()
def main():
server = ChatServer(8989)
server.run()
if __name__ == '__main__':
main()
客户端模块
from com_model import send, receive
import os
import select
import socket
import sys
import signal
class ChatClient(object):
def __init__(self, name, port, host="localhost"):
self.name = name
self.connected = False
self.host = host
self.port = port
self.prompt = '[' + '@'.join((name, socket.gethostname().split('.')[0])) + ']>'
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, self.port))
print "Now connected to chat server@ port %d" % self.port
self.connected = True
send(self.sock, 'NAME: ' + self.name)
data = receive(self.sock)
addr = data.split('CLIENT: ')[1]
self.prompt = '[' + '@'.join((self.name, addr)) + ']> '
except socket.error, e:
print "Failed connect server @port %d" % self.port
sys.exit(1)
def run(self):
while self.connected:
try:
sys.stdout.write(self.prompt)
sys.stdout.flush()
readable, writeable, exceptional = select.select([0, self.sock], [],[])
for sock in readable:
if sock == 0:
data = sys.stdin.readline().strip()
if data:
send(self.sock, data)
elif sock == self.sock:
data = receive(self.sock)
if not data:
print "Client shutting down."
self.connected = False
break
else:
sys.stdout.write(data + '\n')
sys.stdout.flush()
except KeyboardInterrupt:
print "interupt"
self.sock.close()
break
def main():
cli_name = str(os.getpid()) + 'client'
cli_port = raw_input("port: ")
client = ChatClient(cli_name, int(cli_port))
client.run()
if __name__ == '__main__':
main()
先启动服务端,然后启动客户端,输入服务端的port,这里写死成8989了,所以写入8989就可以连入通信了
duck@duck:~/sockdir/chapter_2/select_ex$ Python cli_chat.py
port: 8989
Now connected to chat server@ port 8989
[25395client@127.0.0.1]> haha
[25395client@127.0.0.1]>
(Connected: New client (2) from 25415client@127.0.0.1
[25395client@127.0.0.1]>
#[25415client@127.0.0.1]>>hee
[25395client@127.0.0.1]> nihao
接下来将用epoll写一个简单的WEB服务器,熟悉一下什么叫I/O多路复用,这名字翻译的也是一脸蒙逼
看看大神的们的解释,秒懂
link
相关文章