目录

5.1 模拟弱网状态

5.2 断线重连

5.3 优化玩家名称显示

5.4 完整代码下载地址


导致客户端和服务端断开连接的原因可能有以下三种:

  1. 服务端主动关闭连接。
  2. 客户端窗口关闭,玩家退出游戏。
  3. 客户端所在网络不给力(也叫做弱网),导致延迟或者丢包,严重时掉线。

前两点是正常的断线情况,我们主要来简单了解下针对第三种情况的应对措施,运行结果如下:

Python socket 断网重连 python断线重连_pygame

注:本节代码是在第三节代码的基础上添加的断线重连功能。

本项目结构显示如下(和第三节中的项目结构一样):

├── SimHei.ttf        # 字体文件
├── client.py         # 客户端代码
├── pics              # 图片文件夹
│   ├── 1.png
│   ├── 2.png
│   ├── 3.png
│   ├── 4.png
│   ├── 5.png
│   └── 6.png
├── player.py         # 包含Player类
└── server.py         # 服务端代码

在player.py中我们新导入了以下模块或库:

import uuid

5.1 模拟弱网状态

当客户端处在弱网状态下的时候,客户端界面会出现卡顿现象(人物移动卡顿,聊天延迟等),严重的话就掉线。我们在GameWindow类中添加一些代码来模拟这种情况:

# client.py
class GameWindow:
    def __init__(self):
        ...

        self.port = 5000
        self.host = "127.0.0.1"
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.is_connected = False              # 1

        self.connect()

    ...

    def connect(self):
        self.sock.connect((self.host, self.port))
        self.player.id = self.sock.recv(2048).decode("utf-8")
        self.is_connected = True              # 2

    def send_player_data(self):
        if not self.is_connected:             # 3
            print("和服务端断开连接")
            self.player.message = ""
            return pickle.dumps({})

        if randint(0, 1000) < 20:             # 4
            print("断线")
            self.sock.close()
            self.is_connected = False
            self.player.message = ""
            return pickle.dumps({})
        elif randint(0, 100) < 10:            # 5
            print("卡顿")
            time.sleep(1)

        data = {
            "id": self.player.id,
            "player": self.player
        }

        self.sock.send(pickle.dumps(data))
        self.player.message = ""
        return self.sock.recv(2048)

    ...

代码解释如下:

1. is_connected变量用来表示客户端和服务端是否连接成功。

2. 如果成功连接,就将is_connected值设为True。

3. 如果客户端和服务端之间的连接断开,就打印断开提示并返回一个空字典的pickle序列化值。

4. 模拟掉线状态,让客户端主动断开连接,并将is_connected值设置为False。

5. 使用time.sleep()函数模拟卡顿状态。

运行结果如下:

首先运行服务端,在运行第一个客户端前,我们先把上面新增的代码注释掉,表示该客户端的网络正常。在运行另一个客户端之前,再把注释取消掉,表示该客户端处在弱网状态下。

Python socket 断网重连 python断线重连_Python socket 断网重连_02

右边的客户端处于弱网状态下,笔者在控制人物时会明显感觉到卡顿。掉线之后,其他客户端玩家人物就会从游戏窗口上消失,而该弱网玩家也会在其他玩家的游戏窗口上消失。

5.2 断线重连

当客户端和服务端之间的连接断开后,客户端应该再次发送连接请求。

修改GameWindow类:

# client.py
class GameWindow:
    
    ...
    
    def connect(self):
        try:
            self.sock.connect((self.host, self.port))
        except Exception as e:                 # 3
            print(repr(e))
            print("连接失败,10秒后尝试重新连接。")
            time.sleep(10)
            self.reconnect()
        else:
            self.player.id = self.sock.recv(2048).decode("utf-8")
            self.is_connected = True

    def reconnect(self):                        # 1
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect()

    def send_player_data(self):
        if not self.is_connected:
            print("和服务端断开连接,尝试重新连接")
            self.reconnect()                    # 2
            self.player.message = ""
            return pickle.dumps({})

        ...

    ...

代码解释如下:

1. 在reconnect()函数中,我们重新实例化了一个socket对象并调用connect()方法向服务端发起连接。

2. 和服务端断开连接后,调用reconnect()方法重新连接。

3. 如果因为网络原因连接失败的话,就等10秒后再次尝试连接。

运行结果如下:

Python socket 断网重连 python断线重连_Python socket 断网重连_03

 从打印结果可以看出来,当玩家掉线后,客户端与服务端重新连接了,窗口更新还是很卡顿。如果没有个服务端重连,那人物移动是不会卡顿的。

5.3 优化玩家名称显示

如果大家运行了5.2小节中的代码,会发现断线重连后,玩家的id可能会发生改变。这是因为在重连后,服务端的conn对象是新的,而str(id(conn))的值也可能和断线前的不一样。

注:当然在游戏中应该显示玩家自定义的名称,如果是这样的话,那重连后不会有这个名称变换的问题,因为玩家名称都是根据玩家账号从数据库中获取的。

为了简单起见,我们就在Player类中添加一个name属性,这个就当做玩家自定义的名称。

修改Player类:

# player.py
class Player:
    def __init__(self, p_id, x, y,  pic_num, frame_width, frame_height):
        ...

        self.message = ""
        self.name = f"葬爱{uuid.uuid4()}"                           # 1

    ...

    def draw(self, win, pic):
        win.blit(pic, (self.x, self.y), self.frame_rect)

        font = pygame.font.Font("SimHei.ttf", 10)                   # 2
        name_text = font.render(self.name, True, (150, 150, 150))
        win.blit(name_text, (self.x + round(self.frame_width/2) - round(name_text.get_width()/2), self.y - name_text.get_height()))
        
    ...

代码解释如下:

1. name变量用来保存玩家名称,uuid用来防止名称相同的情况(当然uuid很长,拿来作为名称显示不合理,这里重点是不想让玩家名称重复)。

2. 之前是将id值显示到窗口上,现在改为显示name值。

运行结果如下:

Python socket 断网重连 python断线重连_pygame_04

 断线重连后,玩家名称不会改变。

5.4 完整代码下载地址

链接:https://pan.baidu.com/s/1aq3-0HWPbNwvCoFgPYpYEg  

密码:q15r