能在局域網內實現多人聊天
一、先講一講服務器端
其中有個get模塊是自己寫的
需要把目錄標記爲源
在網上看過不少,但是大多數都不能用。。或者很粗糙,沒有做什麼異常處理
我寫的這個經過了許多的測試,做了比較全面的異常處理,應該不會爆紅字
說一下服務器端的編寫思路:
主要思想:
顯然對於多用戶來說,我們肯定不選擇用戶之間直接連接,否則用戶一多,就成了複雜的網狀結構,顯然即佔用端口,又對網絡資源有很高的消耗,效率低下。
所以我們編寫服務器作爲中轉站,這樣每個用戶都只需要和服務器建立一個連接,然後服務器接受每個用戶發來的消息,然後轉發給所有其他在線用戶,這樣便是星型結構,十分方便且利於管理。
- 我們要利用python的特點,用一個類來封裝,條理清晰,也方便以後的調用
- 有三個初始化屬性:
(1)一個socket套接字
(2)一個元組,包含本機IP和指定的端口(不要急着綁定,防止持續佔用端口)
(3)作爲服務器端,要統計在線用戶,顯然我們要用字典,而不是列表 - 如何與多用戶建立連接:
(1)開啓服務後,綁定端口(這裏不確定端口是否被佔用,所以加一個捕獲異常語句),綁定成功後開始監聽請求,一旦收到連接請求,就正式開始accept
(2)因爲服務器要連接多個用戶,所以必須保持監聽狀態,這裏用while,每建立一個連接,就開啓一個線程(Thread模塊上場),專門爲這位用戶服務,然後這個線程就一邊玩去了。回頭繼續監聽接下來的連接請求。
(3)每當有一個用戶連接,便把與當前用戶建立的socket放入存放用戶的字典,key是用戶名(客戶端在連接時會要求用戶輸入一個用戶名)然後統計當前用戶數量,並打印出來。 - 接下來說一說每一個線程:
(1)每個線程將會一直循環接收當前用戶發來的消息,並且在服務端顯示信息。
(2)接受到消息後,用戶字典中的每一個socket都會立刻把此信息發送給自己對應的用戶,這就實現了服務器的轉發功能。 - 最後來考慮異常處理:
(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