#背景

互联网上在线聊天服务的种类很多, 如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_datafound_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

Tags