使用 Python 进行 socket 编程(5)

ChatServer 类

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

这个类由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 方法

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

服务器的核心是 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 类的用法。 

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwgggx.html