Python实现多用户全双工聊天(一对一)
多用户全双工聊天简陋版
简单实现了两个客户端之间的通信,客户端发送消息,先由服务器接收,然后服务器转发到另一客户端。
该版本功能非常简陋,仅仅实现了最简单的聊天,有很多地方需要注意。
工作步骤:
- 服务器端运行
- 一个客户端运行,连接成功后输入用户名,服务器会保存该用户名在一个字典中,字典的对应关系是
username --> socket
- 输入用户名之后,该客户端需要确定一个聊天用户,客户端输入
To:user
即可;如果客户端发送其他文本的话,会收到来自服务器的提示:“Nobody is chatting with you. Maybe the one talked with you is talking with someone else” - 当两个客户端成功连接之后就可以互相发送消息
服务器端代码如下:
#!/usr/bin/python
#coding:utf-8
#server.py
from socket import *
from time import ctime
import threading
import re
HOST = ''
PORT = 9999
BUFSIZ = 1024
ADDR = (HOST,PORT)
tcpSerSock = socket(AF_INET,SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)
clients = {} # username -> socket
chatwith = {} # user1.socket -> user2.socket
# clients字典中记录了连接的客户端的用户名和套接字的对应关系
# chatwith字典中记录了通信双方的套接字的对应
# messageTransform()处理客户端确定用户名之后发送的文本
# 文本只有四种类型:
# None
# Quit
# To:someone
# 其他文本
def messageTransform(sock,user):
while True:
data = sock.recv(BUFSIZ)
if not data:
if chatwith.has_key(sock):
chatwith[sock].send(data)
del chatwith[chatwith[sock]]
del chatwith[sock]
del clients[user]
sock.close()
break
if data=='Quit':
sock.send(data)
if chatwith.has_key(sock):
data = '%s.' % data
chatwith[sock].send(data)
del chatwith[chatwith[sock]]
del chatwith[sock]
del clients[user]
sock.close()
break
elif re.match('^To:.+', data) is not None:
data = data[3:]
if clients.has_key(data):
if data==user:
sock.send('Please don\'t try to talk with yourself.')
else:
chatwith[sock] = clients[data]
chatwith[clients[data]] = sock
else:
sock.send('the user %s is not exist' % data)
else:
if chatwith.has_key(sock):
chatwith[sock].send('[%s] %s: (%s)' % (ctime(),user,data))
else:
sock.send('Nobody is chating with you. Maybe the one talked with you is talking with someone else')
# 每个客户端连接之后,都会启动一个新线程
# 连接成功后需要输入用户名
# 输入的用户名可能会:
# 已存在
# (客户端直接输入ctrl+c退出)
# 合法用户名
def connectThread(sock,test): # client's socket
user = None
while True: # receive the username
username = sock.recv(BUFSIZ)
if not username: # the client logout without input a name
print('The client logout without input a name')
break
if clients.has_key(username): # username existed
sock.send('Reuse')
else: # correct username
sock.send('OK')
clients[username] = sock # username -> socket
user = username
break
if not user:
sock.close()
return
print('The username is: %s' % user)
# get the correct username
messageTransform(sock,user)
if __name__=='__main__':
while True:
print('...WAITING FOR CONNECTION')
tcpCliSock, addr = tcpSerSock.accept()
print('CONNECTED FROM: ', addr)
chat = threading.Thread(target = connectThread, args = (tcpCliSock,None))
chat.start()
客户端代码如下:
#!/usr/bin/python
#coding:utf-8
#client.py
from socket import *
from time import ctime
# from termios import tcflush,TCIFLUSH
import threading
import sys
HOST = '127.0.0.1'
PORT = 9999
BUFSIZ = 1024
ADDR = (HOST,PORT)
tcpCliSock = socket(AF_INET,SOCK_STREAM)
tcpCliSock.connect(ADDR)
'''
因为每个客户端接收消息和发送消息是相互独立的,
所以这里将两者分开,开启两个线程处理
'''
def Send(sock,test):
while True:
try:
data = raw_input()
sock.send(data)
if data=='Quit':
break
except KeyboardInterrupt:
sock.send('Quit')
break
def Recv(sock,test):
while True:
data = sock.recv(BUFSIZ)
if data=='Quit.':
print('He/She logout')
continue
if data=='Quit':
break
print(' %s' % data)
if __name__=='__main__':
print('Successful connection')
while True:
username = raw_input('Your name(press only Enter to quit): ')
tcpCliSock.send(username)
if not username:
break
# username is not None
response = tcpCliSock.recv(BUFSIZ)
if response=='Reuse':
print('The name is reuse, please set a new one')
continue
else:
print('Welcome!')
break
if not username:
tcpCliSock.close()
recvMessage = threading.Thread(target = Recv, args = (tcpCliSock,None))
sendMessage = threading.Thread(target = Send, args = (tcpCliSock,None))
sendMessage.start()
recvMessage.start()
sendMessage.join()
recvMessage.join()
总结:
功能简陋,后续会有所改进。这里还有很多地方需要注意。
比如说两个客户端A成功连接后,和客户端B聊天。A发送消息时直接输入ctrl+c
退出程序(sendMessage
线程会结束),我将这种情况模拟成A发送Quit
登出。服务器接收到A登出信息之后,会回发一个Quit
给A,A成功登出(recvMessage
线程结束)。此外如果A和B建立了聊天关系,就要接触这个关系,服务器发送Quit.
给B,B会继续接收信息,但是服务器端的chatwith
字典中已经不存在A.socket --> B.socket
关系。
但是还有很多没有解决的问题,比如说客户端A并没有输入信息,直接点击关闭按钮退出,就会发生异常(与之聊天的B客户端会崩溃)。
如果当前存在 A-->B的聊天关系,这时有一个C登录,并且确定了C-->A的聊天关系,那么A会和C聊天,这时客户端B就会被挂起。
主要的问题还是在于客户端非正常登出时的应对,目前解决了一部分问题,但是应该还有不少缺陷。
有缘再见