经过python 构建一个简单的聊天服务器

构建一个 Python 聊天服务器python

一个简单的聊天服务器linux

如今您已经了解了 Python 中基本的网络 API;接下来能够在一个简单的应用程序中应用这些知识了。在本节中,将构建一个简单的聊天服务器。使用 Telnet,客户机能够链接到 Python 聊天服务器上,并在全球范围内相互进行通讯。提交到聊天服务器的消息能够由其余人进行查看(以及一些管理信息,例如客户机加入或离开聊天服务器)。这个模型如图 1 所示。windows


图 1. 聊天服务器使用 select 方法来支持任意多个客户机
聊天服务器使用 select 方法来支持任意多个客户机服务器

聊天服务器的一个重要需求是必须能够伸缩。服务器必须可以支持任意个流(TCP)客户机。网络

要支持任意个客户机,可使用 select 方法来异步地管理客户机的列表。不过也可使用服务器 socket 的 select 特性。select 的读事件决定了一个客户机什么时候有可读数据,并且它也能够用来判断什么时候有一个新客户机要链接服务器 socket 了。能够利用这种行为来简化服务器的开发。app

接下来,咱们将展现聊天服务器的 Python 源代码,并说明 Python 怎样帮助简化这种实现。dom

 

ChatServer 类异步

让咱们首先了解一下 Python 聊天服务器类和 __init__ 方法 —— 这是在建立新实例时须要调用的构造函数。socket

这个类由 4 个方法组成。run 方法用来启动服务器,而且容许客户机的链接。broadcast_string 和 accept_new_connection 方法在类内部使用,咱们稍后就会讨论。函数

__init__ 方法是一个特殊的方法,它们会在建立一个类的新实例时调用。注意全部的方法都使用一个 self 参数,这是对这个类实例自己的引用(与 C++ 中的 this 参数很是相似)。这个 self 参数是全部实例方法的一部分,此处用来访问实例变量。

__init__ 方法建立了 3 个实例变量。port 是服务器的端口号(传递给构造函数)。srvsock 是这个实例的 socket 对象,descriptors 是一个列表,包含了这个类中的每一个 socket 对象。能够在 select 方法中使用这个列表来肯定读事件的列表。

最后,清单 16 给出了 __init__ 方法的代码。在建立一个流 socket 以后,就能够启用 SO_REUSEADDR socket 选项了;这样若是须要,服务器就能够快速从新启动了。通配符地址被绑定到预先定义好的端口号上。而后调用 listen 方法,从而容许到达的链接接入。服务器 socket 被加入到descriptors 列表中(如今只有一个元素),可是全部的客户机 socket 均可以在到达时被加入到这个列表中(请参阅 accept_new_connection)。此时会在 stdout 上打印一条消息,说明这个服务器已经被启动了。


清单 16. ChatServer 类的 init 方法

import socket
import select

class ChatServer:

  def __init__( self, port ):
    self.port = port;

    self.srvsock = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) self.srvsock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) self.srvsock.bind( ("", port) ) self.srvsock.listen( 5 ) self.descriptors = [self.srvsock] print 'ChatServer started on port %s' % port def run( self ): ... def broadcast_string( self, str, omit_sock ): ... def accept_new_connection( self ): ... 

 

run 方法对于聊天服务器来讲是一个循环(请参阅清单 17)。在调用时,它还会进入一个无限循环,并在链接的客户机之间进行通讯。run 方法

服务器的核心是 select 方法。我将 descriptor 列表(其中包含了全部服务器的 socket)做为读事件的列表传递给 select (写事件和异常事件列表都为空)。当检测到读事件时,它会做为 sread 返回。(咱们忽略了 swrite 和 sexc 列表。)sread 列表包含要服务的 socket 对象。咱们循环遍历这个 sread 列表,检查每一个找到的 socket 对象。

在这个循环中首先检查 socket 对象是不是服务器。若是是,就说明一个新的客户机正在试图链接,这就要调用 accept_new_connection 方法。不然,就读取客户机的 socket。若是 recv 返回 NULL,那就关闭 socket。

在这种状况中,咱们构建了一条消息,并将其发送给全部已经链接的客户机,而后关闭 socket,并从 descriptor 列表中删除对应的对象。若是 recv 返回值不是 NULL,那么就说明已经有消息可用了,它被存储在 str 中。这条消息会使用 broadcast_string 发送给其余全部的客户机。


清单 17. 聊天服务器的 run 方法是这个聊天服务器的核心

def run( self ):

  while 1:

    # Await an event on a readable socket descriptor
    (sread, swrite, sexc) = select.select( self.descriptors, [], [] ) # Iterate through the tagged read descriptors for sock in sread: # Received a connect to the server (listening) socket if sock == self.srvsock: self.accept_new_connection() else: # Received something on a client socket str = sock.recv(100) # Check to see if the peer socket closed if str == '': host,port = sock.getpeername() str = 'Client left %s:%s\r\n' % (host, port) self.broadcast_string( str, sock ) sock.close self.descriptors.remove(sock) else: host,port = sock.getpeername() newstr = '[%s:%s] %s' % (host, port, str) self.broadcast_string( newstr, sock ) 



辅助方法

在这个聊天服务器中有两个辅助方法,提供了接收新客户机链接和将消息广播到已链接的客户机上的功能。

当在到达链接队列中检测到一个新的客户机时,就会调用 accept_new_connection 方法(请参阅清单 18)。accept 方法用来接收这个链接,它会返回一个新的 socket 对象,以及远程地址信息。咱们会当即将这个新的 socket 加入到 descriptors 列表中,而后向这个新的客户机输出一条消息欢迎它加入聊天。我建立了一个字符串来表示这个客户机已经链接了,使用 broadcast_string 方法来成组地广播这条消息(请参阅清单 19)。

注意,除了要广播的字符串以外,还要传递一个 socket 对象。缘由是咱们但愿有选择地忽略一些 socket,从而只接收特定的消息。例如,当一个客户机向一个组中发送一条消息时,这条消息应该发送给这个组中除了本身以外的全部人。当咱们生成状态消息来讲明有一个新的客户机正在加入该组时,这条消息也不该该发送给这个新客户机,而是应该发送给其余全部人。这种任务是在 broadcast_string 中使用 omit_sock 参数实现的。这个方法会遍历 descriptors 列表,并将这个字符串发送给那些不是服务器 socket 且不是 omit_sock 的 socket。


清单 18. 在聊天服务器上接收一个新客户机链接

def accept_new_connection( self ):

  newsock, (remhost, remport) = self.srvsock.accept() self.descriptors.append( newsock ) newsock.send("You're connected to the Python chatserver\r\n") str = 'Client joined %s:%s\r\n' % (remhost, remport) self.broadcast_string( str, newsock ) 



清单 19. 将一条消息在聊天组中广播

def broadcast_string( self, str, omit_sock ):

  for sock in self.descriptors:
    if sock != self.srvsock and sock != omit_sock:
      sock.send(str) print str, 

 

 

实例化一个新的 ChatServer

如今您已经看到了 Python 聊天服务器(这只使用了不到 50 行的代码),如今让咱们看一下如何在 Python 中实例化一个新的聊天服务器。

咱们经过建立一个新的 ChatServer 对象来启动一个服务器(传递要使用的端口号),而后调用 run 方法来启动服务器并容许接收全部到达的链接:


清单 20. 实例化一个新的聊天服务器

myServer = ChatServer( 2626 )
myServer.run()

 

如今,这个服务器已经在运行了,您能够从一个或多个客户机链接到这个服务器上。也能够将几个方法串接在一块儿来简化这个过程(若是须要简化的话):


清单 21. 串接几个方法

myServer = ChatServer( 2626 ).run()

 

这能够实现相同的结果。下面咱们将展现 ChatServer 类的用法。

 

 

展现 ChatServer

下面就是 ChatServer 的用法。咱们将展现 ChatServer 的输出结果(请参阅清单 22 )以及两个客户机之间的对话(请参阅清单 23 和 清单 24)。用户输入的文本以黑体形式表示。


清单 22. ChatServer 的输出

[plato]$ python pchatsrvr.py ChatServer started on port 2626 Client joined 127.0.0.1:37993 Client joined 127.0.0.1:37994 [127.0.0.1:37994] Hello, is anyone there? [127.0.0.1:37993] Yes, I'm here. [127.0.0.1:37993] Client left 127.0.0.1:37993 



清单 23. 聊天客户机 #1 的输出

[plato]$ telnet localhost 2626 Trying 127.0.0.1... Connected to localhost.localdomain (127.0.0.1). Escape character is '^]'. You're connected to the Python chatserver Client joined 127.0.0.1:37994 [127.0.0.1:37994] Hello, is anyone there? Yes, I'm here. ^] telnet> close Connection closed. [plato]$ 



清单 24. 聊天客户机 #2 的输出

[plato]$ telnet localhost 2626 Trying 127.0.0.1... Connected to localhost.localdomain (127.0.0.1). Escape character is '^]'. You're connected to the Python chatserver Hello, is anyone there? [127.0.0.1:37993] Yes, I'm here. [127.0.0.1:37993] Client left 127.0.0.1:37993 

 

正如您在清单 22 中看到的那样,全部客户机之间的对话都会显示到 stdout 上,包括客户机的链接和断开消息。

总结以下:

客户端使用linux 在发送消息的时候,在windows7上面显示的信息是正确的,而windows 客户端显示的信息是在linux客户端上面是一个字符一个字符显示的.这个以为跟windows7 使用的winsock有关?这个待验证

此文章为转载!

相关文章
相关标签/搜索