基于UDP的聊天室程序
功能
基于UDP的聊天室程序,实现多人聊天功能,能够实现:
1.用户注册
2.用户登录
3.公聊
4.私聊
知识点
1、服务器和客户端之间发送的消息均为字节流消息,因此在发送和接受消息时需分别执行加密 encode() 和解密 decode() 操作;
2、实现服务器和客户端之间的连接,首先应 sock = socket.socket(socket.AF_INET ,socket.SOCK_DGRAM)
创建一个空套接(其中socket.AF_INET指机器IP采用的是IPv4,socket.SOCK_DGRAM指机器采用的是TCP流式的传输协议类型),再通过 sock.bind((address,port))
绑定并监听一个ip地址和端口号
3、sock.recvfrom(MAX_BYTES)
中的MAX_BYTES指的是一次性能接收数据消息的大小,而返回的是目标对象的数据流(即加密过后的消息)以及对方的地址
思路
在 client.py 中,发送给服务器端的消息(text)内容为以双空格为分隔的内容,消息内容在 server.py 中被切片后,存放在列表(text_list)中:
text_list[0]标识的是用户所期待执行的操作(注册、登陆、公聊、私聊、退出等);
注册和登陆 操作时:text_list[1]、text_list[2]分别用于存放用户名和密码;
公聊和私聊 操作时:text_list[1]、text_list[2]、text_list[3]分别用于存放用户名、IP地址和发送的消息内容;
退出 时:text_list[1]、text_list[2]分别用于保存用户名和IP地址信息。
保存的IP地址,用于在 退出 时服务器需找到对应的客户端,从而断开彼此之间的连接,以及在 公聊私聊 操作时,用于服务器遍历所有或者某一个IP地址发送消息。
源码
client.py
import socket
import sys
from multiprocessing import Process
import os
MAX_BYTES = 65535
ADDRESS = '127.0.0.1'
PORT = 1600
#将注册或者登陆信息打包发送给服务器端
def Person_Message(sock ,choice):
name = input('please input name:')
password = input('please input password:')
text = str(choice) + ' ' + name + ' ' + password
data = text.encode('ascii')
sock.sendto(data ,(ADDRESS ,PORT))
data ,address= sock.recvfrom(MAX_BYTES)
return data.decode('ascii') ,name ,address
#将用户聊天信息传送给公共频道
def Chat_Message(sock ,name ,address):
print('Please enter the chat content:\n\n(input \033[1;44mExit\033[0m to quit the room,\n'
'input \033[1;44ms/name/message\033[0m for Private chat)\t\t\tHistory:')
#创建进程,父进程发送消息,子进程接受消息
p = Process(target = rcvmsg ,args = (sock ,name ,address))
p.start()
sendmsg(sock ,name ,address)
#发送消息
def sendmsg(sock ,name ,address):
while True:
message = input()
Words = message.split('/')
if Words[0] == 's':
Destination = Words[1]
true_message = Words[2]
text = '4' + ' ' + name + ' ' + str(address) + ' ' + true_message + ' ' + Destination
data = text.encode('ascii')
sock.sendto(data ,(ADDRESS , PORT))
print('OK!')
elif message == 'Exit':
text = '5' + ' ' + name + ' ' + str(address)
data = text.encode('ascii')
sock.sendto(data ,(ADDRESS ,PORT))
sys.exit('You have exited the chat room\n')
else:
text = '3' + ' ' + name + ' ' + str(address) + ' ' + message
data = text.encode('ascii')
sock.sendto(data ,(ADDRESS ,PORT))
#接收消息
def rcvmsg(sock ,name ,address):
while True:
data ,address= sock.recvfrom(MAX_BYTES)
message = data.decode('ascii')
if message == 'exit':
os._exit(0)
else:
print('\t\t\t\t\t\t' + message)
#套接字连接
def main():
sock = socket.socket(socket.AF_INET ,socket.SOCK_DGRAM)
while True:
while True:
choice = int(input('please select:\n 1、register 2、login\n'))
if choice == 1 or choice == 2:
break
print('Unknown command')
#signal标识注册或者登陆时返回的值
signal ,name ,address = Person_Message(sock ,choice)
if signal == 'OK':
os.system("cls")
print('\t\t\t\tYou have successfully entered the room\t\t')
break
elif signal == 'Error_UserExist':
print('User already exists!')
elif signal == 'Error_PasswordError':
print('Password error!')
elif signal == 'Error_UserNotExist':
print('user does not exist!')
Chat_Message(sock ,name ,address)
if __name__ == "__main__":
main()
server.py
import socket
import datetime
MAX_BYTES = 65535
ADDRESS = '127.0.0.1'
PORT = 1600
#选项菜单
def menu(sock ,Users_message):
while True:
# data ,address = sock.recvfrom(MAX_BYTES)
# text = data.decode('ascii')
# print('message from {} is {}'.format(address ,text))
# text = 'hello ,too'
# data = text.encode('ascii')
# sock.sendto(data ,address)
data ,address = sock.recvfrom(MAX_BYTES)
text_list = data.decode('ascii').split(' ')
if int(text_list[0]) == 1:
#注册
Register(sock ,Users_message ,text_list ,address)
if int(text_list[0]) == 2:
#登陆
Login(sock ,Users_message ,text_list ,address)
if int(text_list[0]) == 3:
#公聊
Public_chat(sock ,Users_message ,text_list)
if int(text_list[0]) == 4:
#私聊
Private_chat(sock ,Users_message ,text_list)
if int(text_list[0]) == 5:
#退出
Exit(sock ,Users_message ,text_list)
#注册
def Register(sock ,Users_message ,text_list ,address):
name = text_list[1]
password = text_list[2]
if name in Users_message.keys():
sock.sendto('Error_UserExist'.encode('ascii') ,address)
print(Users_message)
else:
Users_message[name] = [password ,address]
print(name + ' is enter the room')
sock.sendto('OK'.encode('ascii') ,address)
#登陆
def Login(sock ,Users_message ,text_list ,address):
name = text_list[1]
password = text_list[2]
if name in Users_message.keys():
if Users_message[name][0] == password:
sock.sendto('OK'.encode('ascii') ,address)
print(name + ' is enter the room\n')
else:
sock.sendto('Error_PasswordError'.encode('ascii') ,address)
else:
sock.sendto('Error_UserNotExist'.encode('ascii') ,address)
#公聊
def Public_chat(sock ,Users_message ,text_list):
name = text_list[1]
#address = text_list[2]
message = text_list[3]
data = ('[' + name + ']:' + message)
for user in Users_message.keys():
if user != name:
sock.sendto(data.encode('ascii') ,Users_message[user][1])
print('[' + str(datetime.datetime.now()) + ']' + '[' + name + ']:' + message)
#私聊
def Private_chat(sock ,Users_message ,text_list):
name = text_list[1]
#address = text_list[2]
message = text_list[3]
Destination = text_list[4]
data = ('[' + name + ']:' + message)
for user in Users_message.keys():
if user == Destination:
sock.sendto(data.encode('ascii') ,Users_message[user][1])
print('[' + str(datetime.datetime.now()) + ']' + '[' + name + ']' + ' to [' + Destination + ']: ' + message)
#退出程序
def Exit(sock ,Users_message ,text_list):
name = text_list[1]
address = text_list[2]
print(address)
data = 'exit'
for user in Users_message.keys():
if name == user:
sock.sendto(data.encode('ascii') ,Users_message[user][1])
print(name + ' is quit the room\n')
#套接字连接
def main():
#用户信息存在字典中,实现可持久化存储可将用户信息写入txt等文本内
Users_message={}
sock = socket.socket(socket.AF_INET ,socket.SOCK_DGRAM)
sock.bind((ADDRESS ,PORT))
print('listen to {}'.format(sock.getsockname()))
menu(sock ,Users_message)
if __name__ == "__main__":
main()
测试结果
首先进行用户的登陆注册(Bob和Tom是提前注册好的)
之后分别执行:左下角在公聊频道打了声招呼,右上角对July私聊,右小角退出聊天室,均成功并且可以同时运行。