Python網路程式設計攻略(2-2)
阿新 • • 發佈:2018-11-11
用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可以隨意定