首页 后端开发 Python教程 Python聊天室实例程序分享

Python聊天室实例程序分享

Jun 10, 2016 pm 03:06 PM
python 聊天室

上一篇 我们学习了简单的Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型。本文再通过一个例子来加强一下对 Socket 编程的理解。

一、聊天室程序需求
我们要实现的是简单的聊天室的例子,就是允许多个人同时一起聊天,每个人发送的消息所有人都能接收到,类似于 QQ 群的功能,而不是点对点的 QQ 好友之间的聊天。如下图:

我们要实现的有两部分:

  • Chat Server:聊天服务器,负责与用户建立 Socket 连接,并将某个用户发送的消息广播到所有在线的用户。
  • Telnet Client:用户聊天客户端,可以输入聊天的内容并发送,同时可以显示其他用户的消息记录。

同样,我们的消息通信采用 TCP 连接保证可靠性。在分别对服务端和客户端进行程序设计之前,首先要学习一下 Python 中实现异步 I/O 的一个函数 —— select。

二、Python 异步I/O
Python 在 select 模块中提供了异步 I/O(Asynchronous I/O),这与 Linux 下的 select 机制相似,但进行一些简化。我首先介绍一下 select,然后告诉你在 Python 中如何使用它。

前面文章使用多线程来并行处理多路 socket I/O,这里介绍的select 方法允许你响应不同 socket 的多个事件以及其它不同事件。例如你可以让 select 在某个 socket 有数据到达时,或者当某个 socket 可以写数据时,又或者是当某个 socket 发生错误时通知你,好处是你可以同时响应很多 socket 的多个事件。

Linux 下 C 语言的 select 使用到位图来表示我们要关注哪些文件描述符的事件,Python 中使用 list 来表示我们监控的文件描述符,当有事件到达时,返回的也是文件描述符的 list,表示这些文件有事件到达。下面的简单程序是表示等待从标准输入中获得输入:

rlist, wlist, elist = select.select( [sys.stdin], [], [] )

print sys.stdin.read()
登录后复制

select 方法的三个参数都是 list 类型,分别代表读事件、写事件、错误事件,同样方法返回值也是三个 list,包含的是哪些事件(读、写、异常)满足了。上面的例子,由于参数只有一个事件 sys.stdin,表示只关心标准输入事件,因此当 select 返回时 rlist 只会是 [sys.stdin],表示可以从 stdin 中读入数据了,我们使用 read 方法来读入数据。

当然 select 对于 socket 描述符也是有效的,下面的一个例子是创建了两个 socket 客户端连接到远程服务器,select 用来监控哪个 socket 有数据到达:

import socket
import select

sock1 = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock2 = socket.socket( socket.AF_INET, socket.SOCK_STREAM )

sock1.connect( ('192.168.1.1', 25) )
sock2.connect( ('192.168.1.1', 25) )

while 1:

  # Await a read event
  rlist, wlist, elist = select.select( [sock1, sock2], [], [], 5 )

  # Test for timeout
  if [rlist, wlist, elist] == [ [], [], [] ]:
    print "Five seconds elapsed.\n"

  else:
    # Loop through each socket in rlist, read and print the available data
    for sock in rlist:
      print sock.recv( 100 )

登录后复制

好了,有了上面的基础,我们就可以来设计聊天室的服务器和客户端了。

三、聊天室服务器
聊天室服务器主要完成下面两件事:

  • 接收多个客户端的连接
  • 从每个客户端读入消息病广播到其它连接的客户端

我们定义一个 list 型变量 CONNECTION_LIST 表示监听多个 socket 事件的可读事件,那么利用上面介绍的我们的服务器使用 select 来处理多路复用 I/O 的代码如下:

# Get the list sockets which are ready to be read through select
read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])
登录后复制

当 select 返回时,说明在 read_sockets 上有可读的数据,这里又分为两种情况:

1、如果是主 socket(即服务器开始创建的 socket,一直处于监听状态)有数据可读,表示有新的连接请求可以接收,此时需要调用 accept 函数来接收新的客户端连接,并将其连接信息广播到其它客户端。
2、如果是其它 sockets(即与客户端已经建立连接的 sockets)有数据可读,那么表示客户端发送消息到服务器端,使用 recv 函数读消息,并将消息转发到其它所有连接的客户端。
上面两种情况到涉及到广播消息的过程,广播也就是将从某个 socket 获得的消息通过 CONNECTION_LIST 的每个 socket (除了自身和主 socket)一个个发送出去:

def broadcast_data (sock, message):
  #Do not send the message to master socket and the client who has send us the message
  for socket in CONNECTION_LIST:
    if socket != server_socket and socket != sock :
      try :
        socket.send(message)
      except :
        # broken socket connection may be, chat client pressed ctrl+c for example
        socket.close()
        CONNECTION_LIST.remove(socket)
登录后复制

如果发送失败,我们假设某个客户端已经断开了连接,关闭该 socket 病将其从连接列表中删除。

完整的聊天室服务器源代码如下:

# Tcp Chat server
 
import socket, select
 
#Function to broadcast chat messages to all connected clients
def broadcast_data (sock, message):
  #Do not send the message to master socket and the client who has send us the message
  for socket in CONNECTION_LIST:
    if socket != server_socket and socket != sock :
      try :
        socket.send(message)
      except :
        # broken socket connection may be, chat client pressed ctrl+c for example
        socket.close()
        CONNECTION_LIST.remove(socket)
 
if __name__ == "__main__":
   
  # List to keep track of socket descriptors
  CONNECTION_LIST = []
  RECV_BUFFER = 4096 # Advisable to keep it as an exponent of 2
  PORT = 5000
   
  server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  # this has no effect, why ?
  server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  server_socket.bind(("0.0.0.0", PORT))
  server_socket.listen(10)
 
  # Add server socket to the list of readable connections
  CONNECTION_LIST.append(server_socket)
 
  print "Chat server started on port " + str(PORT)
 
  while 1:
    # Get the list sockets which are ready to be read through select
    read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])
 
    for sock in read_sockets:
      #New connection
      if sock == server_socket:
        # Handle the case in which there is a new connection recieved through server_socket
        sockfd, addr = server_socket.accept()
        CONNECTION_LIST.append(sockfd)
        print "Client (%s, %s) connected" % addr
         
        broadcast_data(sockfd, "[%s:%s] entered room\n" % addr)
       
      #Some incoming message from a client
      else:
        # Data recieved from client, process it
        try:
          #In Windows, sometimes when a TCP program closes abruptly,
          # a "Connection reset by peer" exception will be thrown
          data = sock.recv(RECV_BUFFER)
          if data:
            broadcast_data(sock, "\r" + '<' + str(sock.getpeername()) + '> ' + data)        
         
        except:
          broadcast_data(sock, "Client (%s, %s) is offline" % addr)
          print "Client (%s, %s) is offline" % addr
          sock.close()
          CONNECTION_LIST.remove(sock)
          continue
   
  server_socket.close()
登录后复制

在控制台下运行该程序:

$ python chat_server.py 
Chat server started on port 5000
登录后复制

四、聊天室客户端
我们写一个客户端程序可以连接到上面的服务器,完成发送消息和接收消息的过程。主要做下面两件事:

  • 监听服务器是否有消息发送过来
  • 检查用户的输入,如果用户输入某条消息,需要发送到服务器

这里有两个 I/O 事件需要监听:连接到服务器的 socket 和标准输入,同样我们可以使用 select 来完成:

rlist = [sys.stdin, s]
     
# Get the list sockets which are readable
read_list, write_list, error_list = select.select(rlist , [], [])
登录后复制

那逻辑就很简单了,如果是 sys.stdin 有数据可读,表示用户从控制台输入数据并按下回车,那么就从标准输入读数据,并发送到服务器;如果是与服务器连接的 socket 有数据可读,表示服务器发送消息给该客户端,那么就从 socket 接收数据。加上一些提示信息及异常处理的完整客户端代码如下:

# telnet program example
import socket, select, string, sys
 
def prompt() :
  sys.stdout.write('<You> ')
  sys.stdout.flush()
 
#main function
if __name__ == "__main__":
   
  if(len(sys.argv) < 3) :
    print 'Usage : python telnet.py hostname port'
    sys.exit()
   
  host = sys.argv[1]
  port = int(sys.argv[2])
   
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
   
  # connect to remote host
  try :
    s.connect((host, port))
  except :
    print 'Unable to connect'
    sys.exit()
   
  print 'Connected to remote host. Start sending messages'
  prompt()
   
  while 1:
    rlist = [sys.stdin, s]
     
    # Get the list sockets which are readable
    read_list, write_list, error_list = select.select(rlist , [], [])
     
    for sock in read_list:
      #incoming message from remote server
      if sock == s:
        data = sock.recv(4096)
        if not data :
          print '\nDisconnected from chat server'
          sys.exit()
        else :
          #print data
          sys.stdout.write(data)
          prompt()
       
      #user entered a message
      else :
        msg = sys.stdin.readline()
        s.send(msg)
        prompt()
登录后复制

可以在多个终端下运行该代码:

$ python telnet.py localhost 5000
Connected to remote host. Start sending messages
<You> hello
<You> I am fine
<('127.0.0.1', 38378)> ok good
<You>
登录后复制

在另一个终端显示的信息:

<You> [127.0.0.1:39339] entered room
<('127.0.0.1', 39339)> hello
<('127.0.0.1', 39339)> I am fine
<You> ok good
登录后复制

总结
上面的代码注意两点:

1、聊天室客户端代码不能在 windows 下运行,因为代码使用 select 同时监听 socket 和输入流,在 Windows 下 select 函数是由 WinSock 库提供,不能处理不是由 WinSock 定义的文件描述符。
2、客户端代码还有个缺陷是,当某个客户端在输入消息但还未发送出去时,服务器也发送消息过来,这样会冲刷掉客户端正在输入的消息。这目前来看没办法解决的,唯一的解决方法是使用像 ncurses 终端库使用户输入和输出独立开,或者写一个 GUI 的程序。
那么本文通过一个聊天室的范例进一步学习了Python 下 Socket 编程。

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

PHP和Python:代码示例和比较 PHP和Python:代码示例和比较 Apr 15, 2025 am 12:07 AM

PHP和Python各有优劣,选择取决于项目需求和个人偏好。1.PHP适合快速开发和维护大型Web应用。2.Python在数据科学和机器学习领域占据主导地位。

Python vs. JavaScript:社区,图书馆和资源 Python vs. JavaScript:社区,图书馆和资源 Apr 15, 2025 am 12:16 AM

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

docker原理详解 docker原理详解 Apr 14, 2025 pm 11:57 PM

Docker利用Linux内核特性,提供高效、隔离的应用运行环境。其工作原理如下:1. 镜像作为只读模板,包含运行应用所需的一切;2. 联合文件系统(UnionFS)层叠多个文件系统,只存储差异部分,节省空间并加快速度;3. 守护进程管理镜像和容器,客户端用于交互;4. Namespaces和cgroups实现容器隔离和资源限制;5. 多种网络模式支持容器互联。理解这些核心概念,才能更好地利用Docker。

visual studio code 可以用于 python 吗 visual studio code 可以用于 python 吗 Apr 15, 2025 pm 08:18 PM

VS Code 可用于编写 Python,并提供许多功能,使其成为开发 Python 应用程序的理想工具。它允许用户:安装 Python 扩展,以获得代码补全、语法高亮和调试等功能。使用调试器逐步跟踪代码,查找和修复错误。集成 Git,进行版本控制。使用代码格式化工具,保持代码一致性。使用 Linting 工具,提前发现潜在问题。

vscode怎么在终端运行程序 vscode怎么在终端运行程序 Apr 15, 2025 pm 06:42 PM

在 VS Code 中,可以通过以下步骤在终端运行程序:准备代码和打开集成终端确保代码目录与终端工作目录一致根据编程语言选择运行命令(如 Python 的 python your_file_name.py)检查是否成功运行并解决错误利用调试器提升调试效率

vscode 扩展是否是恶意的 vscode 扩展是否是恶意的 Apr 15, 2025 pm 07:57 PM

VS Code 扩展存在恶意风险,例如隐藏恶意代码、利用漏洞、伪装成合法扩展。识别恶意扩展的方法包括:检查发布者、阅读评论、检查代码、谨慎安装。安全措施还包括:安全意识、良好习惯、定期更新和杀毒软件。

Python:自动化,脚本和任务管理 Python:自动化,脚本和任务管理 Apr 16, 2025 am 12:14 AM

Python在自动化、脚本编写和任务管理中表现出色。1)自动化:通过标准库如os、shutil实现文件备份。2)脚本编写:使用psutil库监控系统资源。3)任务管理:利用schedule库调度任务。Python的易用性和丰富库支持使其在这些领域中成为首选工具。

vs code 可以在 Windows 8 中运行吗 vs code 可以在 Windows 8 中运行吗 Apr 15, 2025 pm 07:24 PM

VS Code可以在Windows 8上运行,但体验可能不佳。首先确保系统已更新到最新补丁,然后下载与系统架构匹配的VS Code安装包,按照提示安装。安装后,注意某些扩展程序可能与Windows 8不兼容,需要寻找替代扩展或在虚拟机中使用更新的Windows系统。安装必要的扩展,检查是否正常工作。尽管VS Code在Windows 8上可行,但建议升级到更新的Windows系统以获得更好的开发体验和安全保障。

See all articles