構建一個 Python 聊天服務器
現在您已經了解了 Python 中基本的網絡 API;接下來可以在一個簡單的應用程序中應用這些知識了。在本節中,將構建一個簡單的聊天服務器。使用 Telnet,客戶機可以連接到 Python 聊天服務器上,並在全球范圍內相互進行通信。提交到聊天服務器的消息可以由其他人進行查看(以及一些管理信息,例如客戶機加入或離開聊天服務器)。這個模型如圖 1 所示。
圖 1. 聊天服務器使用 select 方法來支持任意多個客戶機
聊天服務器的一個重要需求是必須可以伸縮。服務器必須能夠支持任意個流(TCP)客戶機。
要支持任意個客戶機,可以使用 select
方法來異步地管理客戶機的列表。不過也可以使用服務器 socket 的 select
特性。select
的讀事件決定了一個客戶機何時有可讀數據,而且它也可以用來判斷何時有一個新客戶機要連接服務器 socket 了。可以利用這種行為來簡化服務器的開發。
接下來,我們將展示聊天服務器的 Python 源代碼,並說明 Python 怎樣幫助簡化這種實現。
讓我們首先了解一下 Python 聊天服務器類和 __init__
方法 —— 這是在創建新實例時需要調用的構造函數。
這個類由 4 個方法組成。run
方法用來啟動服務器,並且允許客戶機的連接。broadcast_string
和 accept_new_connection
方法在類內部使用,我們稍后就會討論。
__init__
方法是一個特殊的方法,它們會在創建一個類的新實例時調用。注意所有的方法都使用一個 self
參數,這是對這個類實例本身的引用(與 C++ 中的 this
參數非常類似)。這個 self
參數是所有實例方法的一部分,此處用來訪問實例變量。
__init__
方法創建了 3 個實例變量。port
是服務器的端口號(傳遞給構造函數)。srvsock
是這個實例的 socket 對象,descriptors
是一個列表,包含了這個類中的每個 socket
對象。可以在 select
方法中使用這個列表來確定讀事件的列表。
最后,清單 16 給出了 __init__
方法的代碼。在創建一個流 socket 之后,就可以啟用 SO_REUSEADDR
socket 選項了;這樣如果需要,服務器就可以快速重新啟動了。通配符地址被綁定到預先定義好的端口號上。然后調用 listen
方法,從而允許到達的連接接入。服務器 socket 被加入到descriptors
列表中(現在只有一個元素),但是所有的客戶機 socket 都可以在到達時被加入到這個列表中(請參閱 accept_new_connection
)。此時會在 stdout 上打印一條消息,說明這個服務器已經被啟動了。
|
run
方法對於聊天服務器來說是一個循環(請參閱清單 17)。在調用時,它還會進入一個無限循環,並在連接的客戶機之間進行通信。run 方法
服務器的核心是 select
方法。我將 descriptor
列表(其中包含了所有服務器的 socket)作為讀事件的列表傳遞給 select
(寫事件和異常事件列表都為空)。當檢測到讀事件時,它會作為 sread
返回。(我們忽略了 swrite
和 sexc
列表。)sread
列表包含要服務的 socket
對象。我們循環遍歷這個 sread
列表,檢查每個找到的 socket 對象。
在這個循環中首先檢查 socket
對象是否是服務器。如果是,就說明一個新的客戶機正在試圖連接,這就要調用 accept_new_connection
方法。否則,就讀取客戶機的 socket。如果 recv
返回 NULL,那就關閉 socket。
在這種情況中,我們構建了一條消息,並將其發送給所有已經連接的客戶機,然后關閉 socket,並從 descriptor
列表中刪除對應的對象。如果 recv
返回值不是 NULL,那么就說明已經有消息可用了,它被存儲在 str
中。這條消息會使用 broadcast_string
發送給其他所有的客戶機。
清單 17. 聊天服務器的 run 方法是這個聊天服務器的核心
|
輔助方法
在這個聊天服務器中有兩個輔助方法,提供了接收新客戶機連接和將消息廣播到已連接的客戶機上的功能。
當在到達連接隊列中檢測到一個新的客戶機時,就會調用 accept_new_connection
方法(請參閱清單 18)。accept
方法用來接收這個連接,它會返回一個新的 socket
對象,以及遠程地址信息。我們會立即將這個新的 socket 加入到 descriptors
列表中,然后向這個新的客戶機輸出一條消息歡迎它加入聊天。我創建了一個字符串來表示這個客戶機已經連接了,使用 broadcast_string
方法來成組地廣播這條消息(請參閱清單 19)。
注意,除了要廣播的字符串之外,還要傳遞一個 socket 對象。原因是我們希望有選擇地忽略一些 socket,從而只接收特定的消息。例如,當一個客戶機向一個組中發送一條消息時,這條消息應該發送給這個組中除了自己之外的所有人。當我們生成狀態消息來說明有一個新的客戶機正在加入該組時,這條消息也不應該發送給這個新客戶機,而是應該發送給其他所有人。這種任務是在 broadcast_string
中使用 omit_sock
參數實現的。這個方法會遍歷 descriptors
列表,並將這個字符串發送給那些不是服務器 socket 且不是 omit_sock
的 socket。
|
|
現在您已經看到了 Python 聊天服務器(這只使用了不到 50 行的代碼),現在讓我們看一下如何在 Python 中實例化一個新的聊天服務器。
我們通過創建一個新的 ChatServer
對象來啟動一個服務器(傳遞要使用的端口號),然后調用 run
方法來啟動服務器並允許接收所有到達的連接:
|
現在,這個服務器已經在運行了,您可以從一個或多個客戶機連接到這個服務器上。也可以將幾個方法串接在一起來簡化這個過程(如果需要簡化的話):
|
這可以實現相同的結果。下面我們將展示 ChatServer
類的用法。
下面就是 ChatServer
的用法。我們將展示 ChatServer
的輸出結果(請參閱清單 22 )以及兩個客戶機之間的對話(請參閱清單 23 和 清單 24)。用戶輸入的文本以黑體形式表示。
|
|
|
正如您在清單 22 中看到的那樣,所有客戶機之間的對話都會顯示到 stdout 上,包括客戶機的連接和斷開消息。
總結如下:
客戶端使用linux 在發送消息的時候,在windows7上面顯示的信息是正確的,而windows 客戶端顯示的信息是在linux客戶端上面是一個字符一個字符顯示的.這個覺得跟windows7 使用的winsock有關?這個待驗證
此文章為轉載!