Socket編程和實現(xiàn)聊天室
一、HTTP協(xié)議的socket通信
1、server.py
# 服務(wù)端 import sys import socket ip_point = ('127.0.0.1',9999) sk = socket.socket() sk.bind(ip_point) sk.listen(5) print("進(jìn)入監(jiān)聽狀態(tài)...") conn,addr = sk.accept() print(addr) client_data = conn.recv(1024) print(client_data.decode("utf-8")) conn.send("服務(wù)端回復(fù)內(nèi)容:我很好。".encode("utf-8")) conn.close()
2、client.py
# 客戶端 import socket ip_port = ("127.0.0.1",9999) sk = socket.socket() sk.connect(ip_port) sk.send("客戶端發(fā)送數(shù)據(jù):你好嗎?".encode("utf-8")) server_replay = sk.recv(1024) print("get data:",server_replay.decode("utf-8")) sk.close()
3、執(zhí)行結(jié)果

4、流程說明
- 建立socket對象
- socket綁定ip和端口
- listen
- 有連接,accept
- 讀寫數(shù)據(jù)
- 關(guān)閉
二、UDP協(xié)議的socket通信
1、server.py
# 服務(wù)端 from socket import * from time import ctime HOST = '' PORT = 1200 BUFSIZ = 128 ADDR = (HOST, PORT) # 創(chuàng)建一個服務(wù)器端UDP套接字 udpServer = socket(AF_INET, SOCK_DGRAM) # 綁定服務(wù)器套接字 udpServer.bind(ADDR) print('已經(jīng)進(jìn)入監(jiān)聽狀態(tài)...') # 接收來自客戶端的數(shù)據(jù) data, addr = udpServer.recvfrom(BUFSIZ) print(u"得到客戶端數(shù)據(jù):",data.decode("utf-8")) # 向客戶端發(fā)送數(shù)據(jù) udpServer.sendto(b'%s %s[%s]' % ("服務(wù)器發(fā)送消息:".encode("utf-8"),ctime().encode("utf-8"),data),addr) print('向客戶端發(fā)送數(shù)據(jù):', data) udpServer.close()
2、client.py
# 客戶端 #encoding=utf-8 from socket import * HOST = 'localhost' PORT = 1200 BUFSIZ = 128 ADDR = (HOST, PORT) # 創(chuàng)建客戶端UDP套接字 udpClient = socket(AF_INET, SOCK_DGRAM) data = input('>') # 向服務(wù)器端發(fā)送數(shù)據(jù) udpClient.sendto(data.encode("utf-8"), ADDR) # 接收來自服務(wù)器端的數(shù)據(jù) data, ADDR = udpClient.recvfrom(BUFSIZ) print(data.decode("utf-8")) udpClient.close()
3、結(jié)果

三、實現(xiàn)一個調(diào)用系統(tǒng)命令的例子
1、server.py
import os if __name__ == '__main__': import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost', 8001)) sock.listen(5) while True: connection,address = sock.accept() try: connection.settimeout(5) command = connection.recv(1024) print(command) result=os.popen(command.decode("utf-8")) #拿到執(zhí)行后管道命令后的結(jié)果 connection.send(command) connection.send(result.read().encode("utf-8")) except socket.timeout: print('time out') connection.close()
2、client.py
if __name__ == '__main__': import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', 8001)) import time time.sleep(2) sock.send('ipconfig'.encode("utf-8")) print("command:",sock.recv(10).decode("utf-8")) print ("command result:",sock.recv(1024).decode("utf-8")) sock.close()
3、執(zhí)行結(jié)果
客戶端向服務(wù)端發(fā)送一個dos命令
服務(wù)服務(wù)端返回命令響應(yīng)結(jié)果

四、實現(xiàn)聊天室
1、實現(xiàn)的效果:

2、服務(wù)器端代碼
# encoding=utf-8 from twisted.internet.protocol import Factory from twisted.protocols.basic import LineReceiver # 事件處理器 from twisted.internet import reactor class Chat(LineReceiver):#本質(zhì)就是一個Protocol類的子類,真正管理server端和每個客戶端通訊邏輯的,每一個客戶端的連接,都會自動生成一個Chat類的實例。 message_dict={} #類變量,存儲所有用戶發(fā)送的消息,key:收消息的用戶名,value:列表存所有發(fā)送來的消息 def __init__(self, users): self.users = users #把ChatFactory類中的實例變量user字典,用self.users 變量引用這個字典 self.name = None #當(dāng)前連接的用戶名,你起的名字會存到這個變量里面 self.state = "GETNAME" #狀態(tài)位,確認(rèn)當(dāng)前連接的客戶端起名名字沒有,沒起名狀態(tài)是GETNAME,起了名狀態(tài)是CHAT def connectionMade(self): # 連接開始,且開始做處理 if self.name is None: #判斷客戶端有沒有名字 self.sendLine(u"你叫什么名字?".encode("utf-8")) #給客戶端發(fā)送指定消息! def connectionLost(self, reason): #連接斷開的時候做什么處理 print("斷開時的用戶名:",self.name) if self.name in self.users: del self.users[self.name] #user字典是一個全局變量,value存的是連接對象本身(chat的實例),已經(jīng)斷開不是連接狀態(tài)了,刪除掉。 try: if self.name in Chat.message_dict: #把別人給你發(fā)的消息,從消息字典中刪除掉 del Chat.message_dict[self.name] except: print("刪除用戶的聊天記錄失敗") def lineReceived(self, line): # 收到客戶端發(fā)來的消息做什么處理,會自動調(diào)用 #客戶端發(fā)送的消息,會自動傳到line這個變量里面 if self.state == "GETNAME": # 根據(jù)狀態(tài)開始選擇不同得內(nèi)容處理 print("line:",line) self.handle_GETNAME(line.decode("utf-8")) #調(diào)用起名字的處理方法handle_GETNAME else: self.handle_CHAT(line.decode("utf-8")) #調(diào)用聊天的處理方法handle_CHAT def handle_GETNAME(self, name): #處理用戶起名字的邏輯 if name in self.users: self.sendLine(u"名字沖突了,請做另外起個用戶名.\n".encode("utf-8") ) # 每個用戶只能有一個聊天通信存在 return self.sendLine(("歡迎, %s!\n" %name).encode("utf-8")) #給客戶端發(fā)送一個消息,告訴你歡迎xxx self.name = name #用戶發(fā)來的名字,賦值給self.name變量,連接的客戶端的名字 self.users[name] = self #把self,自己,放到users字典中(存所有用戶的連接對象---Chat的實例--self) self.state = "CHAT" #把用戶從GETNAME狀態(tài)改為CHAT狀態(tài) def handle_CHAT(self, message): # 處理用戶聊天的邏輯 #客戶端輸入:zhangsan:你吃了嗎? if "getmessage" in message: #發(fā)的命令是getmessage,讀別人給你發(fā)的消息,取消息的用戶名:getmessage username = message.split(":")[0]#取到需要取消息的用戶名 print("*"*2,username,Chat.message_dict) #判斷取消息的用戶名在不在消息字典中,或者是不是消息為空 if (username not in Chat.message_dict) or Chat.message_dict[username] == []: self.users[username].sendLine("沒有別人給你發(fā)送的數(shù)據(jù)".encode("utf-8")) print(message,"---->","沒有別人給你發(fā)送的數(shù)據(jù)","\n") return message_list=Chat.message_dict[username]#把消息的列表取出來 print(message_list) if username in self.users:#把所有的鏈接對象遍歷一遍 print(username,self.users[username]) #self.users[username]--》username對應(yīng)的鏈接對象取出來,然后把列表以字符串的形式發(fā)給客戶端 #客戶端,需要用eval(列表的字符串)---》轉(zhuǎn)換為一個列表 #用這個連接對象發(fā)給用戶這個列表字符串 self.users[username].sendLine(("%s" % message_list).encode("utf-8")) del Chat.message_dict[username]#刪除剛才收走的消息 return elif ":" in message: '''
消息格式:
huqiqi:zhangsan:你吃了么? ''' if message.count(":")!=2: self.sendLine("您發(fā)送的消息格式不對,請輸入send命令后,按照格式‘用戶名:消息’來進(jìn)行聊天信息發(fā)送。".encode("utf8")) print("您發(fā)送的消息格式不對,請輸入send命令后,按照格式‘用戶名:消息’來進(jìn)行聊天信息發(fā)送。") return from_username = message.split(":")[0] to_username = message.split(":")[1] chat_message = message.split(":")[2] if to_username not in Chat.message_dict:#判斷接受者是否在消息字典中,不在,聲明一個空列表作為value Chat.message_dict[to_username] = [] Chat.message_dict[to_username].append((from_username,chat_message))#向列表中追加這個消息:(發(fā)消息人名字,消息體) print(message, "---->", "增加了用戶%s發(fā)送的消息:%s" %(to_username,(from_username,chat_message)) , "\n") return #查看哪些用戶處于登錄狀態(tài) elif message.strip() =="list": #查看哪些用戶是處于登錄狀態(tài) print("list response") self.sendLine((str([username for username in self.users]) + "\n").encode("utf-8")) print(message, "---->", (str([username for username in self.users]) + "\n"),"\n") return #如果沒有識別用戶命令,提示用戶可以輸入使用的命令 else: #非上面的消息,就返回命令提示語 send_message= ("""請指定用戶名,輸入send后,按照格式‘用戶名:消息’來進(jìn)行聊天信息發(fā)送。 \n或者輸入list查看當(dāng)前登錄用戶\n輸入getmessage獲取其他用戶發(fā)給你的聊天信息\n""") #print (type(send_message)) self.sendLine(send_message.encode("utf-8")) print(message, "---->",send_message,"\n") return class ChatFactory(Factory): #實現(xiàn)的工廠類,必須定義buildProtocol方法,必須返回一個Protocol子類的實例對象 def __init__(self): self.users = {} #將所有與服務(wù)器端連接的對象存放到此字典中,所有的實例均可以使用此字典獲取所有的連接對象,字典來存儲所有的連接,key:和server連接的客戶端名字(第一次連接的時候取的);value:連接對象也就是Chat的實例 def buildProtocol(self, addr): return Chat(self.users) #每次一個新的客戶端建立了連接,那么會自動生成Chat類的實例,這個實例負(fù)責(zé)管理服務(wù)端和客戶端的所有通訊細(xì)節(jié),會根據(jù)網(wǎng)絡(luò)事件,觸發(fā)不同內(nèi)置的事件方法! if __name__ == '__main__': reactor.listenTCP(1200, ChatFactory()) #監(jiān)聽端口,指定通信協(xié)議的工廠類實例 print ("開始進(jìn)入監(jiān)聽狀態(tài)...") reactor.run() #開始監(jiān)聽
3、客戶端代碼
# socket client end from socket import * import time s = socket(AF_INET, SOCK_STREAM) remote_host = gethostname()print ('remote_host:', remote_host) port = 1200 s.connect((remote_host, port)) # 發(fā)起連接 print (u"連接從", s.getsockname()) # 返回套接字自己的地址。通常是一個元組(ipaddr,port) print (u"連接到", s.getpeername()) # 返回連接套接字的遠(yuǎn)程地址。返回值通常是元組(ipaddr,port) print (u'從服務(wù)器返回消息:') print (s.recv(1200).decode("utf-8").strip()) while 1: username = input("請輸入你要使用的英文用戶名:\n") s.send(('%s\r\n' %username.strip()).encode("utf-8")) # 發(fā)送一行字符串(以\r\n 結(jié)束)到服務(wù)器端 print (u'從服務(wù)器返回消息:') response = s.recv(1200).decode("utf-8").strip() print (response) if "沖突" not in response: break print("*"*50) print("""查看當(dāng)前登錄用戶列表的命令:list 查看別人給你發(fā)送的消息命令要求:getmessage 給別人發(fā)送消息,請先輸入send,然后按照如下格式發(fā)送聊天信息: username:要發(fā)送的消息 斷開連接請輸入bye""") print("*"*50) while 1: send_message=input("請輸入發(fā)送的信息:\n") if send_message=="getmessage" : #當(dāng)前用戶名:getmessage --->發(fā)給服務(wù)器 s.send(('%s:%s\r\n' %(username,send_message)).encode("utf-8")) print (u'從服務(wù)器返回消息:') content = s.recv(1200).decode("utf-8").strip() #返回的消息內(nèi)容是個列表[(發(fā)消息人名1,消息1),(發(fā)消息人名2,消息2),(發(fā)消息人名3,消息3)....] try: if "[" in content and ']' in content: #服務(wù)器返回的是個字符串,用eval轉(zhuǎn)換為列表對象 info_list = eval(content) for index,info in enumerate(info_list):#遍歷 #index是序號,info[0]發(fā)消息的人名,info[1]是發(fā)送的消息體 print("%s>用戶%s信息:%s" %(index,info[0],info[1])) else: print(content) except: print("從服務(wù)器收到的數(shù)據(jù)是無效數(shù)據(jù)!數(shù)據(jù)為%s" %content) elif send_message=="list": s.send(('%s\r\n' %send_message).encode("utf-8")) print (u'從服務(wù)器返回消息:') print (s.recv(1200).decode("utf-8").strip()) elif send_message=="bye": s.close() break elif send_message=="send": print("請輸入你要給用戶發(fā)送的消息,消息格式:\n用戶名:您要發(fā)的消息內(nèi)容\n") info = input(">") s.send(('%s:%s\r\n' %(username,info.strip())).encode("utf-8"))#發(fā)給server端 print("發(fā)送的消息:",('%s:%s\r\n' %(username,info.strip()))) #打印發(fā)送的消息 else: print("輸入的消息無效:請使用getmessage、list、bye或者send之一") time.sleep(1) continue
浙公網(wǎng)安備 33010602011771號