1. 群聊聊天室
功能: 类似QQ群功能
- 有人进入聊天室树要输入姓名,姓名不能重复
- 有人进入聊天室时,其他人会收到通知: xxx 进入聊天室
- 一个人发消息,其他人会收到: xxx: xxxxxx
- 有人退出聊天室,则其他人也会收到通知:xxx 推出了聊天室
- 扩展功能: 服务器可以向所有用户发送公告:管理员消息:xxxxx
2. 技术点的确定
- 数据如何流转
- 转发: 客户端>服务端>其他客户端
- 网络模型如何构建
- 构建UDP数据传输
- 用户信息在哪维护怎么维护
- 服务端:{name:address}或者[(name,address),]
- 随意收发消息如何避免阻塞
- 收发分别使用不同的进程执行
3. 结构设置注意事项
- 采用什么封装结构: 函数
- 编写一个功能测试一个功能
- 注意注释和结构的设计
4.分析功能模块
- 网络搭建
- 进入聊天室
- 客户端:
- 输入姓名
- 将请求发送给服务器
- 接收结果
- 允许则可以聊天,不允许则重新输入姓名
- 服务单:
- 接收请求
- 判断 是否存在用户名
- 如果允许进入则将用于存储,通知其他客户端
- 如果不允许则结束
- 将结果通知客户端
- 客户端:
- 聊天
- 客户端:
- 创建新的进程
- 一个进程循环发送消息
- 一个进程循环接收消息
- 服务端
- 接收请求
- 判断请求类型
- 将消息转发给其他人
- 客户端:
- 退出聊天室
- 客户端
- 输入quit或者ctrl-c退出
- 将请求发送给服务端
- 结束进程
- 服务端
- 接收请求
- 将退出消息告知其他人
- 给该用户发送exit
- 删除用户
- 客户端
- 管理员消息
- 没有做
5.图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pqcx3NOL-1639530767665)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214192723554.png)]
6.通信协议设置
- 进入聊天室: L
- 聊天: C
- 退出: Q
- 服务器反馈: ok成功 其他表示失败
- 客户端收到:exit 退出接收进程
- 客户端输入:quit 直接退出
7. 服务端代码
"""
服务器端
"""
from socket import *
import time
# 服务器地址
ADDR = ('0.0.0.0', 18888)
OK = 'OK'
list_client = []
# 判断是否存在列表里面 如果不存在就加入列表
def is_exeit(name):
global list_client
for item, addr in list_client:
if item == name:
return False
return True
def add_to_list(flag, name, addr):
if flag:
global list_client
list_client.append((name, addr))
# 返回结果
def send_result(flag, addr, s):
s.sendto(OK.encode(), addr) if flag else s.sendto("不允许".encode(), addr)
# 通知其他人
def send_all(flag, msg, s):
if flag:
global list_client
for item, addr in list_client:
s.sendto(msg.encode(), addr)
def do_quit(s, name):
global list_client
r_name, r_addr = ('', '')
for item, addr in list_client:
if item == name:
r_name, r_addr = item, addr
s.sendto('exit'.encode(), addr)
else:
s.sendto((name + ' 退出了').encode(), addr)
list_client.remove((r_name, r_addr))
# 处理请求
def do_chat(s, name, msg):
global list_client
for item, addr in list_client:
t_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
s.sendto(('%s %s:%s' % (t_str, name, msg)).encode(), addr)
# 登录
def do_login(addr, name, s):
# 判断 是否 允许进入 如果允许进入
flag = is_exeit(name)
# 通知其他人
send_all(flag, '欢迎"%s"进入聊天室' % name, s)
# 存储结果
add_to_list(flag, name, addr)
# 返回结果
send_result(flag, addr, s)
def do_request(s):
global list_client
# 循环收发消息
while True:
data, addr = s.recvfrom(1024)
arr = data.decode().split(' ')
state, name = arr[0], arr[1]
if state == 'L':
# 登录
do_login(addr, name, s)
print(list_client)
elif state == 'C':
# 群发
msg = ' '.join(arr[2:])
do_chat(s, name, msg)
elif state == 'Q':
do_quit(s, name)
print(list_client)
# 搭建网络
if __name__ == '__main__':
# udp服务端
s = socket(AF_INET, SOCK_DGRAM)
s.bind(ADDR)
# 请求处理函数
do_request(s)
print('退出程序')
8.客户端代码
"""
客户端
发送请求,暂时结果
"""
import os
import sys
from socket import *
from multiprocessing import Process # 导入模块
import time
# 服务器地址
ADDR = ('127.0.0.1', 18888)
OK = 'OK'
def recv_msg(s):
while True:
try:
data, addr = s.recvfrom(4096)
if data.decode() == 'exit':
break
print(data.decode())
except:
print('发送内容异常')
break
def send_msg(s, name, addr, fn):
sys.stdin = os.fdopen(fn)
while True:
try:
data = input('')
if data == "quit":
msg = 'Q ' + name
s.sendto(msg.encode(), addr)
break
msg = 'C %s %s' % (name, data)
s.sendto(msg.encode(), addr)
except:
msg = 'Q ' + name
s.sendto(msg.encode(), addr)
print('输入内容异常')
break
# 客户端启动函数
if __name__ == '__main__':
fn = sys.stdin.fileno()
s = socket(AF_INET, SOCK_DGRAM)
# 进入聊天室
while True:
name = input("请输入姓名:")
msg = 'L ' + name
s.sendto(msg.encode(), ADDR)
msg, addr = s.recvfrom(1024)
if msg.decode() == OK:
print('您已经进入聊天室')
break
# 创建进程 收发消息
# 接收消息
recv_process = Process(target=recv_msg,
name='recv_process', args=(s,))
# 发送消息
send_process = Process(target=send_msg,
name="send_process", args=(s, name, addr, fn))
# 启动 接收消息进程
recv_process.start()
# 启动 发送消息进程
send_process.start()
# 等待进程结束
try:
recv_process.join()
send_process.join()
print('结束聊天')
except:
print('异常退出?')