python之使用socket和threading多線程,編寫全雙工多人聊天服務器

能在局域網內實現多人聊天

一、先講一講服務器端

其中有個get模塊是自己寫的
需要把目錄標記爲源

在網上看過不少,但是大多數都不能用。。或者很粗糙,沒有做什麼異常處理
我寫的這個經過了許多的測試,做了比較全面的異常處理,應該不會爆紅字

說一下服務器端的編寫思路:

主要思想
顯然對於多用戶來說,我們肯定不選擇用戶之間直接連接,否則用戶一多,就成了複雜的網狀結構,顯然即佔用端口,又對網絡資源有很高的消耗,效率低下。
所以我們編寫服務器作爲中轉站,這樣每個用戶都只需要和服務器建立一個連接,然後服務器接受每個用戶發來的消息,然後轉發給所有其他在線用戶,這樣便是星型結構,十分方便且利於管理。

在這裏插入圖片描述

  1. 我們要利用python的特點,用一個類來封裝,條理清晰,也方便以後的調用
  2. 有三個初始化屬性
    (1)一個socket套接字
    (2)一個元組,包含本機IP和指定的端口(不要急着綁定,防止持續佔用端口)
    (3)作爲服務器端,要統計在線用戶,顯然我們要用字典,而不是列表
  3. 如何與多用戶建立連接
    (1)開啓服務後,綁定端口(這裏不確定端口是否被佔用,所以加一個捕獲異常語句),綁定成功後開始監聽請求,一旦收到連接請求,就正式開始accept
    (2)因爲服務器要連接多個用戶,所以必須保持監聽狀態,這裏用while,每建立一個連接,就開啓一個線程(Thread模塊上場),專門爲這位用戶服務,然後這個線程就一邊玩去了。回頭繼續監聽接下來的連接請求。
    (3)每當有一個用戶連接,便把與當前用戶建立的socket放入存放用戶的字典,key是用戶名(客戶端在連接時會要求用戶輸入一個用戶名)然後統計當前用戶數量,並打印出來。
  4. 接下來說一說每一個線程
    (1)每個線程將會一直循環接收當前用戶發來的消息,並且在服務端顯示信息。
    (2)接受到消息後,用戶字典中的每一個socket都會立刻把此信息發送給自己對應的用戶,這就實現了服務器的轉發功能。
  5. 最後來考慮異常處理
    (1)某個用戶自行退出時,服務器端將會在recv函數這報錯ConnectionResetError
    所以我們要做的就是捕獲以上信息,然後服務器端將會向所有用戶轉發,“xxx用戶已退出聊天
    然後從用戶字典中移除該用戶的socket。
    (2)當服務器被強行關閉時,用戶端也會報錯,我們同樣捕獲後,告知用戶“服務器已關閉”,隨後客戶端將自動關閉

下面的代碼基本是按照上述思路一步步寫的,正是因爲我自己沒找到很好的教程文章,所以掉坑無數。。於是今日寫了這邊文章,希望能幫到大家

後文將會有客戶端代碼的講解

import socket
import get  # 自己寫的
import threading
import os


class ChatSever:

    def __init__(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.addr = (get.get_ip(), 10000)
        self.users = {}

    def start_sever(self):
    	try:
        	self.sock.bind(self.addr)
        except Exception as e:
        	print(e)
        self.sock.listen(5)
        print("服務器已開啓,等待連接...")
        print("在空白處輸入stop sever並回車,來關閉服務器")

        self.accept_cont()

    def accept_cont(self):
        while True:
            s, addr = self.sock.accept()
            self.users[addr] = s
            number = len(self.users)
            print("用戶{}連接成功!現在共有{}位用戶".format(addr, number))

            threading.Thread(target=self.recv_send, args=(s, addr)).start()
            
    def recv_send(self, sock, addr):
        while True:
            try:  # 測試後發現,當用戶率先選擇退出時,這邊就會報ConnectionResetError
                response = sock.recv(4096).decode("gbk")
                msg = "{}用戶{}發來消息:{}".format(get.get_time(), addr, response)

                for client in self.users.values():
                    client.send(msg.encode("gbk"))
            except ConnectionResetError:
                print("用戶{}已經退出聊天!".format(addr))
                self.users.pop(addr)
                break

    def close_sever(self):
        for client in self.users.values():
            client.close()
        self.sock.close()
        os._exit(0)


if __name__ == "__main__":
    sever = ChatSever()
    sever.start_sever()
    while True:
        cmd = input()
        if cmd == "stop sever":
            sever.close_sever()
        else:
            print("輸入命令無效,請重新輸入!")

get模塊如下

import socket
import datetime


def get_ip():
    """用來搞到IP"""
    host = socket.gethostname()
    ip = socket.gethostbyname(host)
    return ip


def get_time():
    """得到發送時間"""
    now = datetime.datetime.now()
    send_time = now.strftime("%Y-%m-%d %H:%M:%S")
    return send_time

二、客戶端:

如果你理解了服務端,那麼客戶端想必你早已胸有成竹
就是一個socket,建立連接後,扔兩個線程,一個發消息,一個接收消息。

import socket
import threading
import os
import get

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
addr = (get.get_ip(), 10000)
s.connect(addr)


def recv_msg():  #
    print("連接成功!現在可以接收消息!\n")
    while True:
        try:  # 測試發現,當服務器率先關閉時,這邊也會報ConnectionResetError
            response = s.recv(1024)
            print(response.decode("gbk"))
        except ConnectionResetError:
            print("服務器關閉,聊天已結束!")
            s.close()
            break
    os._exit(0)


def send_msg():
    print("連接成功!現在可以發送消息!\n")
    print("輸入消息按回車來發送")
    print("輸入esc來退出聊天")
    while True:
        msg = input()
        if msg == "esc":
            print("你退出了聊天")
            s.close()
            break
        s.send(msg.encode("gbk"))
    os._exit(0)


threads = [threading.Thread(target=recv_msg), threading.Thread(target=send_msg)]
for t in threads:
    t.start()

歡迎改進意見orz

發佈了31 篇原創文章 · 獲贊 96 · 訪問量 4308
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章