背景
互联网上在线聊天服务的种类很多, 如IRC, 在Python中实现这样一个方法, 可以使用Twisted
框架.
自己写一个的原因:
学习网络编程基本的知识 看书笔记总结
聊天服务器基本的功能如下:
服务器能接受不同客户端的连接允许用户并行操作能够解释命令, 如在shell中的操作
初次实现1 迷你服务器
from asyncore import dispatcherimport asyncoreclass ChatServer(dispatcher): passs = ChatServer()asyncore.loop
2 可以接受连接的服务器
from asyncore import dispatcherimport asyncoreimport socketPORT = 5267class ChatServer(dispatcher): def handle_accept(self): conn, addr = self.accept() print 'Connection from ', addr[0]s = ChatServer()s.create_socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(('', PORT))s.listen(5)asyncore.loop()
handle_accept
方法会调用允许客户端连接的self.accept函数. 他返回一个连接和一个地址, 初始化的过程, 调用了create_socket
创建套接字, bind
来绑定端口, listen
用来监听. 试着运行下面的语句:
telnet localhost 5267
会出现如下
fish@love67:~$telnet localhost 5267Connection form 127.0.0.1
3 具有一些清理功能的服务器
上面的程序使用键盘关闭(ctrl + c等)会导致堆栈跟踪, 为了避免这类情况, 可以在 try/except
语句放置loop
. 再加上一点清理功能, 如下:
from asyncore import dispatcherimport asyncoreimport socketPORT = 5267class ChatServer(dispatcher): def __init__(self, port): # 子类的初始化, 也可以用super dispatcher.__init__(self, port) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) # 清理函数 set_reuse_addr() self.set_reuse_addr() self.bind(('', port)) self.listen(5) def handle_accept(self): conn, addr = self.accept() print 'Connection from ' , addr[0]if __name__ == '__main__': s = ChatServer(PORT) try: asyncore.loop() except KeyboardInterrupt: pass
4 ChatSession类
下面代码中, async_chat
类(位于asynchat中)的好处是他隐藏了大多数基本的套接字读写操作, 为了让他起作用, 只需要覆盖两个函数: collect_incoming_data
和found_terminator
. 前者每次从套接字中读取一些bit文本时调用, 后者在读取一个结束符是调用.
from asyncore import dispatcherfrom asynchat import async_chatimport asyncore, socketPORT = 5267class ChatSession(async_chat): def __init__(self, sock): asyncore.__init__(self, sock) self.set_terminator('\r\n') self.data = [] # 函数覆盖 def collect_incoming_data(self, data): self.data.append(data) # 函数覆盖 def found_terminator(self): line = ''.join(self.data) self.data = [] print lineclass ChatServer(dispatcher): def __init__(self, port): dispatcher.__init__(self, port) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.bind(('', port)) self.listen(5) self.sessions = [] def handle_accept(self): conn, addr = self.accept() self.sessions.append(ChatSession(conn))if __name__ == '__main__': s = ChatServer(PORT) try: asyncore.loop() except KeyboardInterrupt: pass
值得注意的是:
code>set_terminator</code>方法用于终止对象, 设定为网络卸协议中经常使用的终止符\r\nChatSession
对象会将目前读取的数据保存为字符串列表data, 当读取更多数据时, collect_incoming_data
会自动调用, 将读取的数据追加到列表中found_terminator
方法在读取到终止符时停止, 并将self.data
重置为空列表.ChatServer
保存会话列表
整合简单的聊天服务器
from asyncore import dispatcherimport asyncore, socketfrom asynchat import async_chatPORT = 5267NAME = "ChatRoom"class ChatSession(async_chat): def __init__(self, server, sock): async_chat.__init__(self, sock) self.server = server self.set_terminator('\r\n') self.data = [] self.push('Welcome to %s \r\n', %self.server) def collect_incoming_data(self, data): self.data.append(data) def found_terminator(self): "如果发现了一个终止符, 说明读入了完整的一行, 将其广播给所有人" line = ''.join(self.data) self.data = [] self.server.broadcast(line) def handle_close(self): async_chat.handle_close(self) self.server.disconnect(self)class ChatServer(dispatcher): "接收连接并产生单个会话的类, 他还会处理到其他会话的广播" def __init__(self, port, name): dispatcher.__init__(self, port) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.bind(('', port)) self.name = name self.listen(5) self.sessions = [] def disconnect(self, session): self.sessions.remove(session) def broadcast(self, session): for session in self.sessions: session.push(line + '\r\n') def handle_accept(self): conn, addr = self.accept() self.sessions.append(ChatSession(self, conn))if __name__ == '__main__': s = ChatServer(PORT, NAME) try: asyncore.loop() except KeyboardInterrupt: pass