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("")

先運行服務端,再運行客戶端(可以多運行幾個客戶端)

運行效果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章