聊天服务器:

  服务器能接受来自不同用户的多个连接;

  允许用户同时(并行)操作;

  能解释命令,例如,say或者logout;

  容易拓展

套接字和端口:

  套接字是一种使用标准UNIX文件描述符(file descriptor)与其他程序通信的方式。套接字可以看作是处于不同主机之间的两个程序的通信连接端点。一方面程序将要传输的信息写入套接字中,而另一方面则通过读取套接字内的数据来获得传输的信息。

 

套接字通信示意图

  所示为使用套接字进行通信的示意图。假设存在两台主机A与B,在主机A中存在进程C,主机B中存在进程D,当进程C需要将数据送到进程D时,首先将数据写到套接字中,而进程D可以通过读取套接字来获得进程C发送的信息。

  在网络中,不同计算机是通过IP地址来区分的,也就是说,要将数据由主机A发送到主机B,只要知道主机B的IP地址就可以确定数据要发送的目的地。但是,在主机A与B中不可能只有进程C和进程D两个进程。主机B在收到主机A发送来的数据后,如何才能确定该数据是发送给进程D?因此,还需要某种标识信息,用于描述网络通信数据发往的进程。TCP/IP协议提出了协议端口的概念,用于标识通信的进程。

  当进程与某个端口绑定后,操作系统会将收到的给该端口的数据送往该进程。与文件描述符类似,每个端口都有被称为端口号的整数类型的标识符,该标识符用于区分不同的端口。不同协议可以使用相同的端口号进行数据传输。例如,TCP使用了344的端口号,UDP同样可以使用344端口号进行数据传输。

  端口号为一个16位的无符号整数,其取值范围为0~65535。低于256的端口被作为系统的保留端口号,主要用于系统进程的通信,不在这一范围的端口号被称为自由端口号,可以由进程自由使用。

python  asyncore 

  该模块是事件驱动(event-driver)。主要方法是loop(),用于进行网络事件的循环。而类dispatcher用于进行网络交互事件。不能直接Instance,需要通过继承并在__init__中显式的声明。它封装了网络的读写事件,并通过readable()和writable()来进行事件进行控制。

  下面是dispatcher的主要方法,这些都是基于消息的,捕捉到某个事件,就会触发:
(1)handle_read():Called when the asynchronous loop detects that a read() call on the channel’s socket will succeed.
(2)handle_write():Called when the asynchronous loop detects that a writable socket can be written
(3)handle_expt():Called when there is out of band (OOB) data for a socket connection
(4)handle_connect():Called when the active opener’s socket actually makes a connection
(5)handle_close():Called when the socket is closed.
(6)handle_error():Called when an exception is raised and not otherwise handled
(7)handle_accept():Called on listening channels (passive openers) when a connection can be established with a new remote endpoint that has issued     a connect() call for the local endpoint.
(8)readable():Called each time around the asynchronous loop to determine whether a channel’s socket should be added to the list on which read events    can occur
(10)writable():Called each time around the asynchronous loop to determine whether a channel’s socket should be added to the list on which write       events can occur
  下面是每个channel的主要方法:
(1)create_socket(family, type)
(2)connect(address)
(3)send(data)
(4)recv(buffer_size)
(5)listen(backlog)
(6)bind(address)
(7)accept()
(8)close()

1. 初次实现

1.1 CharServer类

#可接受连接的服务器

from asyncore import dispatcher
import asyncore,socket class ChatServer(dispatcher):
def handle_accept(self):
conn.addr=self.accept()
print 'Connection attempt from', addr[0] #客户端的Ip地址 s=ChatServer()
s.create_socket(socket.AF_INET, socket.SOCK_STREAM)#创建套接字
s.bind(('',5005))#将服务器绑定到具体的地址(主机名和端口),空字符串(主机名),意味着本地主机(即本地所有接口),端口号为5005
s.listen(5) #告诉服务器要监听连接,并且指定5个连接的代办的事物
asyncore.loop() #启动服务器,循环监听
#具有清理功能的基本服务器

from asyncore import dispatcher
import socket, asyncore PORT=5005 class ChatServer(dispatcher):
def __init__(self,port):
dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()#在服务器没有正确关闭时重用同一个地址(端口号),若不调用,服务器重启前需要等待一会
self.bind((' ',port))
self.listen(5) def handle_accept(self):
conn, addr=self.accept()
print 'Connection attempt from', addr[0] if __name__ =="__main__":
s=ChatServer(PORT)
try: asyncore.loop()
except KeyboardInterrupt: pass

1.2 CharSession类

  收集来自客服端的数据(文本)并且进行响应;

  async_chat类(位于asynchat模块中):隐藏了大多数基本的套接字读写操作,简单起见,覆盖其中的collect_incoming_data方法(每次从套接字中读取一些bit文本时调用),found_terminator方法(读取一个结束符时调用)

1)set_terminator方法用于将行终止对象设为网络协议中通常用作终止符的"\r\n"

2)ChatSession对象会将目前读取的数据作为保存为字符串列表data。但读入更多数据时,collect_incoming_data会自动被调用,将新读入的数据追加到列表中。

3)found_terminiator方法在读到终止对象时被调用

4)ChatServer保存回话列表

5)CharServer的handle_accept方法现在创建了新的ChatSession对象,并将其追加到会话列表中

# 带有ChatSession类的服务器程序

from asyncore import dispatcher
from asynchat import async_chat
import socket,asyncore PORT=5005 class ChatSession(async_chat):
def __init__(self,sock):
async_chat.__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)
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: print

1.3 整合

  添加功能:将用户的发言(输入的每一行)广播给其他用户——在服务器端的添加for循环,遍历会话的列表,并且将发言行写到每一个客户端里面。为了能在async_chat对象中写入数据,需要使用push方法。

from asyncore import dispatcher
from asynchat import async_chat
import socket,asyncore PORT=5005
NAME='TestChat' class ChatSession(async_chat):
'''
处理服务器和一个用户之间连接的类
'''
def __init__(self,server,sock):
#标准设置任务
async_chat.__init__(self,server,sock)
self.server=server
self.set_terminator("\r\n")
self.data=[]
# 问候用户
self.push('Welcome to %s \r\n' % self.server.name) 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):
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.sessions=[] def disconnect(self,session):
self.sessions.remove(session) def broadcast(self,line):
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)
try: asyncore.loop()
except KeyboardInterrupt: print

2. 改进

from asyncore import dispatcher
from asynchat import async_chat
import socket, asyncore 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 unknown(self, session, cmd):
#所有未知命令(除了login或者logout外的一起)
#会导致一个警告
session.push('Please log in \nUse "login"\r\n') def do_login(self, session, line):
name = line.strip()
#确保用户输入了名字
if not name:
session.push('Please enter a name\r\n')
#确保用户名没有被使用
elif name in self.server.users:
session.push('The name "%s" is taken.\r\n' % name)
sessoin.push('Please 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):
#处理say命令,用于广播一个单行,用发言的用户的名字作为前缀
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):
dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(('',port))
self.listen(5)
self.name = name
self.users = {}
self.main_room = ChatRoom(self) def handle_accept(self):
conn, addr = self.accept()
ChatSession(self,conn) if __name__ == '__main__':
s = ChatServer(PORT, NAME)
try: asyncore.loop()
except KeyboardInterrupt: print

聊天服务器可用命令:
  

python基础教程总结15——5 虚拟茶话会的更多相关文章

  1. python基础教程项目五之虚拟茶话会

    python基础教程项目五之虚拟茶话会 几乎在学习.使用任何一种编程语言的时候,关于socket的练习从来都不会少,尤其是会写一些局域网的通信的东西.所以书上的这个项目刚好可以练习一下socket编程 ...

  2. python基础教程总结15——7 自定义电子公告板

    1. Python进行SQLite数据库操作 简单的介绍 SQLite数据库是一款非常小巧的嵌入式开源数据库软件,也就是说没有独立的维护进程,所有的维护都来自于程序本身.它是遵守ACID的关联式数据库 ...

  3. python基础教程总结15——6 CGI远程编辑

    功能: 将文档作为普通网页显示: 在web表单的文本域内显示文档: 保存表单中的文本: 使用密码保护文档: 容易拓展,支持处理多余一个文档的情况 1.CGI CGI(Comment Gateway I ...

  4. python基础教程总结15——4 新闻聚合

    NNTP:网络新闻传输协议,Network News Transfer Protocol 目标: 从多种不同的来源收集新闻: 用户可以轻松添加新的新闻来源(甚至是新类型的新闻来源: 程序可以将编译好的 ...

  5. python基础教程总结15——3 XML构建网址

    要求: 网址用一个XML文件描述,其中包括独立网页和目录的信息: 程序能创建所需的目录和网页: 可以改变网址的设计,并且以新的设计为基础重新生成所有网页 概念: 网站:不用存储有关网站本身的任何信息, ...

  6. python基础教程总结15——1.即时标记

    1. 测试文档: # test_input.txt Welcome to World Wide Spam. Inc. These are the corporate web pages of *Wor ...

  7. python基础教程总结15——2 画幅好画

    要求:从Internet上下载数据文件:  分析数据文件并提取感兴趣的部分 工具:图形生成包(ReportLab,PYX等) 数据:太阳黑子和射电辐射流量(http://services.swpc.n ...

  8. Python基础教程(第2版 修订版) pdf

    Python基础教程(第2版 修订版) 目录 D11章快速改造:基础知识11.1安装Python11.1.1Windows11.1.2Linux和UNIX31.1.3苹果机(Macintosh)41. ...

  9. Python基础教程(第3版)PDF高清完整版免费下载|百度云盘

    百度云盘:Python基础教程(第3版)PDF高清完整版免费下载 提取码:gkiy 内容简介 本书包括Python程序设计的方方面面:首先从Python的安装开始,随后介绍了Python的基础知识和基 ...

随机推荐

  1. Linux rpm 命令参数使用…

    RPM是RedHat Package Manager(RedHat软件包管理工具)类似Windows里面的"添加/删除程序" rpm 执行安装包 二进制包(Binary)以及源代码 ...

  2. ACM-ICPC2018焦作网络赛 Transport Ship(二进制背包+方案数)

    Transport Ship 25.78% 1000ms 65536K   There are NN different kinds of transport ships on the port. T ...

  3. 无法加载MainifestResourceTransformer

    Cannot load implementation hint 'org.apache.maven.plugins.shade.resource.MainifestResourceTransforme ...

  4. [UE4]如何替换角色Mesh上的Material材质

    http://www.dawnarc.com/2016/10/ue4%E5%A6%82%E4%BD%95%E6%9B%BF%E6%8D%A2%E8%A7%92%E8%89%B2mesh%E4%B8%8 ...

  5. 洛谷P1368 均分纸牌(加强版)

    P1368 均分纸牌(加强版) 题目描述 有 N 堆纸牌,编号分别为 1,2,…, N.每堆上有若干张,纸牌总数必为 N 的倍数.可以在任一堆上取1张纸牌,然后移动. 移牌规则为:在编号为 1 堆上取 ...

  6. java实现对字符串和数字的冒泡排序

    public static void sort(Object[] objects){ if(objects instanceof Number[]){ for (int i = 0; i < o ...

  7. Python数据科学手册Seaborn马拉松可视化里时分秒转化为秒数的问题

    Python数据科学手册Seaborn马拉松可视化里时分秒转化为秒数的问题 问题描述: 我实在是太懒了,问题描述抄的网上的哈哈哈:https://www.jianshu.com/p/6ab7afa05 ...

  8. LeetCode 刷题笔记 (树)

    1.  minimum-depth-of-binary-tree 题目描述 Given a binary tree, find its minimum depth.The minimum depth ...

  9. jmeter接口测试-调用java的jar包-csv参数化请求-BeanShellPreProcessor生成验签作为请求验证参数-中文乱码----实战

    背景及思路: 需求:要做 创建新卡 接口的测试,要求: 1. 不需要每次手动修改请求参数. 方案:文中先用excle将数据准备好,导出为csv格式,再用jmeter的csv请求进行参数化 2. 卡号需 ...

  10. easyui---tabs(选项卡)

    配置好easyui环境 1.笔记: tabs(选项卡) class:class="easyui-tabs" //<div class="easyui-tabs&qu ...