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就會被掛起。

主要的問題還是在於客戶端非正常登出時的應對,目前解決了一部分問題,但是應該還有不少缺陷。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM