自學python一段時間,一直想弄個有意思的東西,因此就拿socket作一個聊天室,能夠一對多,一對一全雙工聊天。後續可能完善代碼在鼓弄一個帶gui界面的,比較有逼格。python
服務端:多線程
使用socketserver模塊,多線程異步處理客戶端消息,接受客戶消息並轉發 既服務端爲一箇中轉站。app
加入了 登錄 註冊 多人聊天 一對一聊天 防止同時在線異步
客戶端:socket
主線程鏈接服務端,兩個子線程分別負責讀寫ide
sercer: # _*_ coding:utf-8 _*_ import SocketServer from time import ctime import threading, traceback import Queue from db import DB lock = threading.Lock() local_school = threading.local() class Handler(object): queue = [] db = DB() user_name = {} def __init__(self, sock): self.sock = sock self.input = [self.sock] # self.queue.append(self.sock) def recv(self): data = self.sock.recv(1024).strip() return data def send(self, data): self.sock.sendall(data) def stop(self): self.send('ByeBye') self.sock.close() self.queue.remove(self.sock) del self.user_name[local_school.user] def exit(self): self.sock.close() try: self.queue.remove(self.sock) del self.user_name[local_school.user] except ValueError: pass def broadcast(self, user, data): for sock in self.queue: sock.sendall('[%s] %s::%s' % (ctime(), user, data)) def one(self, user_sock, user, data): self.user_name[user_sock].sendall('--------------\n%s\n[%s]::(%s)\n---------------' % (ctime(), user, data)) def yiduiduo(self, user, data): time_data = ctime() for sock in [x for x in self.queue if x != self.sock]: sock.sendall('----------------\n%s\n[%s]::(%s)\n----------------' % (time_data, user, data)) def handler(self): funcdict = { 'login': self.login, 'registered': self.registered, } try: ###異常處理 當客戶端異常斷開鏈接或者正常斷開鏈接:服務端處理異常 self.sock.send('請選擇::login/registered/exit') data = self.recv() if data == 'exit': self.stop() # self.send('exit') elif data in funcdict: funcdict[data]() else: self.handler() except: if self.queue: self.exit() else: pass def login(self): self.send('輸入帳號密碼 格式: user passwd /server') data = self.recv() if data == 'server': self.send('選擇 exit/handler') data = self.recv() if data == 'exit': self.stop() elif data == 'handler': self.handler() else: self.login() user_data = data.split() if len(user_data) == 2: user = user_data[0] passwd = user_data[1] user_data = self.db.get_data() or {} data_scok = self.user_name.get(user) # 檢測該用戶是否在別處登錄 存在則登錄中 得到登錄的sock if data_scok: try: data_scok.sendall('帳號在別處登錄,被迫下線') data_scok.close() self.queue.remove(data_scok) del self.user_name[local_school.user] except: ##異常處理 捕獲此處全部異常不作處理 pass if user in user_data and user_data[user] == passwd: local_school.user = user self.send('歡迎加入聊天室') self.queue.append(self.sock) self.broadcast('systemctl', '[%s]加入聊天室\n' % user) self.user_name[user] = self.sock ##用戶——sock 映射 self.send('選擇:單(d)/多(s)') data = self.recv() if data == 's': self.Ltian() elif data == 'd': self.one_to_one() else: self.send('錯誤\n') self.handler() else: self.send('帳號或密碼不正確!\n') self.login() else: self.send('格式錯誤!\n') self.login() def registered(self): self.send('註冊帳號密碼-格式 user passwd /server') data = self.recv() if data == 'server': self.send('選擇 exit/handler') data = self.recv() if data == 'exit': self.stop() self.send('exit') else: self.handler() user_data = data.split() if len(user_data) == 2: user = user_data[0] passwd = user_data[1] db_data = self.db.get_data() or {} if user in db_data: self.send('用戶已註冊!') self.registered() else: db_data[user] = passwd local_school.user = user lock.acquire() # 添加線程鎖,防止線程同時修改 數據文件 try: self.db.put_data(db_data) finally: lock.release() self.queue.append(self.sock) self.broadcast('system', '[%s]加入聊天室\n' % user) self.user_name[user] = self.sock self.send('選擇:單人聊天(d)/多人聊天(s)\n') data = self.recv() if data == 's': self.Ltian() print self.queue elif data == 'd': self.one_to_one() else: self.send('錯誤!\n') self.handler() else: self.send('格式錯誤\n\n') self.registered() def Ltian(self, ): # 多人聊天 print self.queue print self.user_name self.send('kaishiliaotian') while True: data = self.recv() if data == 'exit': print 'queue1 ::%s' % self.queue self.stop() # self.send('關閉++++++++') print 'queue2 ::%s' % self.queue break self.yiduiduo(local_school.user, data) # 組播消息 def one_to_one(self): self.send('選擇對象:to:user') user_data = self.recv()[3:] if user_data == local_school.user: self.one_to_one() if user_data in self.db.get_data(): if self.user_name.get(user_data) and self.user_name[user_data] in self.queue: self.send('kaishiliaotian') while True: data = self.recv() # if data is None: if data == 'server': self.send('選擇:exit/Ltian(s)') data = self.recv() if data == 'exit': self.one(user_data, local_school.user, '已下線') self.stop() break elif data == 's': self.Ltian() elif not data == '' and self.user_name.get(user_data): # 判斷 數據不爲空 且用戶狀態在線否 self.one(user_data, local_school.user, data) else: self.send('用戶不在線') self.one_to_one() else: self.send('用戶不存在!\n') self.one_to_one() class MyServer(SocketServer.BaseRequestHandler): def handle(self): print self.client_address self.mysock = Handler(self.request) print self.mysock.queue self.mysock.handler() if __name__ == '__main__': host = '127.0.0.1' port = 9999 addr = (host, port) server = SocketServer.ThreadingTCPServer(addr, MyServer) server.request_queue_size = 4399 server.serve_forever() server.shutdown() client: # _*_ coding:utf-8 _*_ from socket import * import threading threads=[] class Client_Handler(object): def __init__(self, ipadr='127.0.0.1', port=9999): self.sock = socket(AF_INET, SOCK_STREAM) self.sock.connect((ipadr, port)) self.input=[self.sock] print self.input def send(self,data): self.sock.sendall(data) def recv(self): data = self.sock.recv(1024).strip() print data return data def write(self): while True: try: data=raw_input('>>>') if data=='exit': self.send('exit') self.sock.close() break self.send(data) except socket.error: #加入異常處理 當服務端斷開sock鏈接時跳出while循環 break except: break def read(self): while True: try: self.recv() except socket.error: break except: break a1=Client_Handler() chat = threading.Thread(target=a1.write) threads.append(chat) chat = threading.Thread(target=a1.read) threads.append(chat) print threads for i in range(len(threads)): threads[i].start()