pygame网络游戏_5_3:网络编程_我们的服务端

项目源码地址:https://github.com/zxf20180725/pygame-jxzj,求赞求星星~

1.前言

两个多月没更新了,这两个月经历了一些事情,让人挺难受的,不过人就是这样在这些经历中变得更加成熟。

好了,回到主题。这一章呢,我们需要动手封装一个非常非常简易的游戏服务端框架。前面我也说过,服务端的水很深,所以我只打算做一个基本能跑的服务端出来。这里就相当于带大家入个门吧,如果大家感兴趣的话,更全面的服务端知识还是得在其他地方系统的学习。

2.封装socket连接

阅读本章之前,本人强烈建议读者先看一遍完整的代码

在上一章中,服务端接收到新连接之后,就会把它存到全局变量g_conn_pool中。这次,我们把客户端socket连接封装成一个独立的类,把上一章的一些零散的操作都封装到一起。

class Connection:
    """
    连接类,每个socket连接都是一个connection
    """

    def __init__(self, socket, connections):
        self.socket = socket
        self.connections = connections
        self.data_handler()

    def data_handler(self):
        # 给每个连接创建一个独立的线程进行管理
        thread = Thread(target=self.recv_data)
        thread.setDaemon(True)
        thread.start()

    def recv_data(self):
        # 接收数据
        try:
            while True:
                bytes = self.socket.recv(2048)  # 我们这里只做一个简单的服务端框架,不去做分包处理。所以每个数据包不要大于2048
                if len(bytes) == 0:
                    self.socket.close()
                    # 删除连接
                    self.connections.remove(self)
                    break
                # 处理数据
                self.deal_data(bytes)
        except:
            self.connections.remove(self)
            Server.write_log('有用户接收数据异常,已强制下线,详细原因:\n' + traceback.format_exc())

    def deal_data(self, bytes):
        """
        处理客户端的数据,需要子类实现
        """
        raise NotImplementedError

Connection封装了创建线程和处理数据的功能,但是处理数据的功能并没有具体实现,需要子类实现deal_data函数。

这么做的目的是为了这个简单的框架具有通用型。因为每个游戏处理数据的方式不同,如果我们这个框架要给别人用的话(也不可能有别人用啦,哈哈,主要是要有这个意识),别人可能有他自己的一套数据处理方式,所以不能给写死了,交给使用者自己实现才更灵活。

上面说了Connection是需要子类继承的,那么下面我们就实现一个简单的子类Player。

class Player(Connection):
    """
    玩家类,我们的游戏中,每个连接都是一个Player对象
    """

    def __init__(self, *args):
        super().__init__(*args)
        self.login_state = False  # 登录状态
        self.nickname = None  # 昵称
        self.x = None  # 人物在地图上的坐标
        self.y = None

    def deal_data(self, bytes):
        """
        处理服务端发送的数据
        :param bytes:
        :return:
        """
        print('\n客户端消息:',bytes.decode('utf8'))

在构造方法中,我们调用了父类的构造方法,并且把外部的参数传给了父类的构造方法,*args就是父类的socket和connections参数。Player重写了父类的deal_data方法,功能很简单,就是输出一下客户端发来的消息。

3.服务端入口

在上面,我们封装了Connection类和Player类,现在我们就要用上它们。

现在整个程序还差一个启动入口,我们现在编写一个Server类,做为程序的入口。

class Server:
    """
    服务端主类
    """
    __user_cls = None

    @staticmethod
    def write_log(msg):
        cur_time = datetime.datetime.now()
        s = "[" + str(cur_time) + "]" + msg
        print(s)

    def __init__(self, ip, port):
        self.connections = []  # 所有客户端连接
        self.write_log('服务器启动中,请稍候...')
        try:
            self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 监听者,用于接收新的socket连接
            self.listener.bind((ip, port))  # 绑定ip、端口
            self.listener.listen(5)  # 最大等待数
        except:
            self.write_log('服务器启动失败,请检查ip端口是否被占用。详细原因:\n' + traceback.format_exc())

        if self.__user_cls is None:
            self.write_log('服务器启动失败,未注册用户自定义类')
            return

        self.write_log('服务器启动成功:{}:{}'.format(ip,port))
        while True:
            client, _ = self.listener.accept()  # 阻塞,等待客户端连接
            user = self.__user_cls(client, self.connections)
            self.connections.append(user)
            self.write_log('有新连接进入,当前连接数:{}'.format(len(self.connections)))

    @classmethod
    def register_cls(cls, sub_cls):
        """
        注册玩家的自定义类
        """
        if not issubclass(sub_cls, Connection):
            cls.write_log('注册用户自定义类失败,类型不匹配')
            return

        cls.__user_cls = sub_cls

write_log是对print的一个封装,就不多说了。

Server类有一个属性__user_cls,这个就保存着用户自己实现的Connection的子类,我们专门写了一个register_cls方法,用来给__user_cls赋值。这个方法可以直接当装饰器使用,就想这样:

@Server.register_cls
class Player(Connection):
    """
    玩家类,我们的游戏中,每个连接都是一个Player对象
    """

    # 具体代码省略

这样,我们的框架就知道了用户自定义的类是Player。

构造方法中,self.connections是用来保存所有客户端连接的。其中有一个while死循环,这是用来一直接收新的连接。

            user = self.__user_cls(client, self.connections)
            self.connections.append(user)

这两句是用来创建Player对象,并且保存到connections中的。

4.运行

我们先写一个非常简单的客户端,来看看我们服务端框架的效果

客户端代码:

import socket

s = socket.socket()
s.connect(('127.0.0.1', 6666))
s.send("你好呀,我是客户端".encode('utf8'))
input("")

先运行服务端,再运行客户端(可以多运行几个客户端)

运行效果:

发布了42 篇原创文章 · 获赞 112 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_39687901/article/details/103038300