本小案例实现了一个简单的多人聊天室,为实现一对多无干扰传输,采用多线程队列,实现的主要功能有:群聊相关内容、下载服务器端文件。
目录
一、整体功能实现流程图
二、服务器端
基本语法
(需要提前导入类:socket)
服务器端首要任务是创建TCP服务器,其步骤为:
- 使用socket创建一个套接字
- 使用bind绑定IP和端口
代码实现如下:
# 建立套接字对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 地址与端口
addr = ('127.0.0.1', 8080)
# 绑定端口
s.bind(addr)
# 涉及监听者个数
s.listen(5)
接下来,我们需要使用accept等待客户端的连接
# 等待接收客户端的连接请求
client, addr = s.accept()
到了最重要的一步,使用recv/send接收和发送数据
基本使用语法为:
#获取客户端请求的数据
data = conn.recv(1024)
#向客户端发送数据
conn.sendall(bytes('欢迎你加入我们的大家庭!\n'.encode('utf-8')))
线程
为实现服务器信息收、发同时进行,此程序使用了线程模块(treadin高级模块)来实现,在开启线程后,独立监听该用户的发来的信息,用于后续处理于转发。
具体使用方法:
# 导入线程模块
import threading
# 创建监听线程
# target:线程执行的函数名
# args:以元组的方式给执行任务传参(用户名,数)
r = threading.Thread(target=receive_msg, args=(client, num))
# 开启线程
r.start()
队列
在接收到用户端的信息后,我们需要将需要群发的信息进行转发,此处使用列表将信息进行非己转发(判断用户名)。
具体使用方法:
import queue
#获取用户端发来的信息
data = client.recv(1024).decode('utf-8')
#创建队列对象
public_message[client] = queue.Queue()
#为队列添加内容
public_message[client].put(data)
#后续获取内容
get_data = public_message[指定的client].get()
三、客户端
相对于服务器端,搭建用户端是较为简单的,创建客户端以后,建立连接成功后,便可以开始愉快的交流啦!
代码实现:
# 创建用户端
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 指定要连接的服务器端口及ip信息
server = ('127.0.0.1', 8080)
# 建立连接
c.connect(server)
四、整合代码
1、服务器端
server.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project :tdp
@File :server.py
@IDE :PyCharm
@Author :BLUE924
@Date :2023/6/16 12:26
"""
import queue
import socket
import threading
import time
num = -1
# 存放已连接的对象
clients = []
# 存放公共消息的容器
public_message = dict()
def file_deal(file_name):
# 定义函数用于处理用户索要下载的文件
try:
# 二进制方式读取
files = open(file_name, "rb")
mes = files.read()
except:
print("没有该文件")
else:
files.close()
return mes
# 接收消息
def receive_msg(client, num):
while True:
time.sleep(1)
try:
if client in clients:
data = client.recv(1024).decode('utf-8')
# 判断单纯的聊天消息
if data != '' and data[:4] != 'file':
print(data)
public_message[client] = queue.Queue()
public_message[client].put(data)
elif data[:4] == 'file':
#判断文件传输
print("用户需要文件传输")
file_name = data[7:]
print(data[7:])
# 调用函数处理用户下载的文件
mes = file_deal(file_name)
if mes:
# 如果文件不为空发送
msg = 'File_content : '.encode() + mes
client.send(msg)
print("传输完成!")
else:
if client in clients:
print("一位用户退出了")
clients.remove(client)
num = num - 1
except BaseException as error:
print('一位用户离开了群聊!')
if client in clients:
clients.remove(client)
num = num - 1
# 转发消息(非/阻塞)
def broadcast():
while True:
if len(clients) > 1:
public_message_clone = [i for i in public_message.keys()] # 解决字典迭代中操作报错的问题(循环迭代键)
for client in clients:
for i in public_message_clone:
if i != client and public_message[i].empty() == False:
data = public_message[i].get_nowait() # 注意:相当q.get(False) 非阻塞
if data != '':
client.send(bytes(data.encode('utf-8')))
def connect():
# 起群名
name = str(input("请为此群起群名:"))
# 建立套接字对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 地址与端口
addr = ('127.0.0.1', 8080)
# 绑定端口
s.bind(addr)
# 涉及监听者个数
s.listen(5)
# 建立转发信息的线程
t = threading.Thread(target=broadcast)
t.start()
print("群活跃中....")
# 目前人数
global num
num = 0
# 存放不同线程的套接字
while True:
try:
client, addr = s.accept() # 等待接收客户端的连接请求
if client in clients:
# 老用户忽略
pass
else:
print('目前群内有:%d 个成员' % int(num + 1))
client.send(bytes('欢迎你加入我们的大家庭!\n'.encode('utf-8')))
client.send(bytes(('目前群内有:%d 位志同道合的朋友' % int(num)).encode('utf-8')))
clients.append(client)
r = threading.Thread(target=receive_msg, args=(client, num))
r.start()
num += 1
except ConnectionResetError:
print('Someone left unexcept.')
if __name__ == '__main__':
connect()
2、客户端
client.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project :tdp
@File :clinet.py
@IDE :PyCharm
@Author :BLUE924
@Date :2023/6/16 13:08
"""
import socket
import threading
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server = ('127.0.0.1', 8080)
s.connect(server) # 建立连接
def receive():
"""
接收群发信息群
"""
while True:
data = s.recv(1024).decode('utf-8')
if data != '' and data[:15] != 'File_content : ':
print(data)
elif data[:15] == 'File_content : ':
content = data[15:]
# 创建一个空文件
new_file = open('下载的文件.txt', "wb")
# 解码并向文件内写入
new_file.write(content.encode())
print("下载成功!")
new_file.close()
def start():
# 起名
name = str(input("请位自己起一个群备注叭~:"))
t1 = threading.Thread(target=receive, daemon=True)
t1.start()
while (True):
string = input()
if string == 'exit':
msg = name + ' : 拜拜我要退出喽,期待下次见面!'
s.send(bytes(msg.encode('utf-8')))
s.close()
break
elif string == 'file':
file_name = input("输入文件名:")
msg = string + ' : ' + file_name
s.send(bytes(msg.encode('utf-8')))
else:
msg = name + ' : ' + string
s.send(bytes(msg.encode('utf-8')))
if __name__ == '__main__':
start()