1. 程式人生 > >Python實現多用戶全雙工聊天(一對一)

Python實現多用戶全雙工聊天(一對一)

nobody mes nec 分開 ctrl ces connect rgs welcome

多用戶全雙工聊天簡陋版

簡單實現了兩個客戶端之間的通信,客戶端發送消息,先由服務器接收,然後服務器轉發到另一客戶端。

該版本功能非常簡陋,僅僅實現了最簡單的聊天,有很多地方需要註意。

工作步驟:

  • 服務器端運行
  • 一個客戶端運行,連接成功後輸入用戶名,服務器會保存該用戶名在一個字典中,字典的對應關系是 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就會被掛起。

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

Python實現多用戶全雙工聊天(一對一)