客户端使用 python 套接字接收两条单独的消息

Client receiving two separate message as one with python socket

我将服务器class定义如下(redacted):

class server:
    def __init__( self, ip = "", port = 0 ):
        self.SetAddress( ip, port )
        self.__players = []

    def __SetSocket( self, blocking = 0, queue = 4 ):
        self.__listener = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        self.__listener.bind( self.GetAddress() )
        self.__listener.setblocking( blocking )
        self.__listener.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
        self.__listener.listen( queue )
        self.__listener.settimeout( 5 )
        self.__read, self.__write, self.__error = [ self.__listener ], [], []

    def __AddClient( self, source ):
        c, a = source.accept()
        c.settimeout( 5 )
        self.__read.append( c )
        send( c, "Welcome!" )
        print a, "Connection established"
        return

    def __AddPlayer( self, source, nick ):
        if len( self.__players ) == 4:
            send( source, ('Error', "4 players already connected.") )
            self.__read.remove( source )
            return
        self.__players.append( nick )
        send( source, ('ID', self.__players.index(nick)) )

    def __RemovePlayer( self, source, gamer_id ):
        self.__players.pop( gamer_id )
        self.__read.remove( source )
        source.close()

    def __Connect( self ):
        joining = True
        while joining:
            r, w, x = select( self.__read, self.__write, self.__error, 0 )
            for s in r:
                if s is self.__listener:
                    self.__AddClient( s )
                else:
                    data = receive( s )
                    if data:
                        print data, s.getpeername()
                        if self.__MaintainPlayers( s, data ):
                            pass
            if len( self.__players ) == 4:
                joining = False
        return

    def __MaintainPlayers( self, source, data ):
        if data[0] == "Nick":
            self.__AddPlayer( source, data[1] )
            return True
        elif data[0] == "Quit":
            self.__RemovePlayer( source, data[1] )
            return True
        return False

    def run( self ):
        self.__SetSocket( 1, 4 )
        print "Waiting for players."
        self.__Connect()

其中,发送和接收函数如下:

def send( channel, message ):
    try:
        channel.send( json.dumps(message) )
        return True
    except OSError as e:
        print e
        return False

def receive( channel, packet_size = 64 ):
    try:
        data = channel.recv( int(packet_size) )
        if not data:
            return None
        print data
        return json.loads( data.strip() )
    except OSError as e:
        print e
        return False

client class 非常简单(编辑):

class client:
    def __init__( self, name, srvIP, srvPort ):
        ip = socket.gethostbyname( socket.gethostname() )
        self.__server_address = self.__server_ip, self.__server_port = srvIP, srvPort
        self.__ID = None
        self.__nick = name
        self.__SetListener()

    def __SetListener( self ):
        self.__listener = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        self.__listener.settimeout( 5 )
        try:
            self.__listener.connect( self.__server_address )
        except Exception, e:
            print "Unable to connect", e
            raise e
        print "Connected to %s:%d." % self.__server_address
        send( self.__listener, ("Nick", self.__nick) )

    def run( self ):
        self.__read, self.__write, self.__error = [ self.__listener ], [], []
        while True:
            r, w, x = select( self.__read, self.__write, self.__error, 0 )
            for f in r:
                if f is self.__listener:
                    data = receive( f )
                    if data:
                        print data
                        if data[0] == "ID":
                            self.__ID = int( data[1] )
                        # More conditions

发生的情况是,我的客户同时反对 receive WelcomeID 消息。抛出如下异常:

$ client.py
Connected to 10.109.1.92:7777.
"Welcome!"["ID", 0]
Traceback (most recent call last):
  File "%PATH%\client.py", line 115, in <module>
    c.run()
  File "%PATH%\client.py", line 86, in run
    data = receive( f )
  File "%PATH%\connect.py", line 17, in receive
    return loads( data.strip() )
  File "%PYTHON%\lib\json\__init__.py", line 338, in loads
    return _default_decoder.decode(s)
  File "%PYTHON%\lib\json\decoder.py", line 369, in decode
    raise ValueError(errmsg("Extra data", s, end, len(s)))
ValueError: Extra data: line 1 column 28 - line 1 column 37 (char 27 - 36)

也就是说,客户端收到以下作为单个字符串:

"Welcome!"["ID", 0]

这会在 json.loads 中引发错误。

是否有某种方法可以在消息之间引入任何类型的延迟?

您需要在发送邮件时添加邮件的大小,以便您在收到邮件时只能return 该邮件,并且知道您拥有整个邮件。套接字模块中没有任何东西可以执行此操作,因为它们只是实现了一个低级管道。

当您发送消息时,在其前面加上消息的大小:

def send( channel, message ):
  try:
    msg = json.dumps(message)
    channel.send(struct.pack("i", len(msg)) + msg)
    return True
except OSError as e:
    print e
    return False

当您收到消息时,首先检索大小,然后重复调用recv,直到您拥有整个消息。

def receive( channel ):
    try:
        size = struct.unpack("i", channel.recv(struct.calcsize("i")))[0]
        data = ""
        while len(data) < size:
            msg = channel.recv(size - len(data))
            if not msg:
                return None
            data += msg.decode('utf-8')
        print data
        return json.loads( data.strip() )
    except OSError as e:
        print e
        return False

或者,如果您可以保证您的消息不会包含特定字符(例如空字节),您可以将其附加到您的字符串,然后在服务器端,将字符串拆分为空字符:

客户:

socket.sendall(json_string + '[=10=]')

服务器:

recv_buffer = ""
while True:
    data = connection.recv(128)
    recv_buffer = recv_buffer + data
    strings = recv_buffer.split('[=11=]')
    for s in strings[:-1]:
        print("Received: %s" % s)
    recv_buffer = strings[-1]