一个聊天室,由两个部分组成。服务端和客户端。服务端接收客户端发来的消息,并将接收到的消息发送给其他客户端。客户端负责发送消息到服务端,并接收来自服务端发送的来自其他客户端的消息。
示例图(服务端和客户端):
这是属于一个群聊的聊天室,服务端会把每一条消息发送给所有客户端。当有人连接服务端或退出聊天时,所有在线的客户端都会收到提醒。
服务端源码:
# encoding: utf-8
import socket
import threading
import time
import sys
import json
import configparser as conf
import redis
# redis.StrictRedis
r = redis.Redis(host='127.0.0.1', port=6379, db=1, decode_responses=True)
cf = conf.ConfigParser()
cf.read('Server_Socket.ini')
socket_host = cf.get("socket", "host")
socket_port = cf.get("socket", "port")
clients = set()
clients_lock = threading.Lock()
global data_type
def socket_service():
global data_type
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((socket_host, int(socket_port)))
s.listen(100)
except socket.error as msg:
print(msg)
sys.exit(1)
print('等待客户端连接')
while True:
conn, addr = s.accept()
try:
data_type = json.loads(conn.recv(1024).decode("utf8"))
except Exception as e:
print(e)
WelcomeClient = {"UserSum": 0, "text": "欢迎加入慕公子的聊天室!", "type": "client"}
conn.send(bytes(json.dumps(WelcomeClient), "UTF-8"))
if r.exists(data_type['Name']) == 0:
r.set(str(addr), data_type['Name'])
r.set(data_type['Name'], str(addr))
if data_type['type'] == 'server':
with clients_lock:
clients.add(conn)
for c in clients:
if data_type['type'] == "server":
try:
sums = len(clients)
dataClient = {"UserSum": sums, "text": data_type['text'], "type": "change"}
c.send(bytes(json.dumps(dataClient), "UTF-8"))
except ConnectionResetError as e:
print(e)
t = threading.Thread(target=deal_data, args=(conn, addr))
t.start()
elif r.exists(data_type['Name']) == 1:
sums = len(clients)
data1Client = {"UserSum": sums, "text": "名字重复,换一个名字吧。", "type": "client"}
conn.send(bytes(json.dumps(data1Client), "UTF-8"))
def deal_data(conn, addr):
print(f'有新的客户端连接:{conn}-{addr}')
try:
while True:
data = conn.recv(1024).decode("utf8")
msg = json.loads(data)
print(f"{addr}-{msg['Name']} 发送的消息:{msg['text']}")
time.sleep(0.3)
if data == 'exit' or not data:
print(f'{addr} connection close')
conn.send(bytes('Connection closed!'), 'UTF-8')
break
with clients_lock:
for c in clients:
if msg['type'] == "client":
sums = len(clients)
dataClient = {"UserSum": sums, "text": f"{msg['Name']} 发送的消息:{msg['text']}", "type": "client"}
text = bytes(json.dumps(dataClient), "UTF-8")
c.send(text)
except ConnectionResetError as e:
clients.remove(conn)
UserName = r.get(str(addr))
clSums = len(clients)
with clients_lock:
for c in clients:
changeClient = {"UserSum": clSums, "text": f"{UserName},退出聊天。", "type": "change"}
text = bytes(json.dumps(changeClient), "UTF-8")
c.send(text)
print(f"{e}--{addr}--{UserName},退出聊天。")
r.delete(str(addr))
r.delete(UserName)
except json.decoder.JSONDecodeError as e:
print(e)
conn.close()
if __name__ == '__main__':
socket_service()
客户端代码:
import socket
import tkinter as tk
import tkinter.font as tkFont
from tkinter.filedialog import askdirectory
import tkinter.messagebox
import tkinter.filedialog
from tkinter import ttk
import threading as thr
from tkinter import scrolledtext
import os
import json
import configparser as conf
window = tk.Tk()
window.title('MuChat')
# 获取屏幕宽高
sw = window.winfo_screenwidth()
sh = window.winfo_screenheight()
# 设置屏幕的宽和高
ww = 370
wh = 470
x = (sw - ww) / 2
y = (sh - wh) / 2
# 根据屏幕宽高来让程序居中
window.geometry("%dx%d+%d+%d" % (ww, wh, x, y))
fontStyle = tkFont.Font(family="Lucida Grande", size=15)
tk.Label(window, text="MuChat", font=fontStyle).pack()
# 设置frame容器
frame = tk.Frame(window, padx=3, pady=3)
frame.pack()
frame_left = tk.Frame(frame)
frame_right = tk.Frame(frame)
frame_left.pack(side='left')
frame_right.pack(side='right')
global s
def SumChange(sumCa):
global sumLa
sumLa = tk.Label(window, text=f"当前在线{int(sumCa)}人", font=fontStyle)
sumLa.pack()
def socket_client():
cf = conf.ConfigParser()
cf.read('Client_Socket.ini')
socket_host = cf.get("socket", "host")
socket_port = cf.get("socket", "port")
global s
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((socket_host, int(socket_port)))
print(s)
data_json = {"Name": yourName.get(), "text": f"欢迎{yourName.get()}的加入!", "type": "server"}
data = json.dumps(data_json).encode()
s.send(data)
except socket.error as msg:
print(msg)
while True:
message = s.recv(1024).decode('utf-8') # ConnectionAbortedError
# print(message)
jsonData = json.loads(message)
print(jsonData)
if jsonData:
scr.insert(tk.END, f"{jsonData['text']}\n\n")
scr.see(tk.END)
scr.update()
if jsonData['type'] == 'change':
sumLa.pack_forget()
SumChange(jsonData['UserSum'])
def thrSocket():
en.focus_set()
soc = thr.Thread(target=socket_client, daemon=True, args=())
soc.start()
yourName = tk.StringVar()
tk.Entry(frame_left, textvariable=yourName, width=30).pack(padx=5, pady=10)
tk.Button(frame_right, text='连接服务端', font=fontStyle, width=18, height=1, command=lambda: thrSocket()).pack(padx=3,
pady=3)
def send_socket_client():
global s
if (not yourMsg.get()) | (yourMsg.get() == ' '):
tkinter.messagebox.showinfo(title='提示', message="你还未输入内容!")
return
data_json = {"Name": yourName.get(), "text": yourMsg.get(), "type": "client"}
yourMsg.set('')
data = json.dumps(data_json).encode()
s.send(data)
def SendSocket():
soc = thr.Thread(target=send_socket_client, daemon=True, args=())
soc.start()
def socket_cloase():
s.close()
def ReSend(e):
SendSocket()
def socket_ini():
os.startfile("Client_Socket.ini")
yourMsg = tk.StringVar()
en = tk.Entry(frame_left, textvariable=yourMsg, width=30)
en.pack(padx=5, pady=10)
en.bind('', ReSend)
tk.Button(frame_right, text='发送消息', font=fontStyle, width=18, height=1, command=lambda: SendSocket()).pack(padx=3,
pady=3)
fontStyleRadio = tkFont.Font(family="Lucida Grande", size=13)
tk.Button(frame_left, text='编辑配置文件', font=fontStyle, width=18, height=1, command=lambda: socket_ini()).pack(padx=3,
pady=3)
tk.Button(frame_right, text='关闭连接', font=fontStyle, width=18, height=1, command=lambda: socket_cloase()).pack(padx=3,
pady=3)
scr = scrolledtext.ScrolledText(window, width=30, height=13, font=("宋体", 13))
scr.pack()
sumLa = tk.Label(window, text=f"当前在线0人", font=fontStyle)
sumLa.pack()
window.mainloop()