Python TCP 服务器,如何向客户端发送数据?

1 投票
2 回答
2372 浏览
提问于 2025-04-15 23:59

我有一个TCP服务器,它使用select调用来同时处理多个客户端的读取请求。还有一个客户端类(MClient),负责解码从客户端收到的数据包。

while(1)

    rlist, wlist, xlist = select( input_sockets, output_sockets, [] , 1)

    for insock in rlist: #any clients????

        if insock is server_socket:
            new_socket, addr = server_socket.accept()
            input_sockets.append(new_socket)    
            clients[insock.fileno()] = MClient(new_socket, addr, client_id) #dict of clients        
        else:
            data = insock.recv(512)
            if data:
                clients[insock.fileno()].ProcessPacket(data)
            else:
                input_sockets.remove(insock)
                del clients[insock.fileno()]

    #handle writing to sockets
    for outsock in wlist: 
        ....not done yet



   #do some other stuff not associated with the socket

我现在有点困惑,怎么把数据发送回客户端,也就是怎么写入'output_sockets'这个列表。我在想,能不能在我的MClient对象里设置一个标志,表示我有数据要发送回客户端。然后在我的服务器循环中检查每个客户端,看这个标志是否被设置,如果设置了,就把对应的socket放到output_list里。当这个socket可以写入时,我就调用相应客户端的写入函数。

不过,这种方法看起来不是很优雅,我想在主服务器循环中处理写入。请问我该怎么做呢?

谢谢!

2 个回答

0

你在第一段描述的实现方式听起来像是用 select 来搭建服务器的传统方法。

如果你想在“主服务器循环”中给客户端发送信息,我猜你的意思是代码看起来像这样:

request = socket.recv()
response = process_request(request)
socket.send(response)

那么你就需要为每个客户端开一个单独的线程。

1

这是我之前写的一个小程序,用来学习如何用一个线程处理多个连接。虽然这个程序并不完美,但它能很好地展示你想要做的事情。客户端对象负责管理连接的读写流,并确保服务器能把客户端的套接字放在正确的select()列表中。这个程序实现了一个简单的协议,消息以换行符结束。pumpXXXX()函数的作用就是阻塞地读写这些流,并管理读写缓冲区。只有当缓冲区中找到换行符时,完整的消息才会被处理。

import socket
import select

class Client(object):

    '''This object is created for each client connection.  It tracks
    what has been read, what has been written, and processes complete
    messages terminated by newlines.  It responds by returning the
    original message wrapped in square brackets and terminated by a
    newline. '''

    def __init__(self,who,sock,server):

        '''who - client address
        sock - client socket
        server - server object for this client
        '''

        self.who = who
        self.readbuf = ''
        self.writbuf = ''
        self.server = server
        self.sock = sock

    def close(self):

        '''Removes client from server's reader/writer queues and
        closes the connection.'''

        self.sock.close()
        if self.sock in self.server.readers:
            self.server.readers.remove(self.sock)
        if self.sock in self.server.writers:
            self.server.writers.remove(self.sock)
        self.server.data.pop(self.sock)

    def pumprecv(self):

        '''Server calls pumprecv() when something is readable from the
        client socket.  The data is appended to the client's read
        buffer.mro Complete messages (if any) are then removed from
        the buffer and processed.'''

        try:
            tmp = self.sock.recv(1000)
        except socket.error,e:
            print 'recv',e
            self.close()
        else:                
            if tmp:
                self.readbuf += tmp

                # Complete messages are processed
                while '\n' in self.readbuf:
                    msg,self.readbuf = self.readbuf.split('\n',1)
                    print self.who,msg
                    self.writbuf += '[' + msg + ']\n'
                    # New data to send.  Make sure client is in the
                    # server's writer queue.
                    if self.sock not in self.server.writers:
                        self.server.writers.append(self.sock)
            else:
                self.close()

    def pumpsend(self):
        try:
            # send some data.  tmp is #chars sent (may not be all in writbuf).
            tmp = self.sock.send(self.writbuf)
        except socket.error,e:
            print 'send:',e
            self.close()
        else:
            # Removed sent characters from writbuf.
            self.writbuf = self.writbuf[tmp:]
            # If writbuf is empty, remove socket from server's write queue.
            if not self.writbuf:
                self.server.writers.remove(self.sock)

class Server(object):
    def __init__(self,ip='127.0.0.1',port=9999):
        self.ssock = socket.socket()
        self.ssock.bind((ip,port))
        self.ssock.listen(5)
        self.readers = [self.ssock]
        self.data = {}
        self.writers = []
        self.quit = False

    def pumpaccept(self):

        '''Called when server socket is readable to accept a
        connection and create a Client object.'''

        csock,who = self.ssock.accept()
        print 'Connected %s:%d' % who
        self.readers.append(csock)
        self.data[csock] = Client(who,csock,self)

    def serve(self):
        while not self.quit or self.writers:
            readable,writable,other = select.select(self.readers,self.writers,[],1.0)
            # Operate on copies of the queues since the pumpXXX() commands can modify the lists.
            if self.ssock in readable[:]:
                self.pumpaccept()
                readable.remove(self.ssock)
            for reader in readable[:]:
                self.data[reader].pumprecv()
            for writer in writable[:]:
                self.data[writer].pumpsend()

            if not readable and not writable and not other:
                print '.',

if __name__ == '__main__':
    srv = Server()
    srv.serve()

我通过在一个控制台启动服务器,然后在其他控制台运行以下代码来测试多个连接。你可以创建多个连接,在不同的窗口间交替发送消息,甚至发送不完整的消息,看看服务器是如何响应的。

>>> from socket import *
>>> s=socket()
>>> s.connect(('localhost',9999))
>>> s.send('one\ntwo\nthree')
13
>>> s.send('\nfour\n')
6
>>> s.recv(1024)
'[one]\n[two\three]\n[four]\n'
>>> s.close()

输出结果应该大致如下:

. . . . . . . . . . . . . . . . . . . Connected 127.0.0.1:1514
. . . . . . . . . ('127.0.0.1', 1514) one
. . . . . . . ('127.0.0.1', 1514) two
. . . ('127.0.0.1', 1514) three
('127.0.0.1', 1514) four
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

撰写回答