聊天服务器:

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

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

  能解释命令,例如,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

聊天服务器可用命令:
  

最新文章

  1. SuperSocket 1.6.4 通过FixedHeaderReceiveFilter解析自定义协议
  2. JAVA数据库处理(连接,数据查询,结果集返回)
  3. Headfirst设计模式的C++实现——外观模式(Facade)
  4. 【原创】一起学C++ 之enum ---------C++ primer plus(第6版)
  5. (简单) HDU 2612 Find a way,BFS。
  6. CentOS下架设VNC服务器
  7. POJ1845-Sumdiv大数约数和
  8. Django项目结构介绍
  9. .NET Core GC 的设计
  10. php签名认证
  11. Apache ActiveMQ 远程代码执行漏洞 (CVE-2016-3088)案例分析
  12. Android -- 贝塞尔曲线公式的推导和简单使用
  13. [Algorithm] Warm-up puzzles
  14. APP-3-百度地图应用
  15. Iterator迭代器对象
  16. 虚拟机内存复用技术的比较(XEN系统)
  17. Package设计1:选择数据类型、暂存数据和并发
  18. implicit declaration of function 'copy_from_user'
  19. [Training Video - 4] [Selenium IDE]
  20. New Concept English three (22)

热门文章

  1. Hibernate区分不同对象的方法
  2. php使用curl带cookie访问一直失败求助
  3. 项目debug2
  4. 数组,for语句(补10.11)
  5. Sharepoint2013搜索学习笔记之修改搜索拓扑(三)
  6. 再论c#获取存储过程返回值(包括SqlSugar)
  7. cf706C(dp)
  8. mCustomScrollbar 滚动条的使用
  9. IT兄弟连 Java语法教程 编译Java程序
  10. pytest框架(三)