功能:类似qq群聊、linux环境下
1.进入聊天室需要输入姓名,姓名不能重复
2.有人进入聊天室会向其他人发送通知:xxx进入了聊天室
3.一个人发消息,其他人会收到消息
xxx 说: xxxxxx
4.某人退出聊天室,其他人也会收到通知
xxx退出了聊天室
5.管理员喊话 服务端发送消息给所有的客户端都收到
管理员说:xxxxxx
项目问题:
* 服务端 客户端
* 使用什么技术
* 知识点回顾复习
功能模型: 转发
需要的技术: 套接字通信 udp套接字
用户存储: 字典或列表
消息的收发: 多进程
1.封装(代码量小,将每个功能封装为函数)
2.接口测试(每实现一步就测试一步)
1.搭建网络连接
2.创建多进程
3.每个进程功能编写
4.项目功能模块实现
1.进入聊天室
客户端:
输入姓名
将信息发送给服务端(注明消息类型)
等待服务端回复
根据回复判断是否登录成功
服务端:
接收请求消息
判断请求类型
判断用户名是否存在
如果存在回复不能
如果不存在回复可以登录并插入到数据结构
发送通知给其他用户
2.聊天
客户端: 创建父子进程,分别发消息和收消息
发送聊天请求
接收聊天信息
服务端:
接收请求信息
将消息转发给其他客户端
3.退出
4.管理员消息
'''
name: Junx
email: wjfabcd@outlook.com
date: 2019-7
introduce: Chatroom Client
env: python3.6
'''
from socket import *
import sys,os
#创建套接字,登录,创建子进程
def main():
host,port = (input('请输入聊天室地址:')).split()
addr = host,int(port)
if len(addr) < 2:
return
#创建套接字
s = socket(AF_INET,SOCK_DGRAM)
#循环登录
while True:
name = input("请输入姓名:")
#约定登录请求为L开头+空格,并发送请求,不允许姓名之中有空格
if ' ' in name:
print('不允许输入空格')
break
msg = "L " + name
s.sendto(msg.encode(),addr)
#等待服务器回复
data,addr = s.recvfrom(1024)
#约定OK为登录成功
if data.decode() == 'OK':
print('您已进入聊天室')
break # 退出后创建父子进程实现收发消息功能
else:
#不成功服务器会回复不允许登录的原因
print(data.decode())
#创建父子进程
pid = os.fork()
if pid < 0:
print('创建子进程失败')
elif pid == 0:
#子进程用来发送消息
#指名是谁发的,避免服务端遍历
send_msg(s,name,addr)
else:
#父进程用来接受所有消息
recv_msg(s)
def send_msg(s,name,addr):
while True:
text = input('发言:')
#输入quit表示退出
if text.strip() == 'quit':
msg = 'Q {}'.format(name)
s.sendto(msg.encode(),addr)
# 退出聊天室,子进程退出
sys.exit('退出聊天室')
msg = 'C {} {}'.format(name,text)
s.sendto(msg.encode(),addr)
def recv_msg(s):
#用该函数来接收所有客户端发来的消息
while True:
data, addr = s.recvfrom(1024)
#如果子进程退出聊天室,根据服务端发送的响应终止父进程
if data.decode() == 'EXIT':
sys.exit(0)
#接收消息并解决linux终端的显示问题
print(data.decode()+'\n发言:',end ='')
if __name__ == '__main__':
main()
#!/usr/bin/env python3
#coding = utf-8
'''
name: Junx
email: wjfabcd@outlook.com
date: 2019-7
introduce: Chatroom Server
env: python3.6
'''
from socket import *
import os,sys
#创建网络连接,创建进程,调用功能函数
def main():
#server address
addr = ('0.0.0.0',8888)
#创建套接字:udp套接字
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)
def do_parent(s):
'''接受客户端请求'''
user = {} # 字典结构 ’name‘ : addr
print('处理客户端请求')
#不断接受客户端发送的消息
while True:
msg,addr = s.recvfrom(1024)
msg = msg.decode().split()
if msg[0] == 'L':
#实参传递形参时,实参为可变数据类型时会对实参进行修改
do_login(s,user,msg[1],addr) # 参数为客户端用户名称
elif msg[0] == 'C':
do_chat(s,user,msg[1],' '.join(msg[2:]))
elif msg[0] == 'Q':
do_quit(s,user,msg[1])
def do_child(s,addr):
'''做管理员喊话'''
#与父进程是两个独立的进程,不能使用父进程的user字典
#故向自己发送消息从而向其他所有用户转发消息
while True:
text = input('管理员:')
msg = "C 管理员 {}".format(text)
s.sendto(msg.encode(),addr)
def do_login(s,user,name,addr):
'''判断名称是否在user字典内'''
if (name in user) or name == '管理员':
s.sendto('该用户已存在'.encode(),addr)
return
s.sendto(b'OK',addr)
#通知其他人
msg = '\n欢迎{}进入聊天室'.format(name)
for new_addr in user.values():
s.sendto(msg.encode(),new_addr)
#插入用户
user[name] = addr
def do_chat(s,user,name,text):
#将接受的消息转发给聊天室内的其他人
msg = '\n{}说:{}'.format(name,text)
for i in user:
if i == name: continue
s.sendto(msg.encode(),user[i])
def do_quit(s,user,name):
#某用户退出聊天室,将用户从字典中删除
#并将该用户的退出消息发送给聊天室内其他用户
msg = '\n{}退出了聊天室'.format(name)
for i in user:
if i == name:
s.sendto(b'EXIT',user[i])
s.sendto(msg.encode(),user[i])
del user[name]
if __name__ == '__main__':
main()