简单聊天室<一>
#背景
互联网上在线聊天服务的种类很多, 如IRC, 在Python中实现这样一个方法, 可以使用Twisted
框架.
自己写一个的原因:
学习网络编程基本的知识
看书笔记总结
聊天服务器基本的功能如下:
- 服务器能接受不同客户端的连接
- 允许用户并行操作
- 能够解释命令, 如在shell中的操作
#初次实现
###1 迷你服务器
from asyncore import dispatcher
import asyncore
class ChatServer(dispatcher):
pass
s = ChatServer()
asyncore.loop
###2 可以接受连接的服务器
from asyncore import dispatcher
import asyncore
import socket
PORT = 5267
class 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 5267
Connection form 127.0.0.1
###3 具有一些清理功能的服务器
上面的程序使用键盘关闭(ctrl + c等)会导致堆栈跟踪, 为了避免这类情况, 可以在 try/except
语句放置loop
. 再加上一点清理功能, 如下:
from asyncore import dispatcher
import asyncore
import socket
PORT = 5267
class 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 dispatcher
from asynchat import async_chat
import asyncore, socket
PORT = 5267
class 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 line
class 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\n
ChatSession
对象会将目前读取的数据保存为字符串列表data, 当读取更多数据时,collect_incoming_data
会自动调用, 将读取的数据追加到列表中found_terminator
方法在读取到终止符时停止, 并将self.data
重置为空列表.ChatServer
保存会话列表
#整合
##简单的聊天服务器
from asyncore import dispatcher
import asyncore, socket
from asynchat import async_chat
PORT = 5267
NAME = "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
blog comments powered by Disqus
Published
18 February 2014