用select.select编写一个聊天服务器
代码如下:
#coding: utf-8
import select
import socket
import sys
import signal
import cPickle
import struct
import argparse
SERVER_HOST = 'localhost'
CHAT_SERVER_NAME = 'server'
# Some utilities
'''send函数定义通过cPicle.dumps()将需要发送的数据序列化,
然后通过socket.htonl()方法将序列化后的数据长度转化为网络字节序格式,以便于底层传输,
再将网络字节序格式的长度打包为'L'类型的C struct, 最后发送打包后的长度以及序列化后的数据
receive函数即是send反向过程,先接收到打包后的长度,将其解包,然后再主机序列化,
所有数据接收完成以后,返回解除序列化后的原始数据。'''
def send(channel, *args):
buffer = cPickle.dumps(args)#
value = socket.htonl(len(buffer))#
size = struct.pack("L",value)
channel.send(size)
channel.send(buffer)
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]
'''聊天室服务器需要能做到:
1,记录连接数
2,记录连接的客户端地址以及名称映射 ,需要时返回名称地址
3,重用地址
4,检测键盘中断
5,处理输入及请求
先实现1,2,3,4点:'''
class ChatServer(object):
""" An example chat server using select """
def __init__(self, port, backlog=5):
self.clients = 0
self.clientmap = {}# record the map
self.outputs = [] # list output sockets
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#TCP socket
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)#enable socket reuse
self.server.bind((SERVER_HOST, port))#bind socket to the address
print 'Server listening to port: %s ...' %port
self.server.listen(backlog)# listen the port
# Catch keyboard interrupts
signal.signal(signal.SIGINT, self.sighandler)
def sighandler(self, signum, frame):
""" Clean up client outputs"""
# Close the server
print 'Shutting down server...'
# Close existing client sockets
for output in self.outputs:
output.close()
self.server.close()
def get_client_name(self, client):
""" Return the name of the client """
info = self.clientmap[client]
host, name = info[0][0], info[1]
return '@'.join((name, host))
'''第5点处理输入及请求分几种情况,
1处理客户端接入并通知其他客户端,
2处理客户端输入信息并转发给其他客户端,
处理标准输入, 这里丢弃,
3处理异常
'''
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:
# handle the server socket
client, address = self.server.accept()
print "Chat server: got connection %d from %s" % (client.fileno(),address)
# Read the login name
cname = receive(client).split('NAME: ')[1]
# Compute client name and send back
self.clients += 1
send(client, 'CLIENT: ' + str(address[0]))
inputs.append(client)
self.clientmap[client] = (address, cname)
# Send joining information to other clients
msg = "\n(Connected: New client (%d) from %s)" % (self.clients,self.get_client_name(client))
for output in self.outputs:
send(output, msg)
self.outputs.append(client)
elif sock == sys.stdin:
# handle standard input
junk = sys.stdin.readline()
running = False
else:
# handle all other sockets
try:
data = receive(sock)
if data:
# Send as new client's message...
msg = '\n#[' + self.get_client_name(sock) + ']>>' + data
# Send data to all except ourself
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)
# Sending client leaving information to others
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:
# Remove
inputs.remove(sock)
self.outputs.remove(sock)
self.server.close()
'''
实现客户端类。主要处理输入与从server端返回消息的读取
'''
class ChatClient(object):
def __init__(self, name, port, host=SERVER_HOST):
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)#TCP socket
self.sock.connect((host, port))#make a connnect
print "Now connceted to chat server at port %d" % port
self.connected = True# flags to record the connection
send(self.sock, 'NAME: ' + self.name)#send client name to other sockets
data = receive(self.sock)
addr = data.split('CLIENT: ')[1]
self.prompt = '[' + '@'.join((self.name, addr)) + ']>'
except socket.error, e:
print "Failed to connect to chat server @ port %d" % self.port
sys.exit(1)
def run(self):
"""client main loop"""
while self.connected:
try:
sys.stdout.write(self.prompt)
sys.stdout.flush()
#wait for input from stdin or socket
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, e:
print " Client interrupted, "
self.sock.close()
break
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Socket Server Example with Select')
parser.add_argument('--name', action="store", dest="name", required=True)
parser.add_argument('--port', action="store", dest="port", type=int, required=True)
given_args = parser.parse_args()
port = given_args.port
name = given_args.name
if name == CHAT_SERVER_NAME:
server = ChatServer(port)
server.run()
else:
client = ChatClient(name=name, port=port)
client.run()
运行及测试,开一个终端 python uchat.py --name=server --port=8800 运行服务器
而后再开几个客户端,注意需要新开终端运行 python uchat.py --name=client1 --port=8800
注意端口一定要一致,name可以随意定