网络编程:fork多进程网络通信应用实例--聊天室(群聊)
简单的群聊聊天室
1、功能
类似于qq群聊
【1】进入聊天室需要输入姓名, 姓名不能重复
【2】有人进入聊天室此时会向其他人发起通知 ,xxx 进入了聊天室
【3】如果一个人发消息,则其他人都能收到 ,xxx 说 : xxxxxxx
【4】如果某个人退出聊天室其他人也会收到通知, xxx 退出了聊天室
【5】服务端可以喊话 :此时群里所有人都能收到服务端消息 。管理员 说:xxx
2、功能分析
整体结构
Q:分为几部分,如何封装,使用什么样的技术手段
A:服务端 ,客户端, 在客户端和服务端将每个功能封装为一个函数
3、技术方案
【1】转发
一个客户端发送给服务器,服务器发送给其他人
【2】套接字使用
udp 完成操作
【3】用户存储
字典 或者 列表 (可变类型,能够遍历提取)
【4】地址 用户名
【5】发送和接受消息的控制
发送和接收使用多进程分离互不影响
4、注意事项
【1】注重封装
【2】分段测试
5、代码编写流程
搭建通信 --> 创建多进程 --->每个进程功能确定 --> 实现每一个功能模块
6、具体细节梳理
【1】进入聊天室
① 客户端
① 输入姓名, 将信息发送给服务器,adress,name
② 接受到服务端返回结果判断下一步执行什么
② 服务端
① 接受消息,判断请求类型
② 判断是否可以登录(姓名是否已经存在)
③ 返回给客户端是否登录(如果可以服务端会将姓名插入到存储用户信息的数据结构中)
④ 给所有人发送消息
【2】聊天
① 客户端
① 发起聊天:name ,msg
② 接受服务器回复
② 服务端
① 接受消息 ,判断消息类型
② 组织消息结构转发给其他客户端
【3】退出聊天室
① 客户端
① 发送消息退出:Q,name
② 接收服务端回复
③ 退出程序
② 服务端
① 接收消息
② 判断请求类型
③ 从存储用户信息的数据结构中删除对应用户
④ 告知所有人,xxx退出
【fork-socket-groupChat-server.py】
from socket import *
import os,sys
#发送管理员消息
def do_child(s,addr):
while True:
msg = input("管理员消息:")
msg = "C 管理员 " + msg
s.sendto(msg.encode(),addr)
#用户登录
def do_login(s,user,name,addr):
if (name in user) or name == "管理员":
s.sendto("该用户已存在".encode(),addr)
return
s.sendto(b'OK',addr)
#通知所有人
msg = "\n欢迎 %s 进入聊天室"%name
for i in user:
s.sendto(msg.encode(),user[i])
#插入user
user[name] = addr
def do_chat(s,user,name,data):
msg = "\n{} 说: {}".format(name,data)
for i in user:
if i != name:
s.sendto(msg.encode(),user[i])
def do_quit(s,user,name):
msg = "\n%s 离开了聊天室"%name
for i in user:
if i == name:
s.sendto(b'EXIT',user[i])
else:
s.sendto(msg.encode(),user[i])
del user[name] #删除离开的用户
#接收客户端请求并处理
def do_parent(s):
# 用于存储用户 {'Alex':('127.0.0.1',8888)}
user = {}
while True:
msg,addr = s.recvfrom(1024)
msgList = msg.decode().split(' ')
if msgList[0] == 'L':
do_login(s,user,msgList[1],addr)
elif msgList[0] == 'C':
# "C Levi [I miss you]"
data = ' '.join(msgList[2:])
do_chat(s,user,msgList[1],data)
elif msgList[0] == 'Q':
do_quit(s,user,msgList[1])
# 创建套接字,网络连接,创建父子进程
def main():
#server address
ADDR = ('0.0.0.0',8888)
#创建套接字
s = socket(AF_INET,SOCK_DGRAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
#创建父子进程
pid = os.fork()
if pid < 0:
sys.exit("创建进程失败")
elif pid == 0:
do_child(s,ADDR)
else:
do_parent(s)
if __name__ == "__main__":
main()
【fork-socket-groupChat-client.py】
from socket import *
import sys,os
def login(s,ADDR):
while True:
name = input("请输入用户名:")
msg = "L " + name
s.sendto(msg.encode(),ADDR)
#接收登录结果
data,addr = s.recvfrom(1024)
if data.decode() == 'OK':
print("@进入聊天室@")
return name
else:
print(data.decode())
#发送消息
def do_child(s,name,addr):
while True:
text = input("发言(quit退出):")
#退出
if text.strip() == "quit":
msg = "Q " + name
s.sendto(msg.encode(),addr)
sys.exit("退出聊天室")
msg = "C %s %s"%(name,text)
s.sendto(msg.encode(),addr)
#接收消息
def do_parent(s):
while True:
msg,addr = s.recvfrom(1024)
if msg.decode() == 'EXIT':
sys.exit(0)
print(msg.decode()+"\n发言(quit退出):",end="")
#main控制套接字的创建
def main():
if len(sys.argv) < 3:
print("argv is error")
return
HOST = sys.argv[1]
PORT = int(sys.argv[2])
ADDR = (HOST,PORT)
s = socket(AF_INET,SOCK_DGRAM)
name = login(s,ADDR)
if name:
pid = os.fork()
if pid < 0:
sys.exit("创建子进程失败")
elif pid == 0:
do_child(s,name,ADDR)
else:
do_parent(s)
else:
return
if __name__ == "__main__":
main()