继续学习Python中,最近看书《Python基础教程》中的虚拟茶话会项目,觉得很有意思,自己敲了一遍,受益匪浅,同时记录一下。

主要用到异步socket服务客户端和服务器模块asyncore以及异步socket命令和响应处理模块asynchat

其中asyncore模块中只有一个类dispatcher,我们继承该类去创建单会话的服务端,包括初始化socket的ip、port、ChatRoom(聊天房间)等,复写handle_accept方法调用命令和响应处理类ChatSession()。ChatSession()类继承了asynchat中的async_chat类,该类为虚拟类,继承asyncore.dispatcher,可以使用dispatcher的方法,但必须复写自己的collect_incoming_data()found_terminator()方法,使其有意义。从而基于事件触发,异步处理socket通信中的响应和请求。运行时,ChatSession初始化函数中可用定义的enter方法进入LoginRoom类(用来进入房间,注册),所有的会话都开始于单独的LoginRoom中。

之后,定义了CommandHandler,Room,LoginRoom,LogoutRoom和ChatRoom类。继承关系:CommandHandler->Room->LoginRoom,LogoutRoom,ChatRoom

CommandHandler是负责具体处理相应的命令,在ChatSession.found_terminator方法中,在读取完整的命令后,调用当前room(Room,LoginRoom或ChatRoom)继承的CommandHandler中的handle方法,处理响应命令,此处用来调用Room.do_logout、LoginRoom.do_login、ChatRoom.do_say、ChatRoom.do_look、ChatRoom.do_who这些do_前缀的方法。

do_Logout方法生成EndSession异常,EndSession异常调用ChatSession.handle_close()方法,handle_close()方法使用enter方法进入LogoutRoom类,并调用LogoutRoom.add(self, session)解除users字典中的用户的绑定

代码:

#-*- coding: UTF-8 -*-
from asyncore import dispatcher
from asynchat import async_chat
import asyncore, socket

PORT = 5005
NAME = 'TestChat'

class EndSession(Exception):pass

class CommandHandler:
    """
    类似于标准库中cmd.Cmd的简单命令处理程序
    """
    def unknown(self, session, cmd):
        """响应未知命令"""
        session.push('Unknown command: %s\r\n' % cmd)
        
    def handle(self, session, line):
        """处理从给定的会话中接收到的行"""
        if not line.strip(): return
        #分离命令:
        parts = line.split(' ', 1)
        cmd = parts[0]
        try: line = parts[1].strip()
        except IndexError: line = ''
        #试着查找处理程序
        meth = getattr(self, 'do_'+cmd, None)
        try:
            #假定它是可以调用的:
            meth(session, line)
        except TypeError:
            #如果不可以呗调用,此段代码响应位置的命令:
            self.unknown(session, cmd)
class Room(CommandHandler):
    """
    包括一个或多个用户(会话)的泛型环境。它负责基本的命令处理和广播
    """
    def __init__(self, server):
        self.server = server
        self.sessions=[]
    
    def add(self, session):
        #一个会话(用户)已经进入房间
        self.sessions.append(session)
        
    def remove(self, session):
        #一个会话(用户)已经离开房间
        self.sessions.remove(session)
        
    def broadcast(self, line):
        #向房间中的所有会话发送一行
        for session in self.sessions:
            session.push(line)
    
    def do_logout(self, session, line):
        '响应logout命令'
        raise EndSession
    
class LoginRoom(Room):
    """
    为刚刚连接上的用户准备房间
    """
    
    def add(self, session):
        Room.add(self, session)
        #当用户进入时, 问候
        self.broadcast('Welcome to %s\r\n' % self.server.name)
    
    def unknow(self, session, cmd):
        #所有未知命令(除了login或者logout外的一切)
        #会导致一个告警
        session.push('Please log in \nUse "login<nick>"\r\n')
        
    def do_login(self, session, line):
        name = line.strip()
        #确保用户输入了名字
        if not name:
            session.push('Pleas enter a name\r\n')
        #确保用户名字没有被使用
        elif name in self.server.users:
            session.push('The name "%s" is taken.\r\n' % name)
            session.push('Pleas try again.\r\n')
        else:
            #名字没问题,所以存储在会话中,并且
            #将用户移动到主聊天室
            session.name = name
            session.enter(self.server.main_room)

 class ChatRoom(Room):
    """
    为多用户相互聊天准备的房间
    """
    
    def add(self, session):
        #告诉所有人有新用户进入:
        self.broadcast(session.name + 'has entered the room\r\n')
        self.server.users[session.name] = session
        Room.add(self, session)
    
    def remove(self, session):
        Room.remove(self, session)
        #告诉所有人有用户离开:
        self.broadcast(session.name + 'has left the room\r\n')
    
    def do_say(self, session, line):
        self.broadcast(session.name + ':' + line + '\r\n')
        
    def do_look(self, session, line):
        #处理look命令,该命令用于查看谁在房间内
        session.push('The following are in this room: \r\n')
        for other in self.sessions:
            session.push(other.name + '\r\n')
    
    def do_who(self, session, line):
        #处理who命令,该命令用于查看谁登陆了
        session.push('The following are logged in: \r\n')
        for name in self.server.users:
            session.push(name + '\r\n')
            
class LogoutRoom(Room):
    """
    为单用户准备的简单房间。只用于将用户名从服务器移除
    """
    
    def add(self, session):
        #当会话(用户)进入要删除的LogoutRoom时
        try: del self.server.users[session.name]
        except KeyError: pass
            
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.name = None
        
        #所有的会话都开始于单独的LoginRoom中:
        self.enter(LoginRoom(server))
        
    def enter(self, room):
        #从当前房间中移除自身(self),并且将自身添加到下一个房间...
        try: cur = self.room
        except AttributeError: pass
        else: cur.remove(self)
        self.room = room
        room.add(self)
        
    def collect_incoming_data(self, data):
        self.data.append(data)
    
    def found_terminator(self):
        """
        如果发现一个终止对象,也就意味着读入了一个完整的行,将其广播给每个人
        """
        line = ''.join(self.data)
        self.data = []
        try: self.room.handle(self, line)
        except EndSession:
            self.handle_close()
        
    def handle_close(self):
        async_chat.handle_close(self)
        self.enter(LogoutRoom(self.server))
        
class ChatServer(dispatcher):
    """
    只有一个房间的聊天服务器
    """
    
    def __init__(self, port, name):
        # Standard setup tasks
        dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(('',port))
        self.listen(5)
        self.name = name
        self.users={}
        self.main_room = ChatRoom(self)
    
    def handle_accept(self):
        conn, addr = self.accept()
        #print 'Connection attempt from', addr[0]
        #self.sessions.append(ChatSession(self, conn))
        ChatSession(self, conn)

if __name__=="__main__":
    s = ChatServer(PORT, NAME)
    try: asyncore.loop()
    except KeyboardInterrupt: print

测试效果:

服务启动在 telnet 119.29.207.141 5005,有兴趣可以一起玩耍交流:)

当前命令:login 名字;say 要说的话;look(查看房间有谁);who(查看谁在线,谁登陆了);loginout(注销登陆)

Python简易聊天工具-基于异步Socket通信的更多相关文章

  1. RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.8 版本━新增企业通(内部简易聊天工具)

    RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.8 版本 新增企业通(内部简易聊天工具) RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,给用 ...

  2. c#之异步Socket通信

    0.基于上一篇的c#之Socket(同步)通信,在几个大神评论之后,发现是有挺多地方不足的,所以写了一个改进版本的基于c#的异步Socket通信.再加深一下对Socket的使用和理解.其中客户端和服务 ...

  3. JAVA基础知识之网络编程——-基于AIO的异步Socket通信

    异步IO 下面摘子李刚的<疯狂JAVA讲义> 按照POSIX标准来划分IO,分为同步IO和异步IO.对于IO操作分为两步,1)程序发出IO请求. 2)完成实际的IO操作. 阻塞IO和非阻塞 ...

  4. java+socket 简易聊天工具

    1.服务器端程序 package com.test3; import java.io.*; import java.net.*; import java.awt.*; import java.awt. ...

  5. Python 简易聊天机器人

    聊天机器人 | |-----MySql | |---module--"逻辑运算层" | | | |---ciku--"与词库交互" | | | |---dict ...

  6. 使用 boost.asio 简单实现 异步Socket 通信

     客户端: class IPCClient { public: IPCClient(); ~IPCClient(); bool run(); private: bool connect(); bool ...

  7. c# TcpClient简易聊天工具

    说明: TcpClient 链接是一个比较安全稳定的链接,作为聊天或者是数据稳定传输,是比较合适的,下面的代码测试过,如果你吧他放在公网服务器上,也是可以用的 using System; using ...

  8. 05-python 学习第五天,简易聊天工具(shelve模块练习)

    需求:1.有两个用户,一个是mychat.py,另一个是youchat.py2.通过执自己的文件,可以看到对方输入的内容,实现连个命令行窗口之间聊天的功能.3.通过shelve 持久化写入和和读取功能 ...

  9. 使用PHP+Swoole实现的网页即时聊天工具:PHPWebIM

    使用PHP+Swoole实现的网页即时聊天工具 全异步非阻塞Server,可以同时支持数百万TCP连接在线 同时支持websocket+comet2种兼容协议,可用于所有种类的浏览器包括IE 拥有完整 ...

随机推荐

  1. HTML5 video 视频标签 常用属性

    最近在做手机端的 h5 页面的视频直播功能,用到了 Video 标签.其常用的属性有以下几个: src.poster.preload.autoplay.loop.controls.width.heig ...

  2. An error I have completed recently

    在上学期开发javaweb的项目中,遇见一个字符串池的问题. 大致如下: 在上传一篇文章的时候,通过字符串的截取获取该篇文章的后缀名,如doc.pdf.txt....然后规定只能上传pdf和doc格式 ...

  3. Swift2.1 语法指南——高级操作符

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  4. BeanFactory和ApplicationContext的区别

     1.BeanFactory和ApplicationContext的异同点: 相同点:     两者都是通过xml配置文件加载bean,ApplicationContext和BeanFacotry相比 ...

  5. 利用loadrunner代理方式,录制手机APP脚本

    利用loadrunner代理方式录制手机(iPhone.android)应用程序HTTP脚本 工具/原料 loadrunner 智能手机 方法/步骤   利用笔记本网卡或者类似360随身wifi,在安 ...

  6. php运行出现Call to undefined function curl_init()的解决方法

    解决方法如下: 1.在php.ini中找到extension=php_curl.dll,去掉前面的分号;,然后将php.ini拷贝到c:\windows. 2.重启IIS服务,或回收应用程序池即可.

  7. select根据text选择项与select其它操作

    // 6.设置select中text="paraText"的第一个Item为选中 function jsSelectItemByValue(objSelect, objItemTe ...

  8. 京东云、新浪微博等专家畅谈Docker未来格局:开放与竞争(下)

    在上次推送的文章中(传送门),田琪老师分享了他的DockerCon 2015峰会见闻.在“QCon高可用架构群”中,田老师分享之后,几位专家也参与了讨论.他们是: 闫国旗:京东资深架构师,京东架构技术 ...

  9. Entity Framework Code First数据库自动更新2

    以前做项目的时候,没有采用分类库的形式,所以迁移一致非常顺利,没有出现过任何状况. 这次做项目稍微有点大,必须要分类库才方便开发维护. 在解决方案中启用项目EntityFramework迁移时却发生了 ...

  10. Caffe学习系列(15):添加新层

    如何在Caffe中增加一层新的Layer呢?主要分为四步: (1)在./src/caffe/proto/caffe.proto 中增加对应layer的paramter message: (2)在./i ...