如果在TCP通信中丢失数据包,如何将客户端重新连接到服务器?

时间:2019-11-28 05:14:41

标签: python python-3.x tcp

我正在使用两台不同的计算机(两个不同的IP)在服务器和客户端之间建立通信,但是当我在将数据包发送到服务器的过程中丢失数据包时,我的客户端无法重新连接到服务器,因此它停止发送数据包。

我试图使用另一个sock.connect(server_address),但即使这样也无法正常工作。

这是我的代码(在堆栈溢出中有一个有用的程序员的结构提示):

import socket
import threading
import time

class Server(threading.Thread):

    def run(self) -> None:
        MY_IP = "10.0.0.113" # Other computer running this class
        PORT_NUMBER = 13000


        # socket TCP/IP
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


        server_address = (MY_IP, PORT_NUMBER)
        print('Server starting up on {} port {}'.format(*server_address))
        sock.bind(server_address)

        sock.listen(1)

        while True:

            print('Waiting a connection')
            connection, client_address = sock.accept()
            try:
                print('Connection from ', client_address)

                while True:
                    data = connection.recv(16)
                    #print('received {!r}'.format(data))
                    if data:
                        #print('sending data back to the client')
                        connection.sendall(data)
                    else:
                        print('no data came from ', client_address)
                        break

            finally:
                connection.close()

class Client(threading.Thread):

    def run(self) -> None:
        #MY_IP = "10.0.0.112" won't be used because it will send to the server IP.
        OTHER_IP = "10.0.0.113"
        PORT_NUMBER = 13000

        g_windowTime = 0

        # socket TCP/IP
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        server_address = (OTHER_IP, PORT_NUMBER)
        sock.connect(server_address)

        def execute():
            global g_windowTime
            g_windowTime = time.time()
            sizeWindow = 1
            id_packet = "1"
            packets_resend = []

            while True:
                packets_sent = []

                # Send data
                sizeWindow, packets_sent, id_packet = send_packets(sizeWindow, packets_sent, packets_resend, id_packet)
                print(f"Send packet returned: {sizeWindow}")

                # Waits for the amount
                amount_received = 0
                amount_expected = len(packets_sent)

                while amount_received < amount_expected:
                    try:
                        sock.settimeout(5.0)
                        data = sock.recv(16)
                        amount_received += len(data)
                    except:
                        print("The packet needs to be resend")
                print(f"execute: {sizeWindow} -> {sizeWindow*2}")
                sizeWindow = sizeWindow * 2
                currentTime = (time.time() - g_windowTime) * 1000
                print(f'{str(round(currentTime, 2)).replace(".", ",")}; {int(sizeWindow)}')
                if currentTime > 10000:
                    exit()


        def send_packets(sizeWindow, packets_sent, packets_resend, id_packet):
            global g_windowTime
            i = 0
            j = 0
            timer = 0

            while i < sizeWindow:

                if packets_resend == []:
                    packet = id_packet.encode('utf-8')
                    id_packet = str(int(id_packet) + 1)
                elif packets_resend != []:
                    packet = packets_resend.pop(0)

                if packet not in packets_sent:
                    packets_sent.append(packet)

                # Send the packet
                try:
                    sock.sendall(packet)
                except:
                    # Here is the problem, it cannot connect to the server if it looses a packet
                connected = False
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                while not connected:
                    try:
                        print("Problem with sendall")
                        connect()
                        connected = True
                        print('re-connection successful')
                    except socket.error:
                        time.sleep(2)

                # Timer
                if (i == 0):
                    timer = time.time()
                elif (i > 0) and (time.time() > (timer + 0.01)):
                    if sizeWindow > 1:
                        j = i + 1
                        while j < sizeWindow:
                            packet = id_packet.encode('utf-8')
                            id_packet = str(int(id_packet) + 1)
                            packets_resend.append(packet)
                            j += 1
                        print(f"send packets: {sizeWindow} -> {sizeWindow/2}")
                        sizeWindow = sizeWindow / 2
                        currentTime = (time.time() - g_windowTime) * 1000
                        print(f'{str(round(currentTime, 2)).replace(".", ",")}; {int(sizeWindow)}')

                        send_packets(sizeWindow, packets_sent, packets_resend, id_packet)

                i += 1

            return sizeWindow, packets_sent, id_packet

        def connect():
            sock.connect(server_address)

        execute()

        sock.close()

if __name__ == '__main__':
    #server = Server() needs to be executed in another computer to see the problem, in the same computer it works fine
    #server.start()
    time.sleep(1)

    client = Client()
    client.start()

当客户端丢失数据包时,我无法将其重新连接到服务器,是否有另一种方法可以重新连接它们而不丢失连接?

如果有人可以帮助我,我真的可以伸出援手...

谢谢。

1 个答案:

答案 0 :(得分:2)

如@ user207421所述,由于您仍在使用TCP连接,因此通常不必自己处理传输错误,因为无错误的数据传输是针对UDP的key features of TCP之一。

此外,我建议使用socketserver Python模块来构建某种客户端/服务器应用程序。它将您从许多用于连接建立和管理以及线程管理的样板代码中节省下来。

最后,只有当服务器处于sock.accept()方法中时,才能连接到服务器。我担心当您断开连接时,服务器将无法使用connection.recv()connection.sendall()函数中的任何一个,从而不接受任何新的连接;这就是为什么我们经常将服务器工作负载委派给其他线程或进程;来自socketserver文档:

  

这四个类同步处理请求 ;必须先完成每个请求,然后才能开始下一个请求。如果每个请求都需要很长时间才能完成,这是不合适的,因为它需要大量的计算,或者因为它返回了很多客户端处理缓慢的数据。解决方案是创建一个单独的进程或线程来处理每个请求。 ForkingMixInThreadingMixIn混合类可用于支持异步行为。

因此,总结一下我的答案: 您正在经历您不打算遇到的行为。这可能是误解的症状。因此,请使用可用的工具来解决此问题。