python实现虚拟茶话会

这个项目目的是编写一个聊天服务器,该聊天服务器的功能有:

服务器能同时接收来自不同用户的连接

允许用户同时操作

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

服务器容易扩展

这个项目里面我们会使用到的模块式asyncore,使用asyncore框架,程序可以处理同时连接多个用户。asyncore框架基于一些底层的机制,这些机制允许服务器逐个的对于连接上的用户进行服务。在处理下一个连接前,它并不读取当前用户的所有可用数据,而只读取一部分。除此之外,服务器只从那些需要读取数据的套接字中读取。程序就这样一遍遍的循环。写入操作同理。只使用socket和select模块就可以实现,但是asyncore和asynchat提供了一个可以处理所有细节的有用框架。

首先我们需要一个客户端来测试服务器,在windows下我们可以用telnet服务端,在unix内也可以用telnet。

初次实现:

首先我们需要创建两个类:一个作为聊天服务器,一个用于表示每个聊天会话(以连接用户)

要生成基本的ChatServer类,需要继承asyncore模块中的dispatcher类,dispatcher基本上就是一个套接字对象,但是可以利用它额外的事件处理特性。

下面是可以接受连接的服务器代码

 1 from asyncore import dispatcher
 2 import socket, asyncore
 3 
 4 class ChatServer(dispatcher):
 5     def handle_accept(self):
 6         conn, addr = self.accept()
 7         print 'Connection attempt from ',addr[0]
 8 
 9 s = ChatServer()
10 s.create_socket(socket.AF_INET, socket.SOCK_STREAM)
11 s.bind((''.5005))
12 s.listen(5)
13 asyncore.loop()

handle_accpet方法会调用允许客户端连接的self.accept函数。她会返回一个连接和一个地址。handle_accept方法知识打印有关连接尝试的信息。

当我们运行这个服务器并且用客户端连接他的时候,我们可以再服务器的控制台上看到返回的连接信息。

ChatSession类的主要任务是为每一个连接创建一个对象,该对象负责收集来自客户端的数据并且进行响应。我们可以自己继承dispatcher并且重写一些方法来实现这个功能,但是幸运的是已经有现成的模块能够完成绝大多数的工作,asynchat

下面便是一个简单的聊天服务器

 1 # -*- coding:utf-8 -*-
 2 '''
 3 xianghang
 4 2015.4.9
 5 虚拟聊天室
 6 '''
 7 
 8 from asyncore import dispatcher
 9 from asynchat import async_chat
10 import socket, asyncore
11 
12 PORT = 5005
13 NAME = 'TestChat'
14 class ChatSessioin(async_chat):
15     '''
16     处理服务器和一个用户之间连接的类
17     '''
18     def __init__(self, server, sock):
19         #标准设置任务
20         async_chat.__init__(self, sock)
21         self.server = server
22         self.set_terminator('
')
23         self.data = []
24         #问候用户
25         self.push('Welcome to %s
' % self.server.name)
26 
27     def collect_incoming_data(self, data):
28         self.data.append(data)
29 
30     def found_terminator(self):
31         '''
32         如果发现了一个终止对象,也就意味着读入了一个完整的行,将广播给所有人
33         '''
34         line = ''.join(self.data)
35         self.data = []
36         self.server.broadcast(line)
37 
38     def handle_close(self):
39         async_chat.handle_close(self)
40         self.server.disconnect(self)
41 
42 class ChatServer(dispatcher):
43     '''
44     接收连接并且产生单个会话的类。他还会处理到其他会话的广播
45     '''
46     def __init__(self, port, name):
47         #标准设置任务
48         dispatcher.__init__(self)
49         self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
50         self.set_reuse_addr()
51         self.bind(('', port))
52         self.listen(5)
53         self.name = name
54         self.sessions = []
55     def disconnect(self, session):
56         self.sessions.remove(session)
57 
58     def broadcast(self, line):
59         for session in self.sessions:
60             session.push(line+'
')
61 
62     def handle_accept(self):
63         conn, addr = self.accept()
64         self.sessions.append(ChatSessioin(self, conn))
65 
66 if __name__ == '__main__':
67     s = ChatServer(PORT, NAME)
68     try: asyncore.loop()
69     except KeyboardInterrupt: print

运行这个程序后会发现,客户端虽然能够显示其它用户发送的信息,但不能识别是哪一个用户,而且整个程序没有登录登出等功能,下面我们就来扩展这些功能。

再次实现:

首先我们把服务器分为三个房间,登录房间,聊天室,登出房间,三个房间分别为三个类,这些类的父类拥有添加和删除会话 的功能。代码如下

 1 class Room(CommandHandler):
 2     '''
 3     包括一个或多个用户(会话)的泛型环境,他负责基本的命令处理和广播
 4     '''
 5 
 6     def __init__(self, server):
 7         self.server = server
 8         self.sessions = []
 9 
10     def add(self, session):
11         '一个会话用户已进入房间'
12         self.sessions.append(session)
13 
14     def remove(self, session):
15         '一个会话用户已离开房间'
16         self.sessions.remove(session)
17 
18     def broadcast(self, line):
19         '向房间中的所有会话发送一行'
20         for session in self.sessions:
21             session.push(line)
22 
23     def do_logout(self, session, line):
24         '响应logout命令'
25         raise EndSession

登录房间继承room父类

 1 class LoginRoom(Room):
 2     '''
 3     为刚刚连接上的用户准备的房间
 4     '''
 5     def add(self, session):
 6         Room.add(self, session)
 7         #当用户进入时,问候他
 8         self.broadcast('Welcome to %s 
' % self.server.name)
 9 
10     def unknown(self, session, cmd):
11         #所有未知命令(除了login或者logout除外)
12         #会导致一个警告
13         session.push('Please log in 
Use "login <nick>"
')
14 
15     def do_login(self, session, line):
16         name = line.strip()
17         #确保用户输入了名字
18         if not  name:
19             session.push('Please enter a name
')
20         #确保用户名没有被使用
21         elif name in self.server.users:
22             session.push('The name "%s" is taken.
' % name)
23             session.push('Please try again.
')
24         else:
25             #名字没问题,所以存储在会话中
26             #将用户移动到主聊天室
27             session.name = name
28             session.enter(self.server.main_room)

  登录房间是让用户登录时所在的房间,在该房间内接收用户的登录命令,登陆成功后进入聊天房间。同样的,也有登出房间登出房间同理。

  聊天室房间负责处理say,look命令,say命令可以让用户在该房间内发言,并显示在其他的客户端上;look命令可以让用户查看当前聊天室所存在的用户。

 1 class ChatRoom(Room):
 2     '''
 3     为多用户相互聊天准备房间
 4     '''
 5     def add(self, session):
 6         #告诉所有人有新用户进入:
 7         self.broadcast(session.name + ' has entered the room
')
 8         self.server.users[session.name] = session
 9         Room.add(self, session)
10 
11     def remove(self, session):
12         Room.remove(self, session)
13         #告诉所有人有用户离开
14         self.broadcast(session.name + ' has left the room.
')
15 
16     def do_say(self, session, line):
17         self.broadcast(session.name +':' + line + '
')
18 
19     def do_look(self, session, line):
20         '处理look命令,该命令用于查看谁在房间'
21         session.push('The following are in this room:
')
22         for other in self.sessions:
23             session.push(other.name + '
')

整个服务器程序的结构如下图:

原文地址:https://www.cnblogs.com/Blaxon/p/4411474.html