目標是寫一個python的p2p聊天的項目,這里先說一下python socket的基礎課程
一、Python Socket 基礎課程
Socket就是套接字,作為BSD UNIX的進程通信機制,取后一種意思。通常也稱作"套接字",用於描述IP地址和端口,是一個通信鏈的句柄,可以用來實現不同虛擬機或不同計算機之間的通信。在Internet上的主機一 般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不同的端口對應於不同的服務。Socket正如其英文原 意那樣,像一個多孔插座。一台主機猶如布滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟件將插頭插到不同編號的插座,就可以得到不同的服務

Socket連接的步驟
(1)服務器監聽:是服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態。
(2)客戶端請求:是指由客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然后就向服務器端套接字提出連接請求。
(3)連接確認:是指當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求,它就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接 字的描述發給客戶端,一旦客戶端確認了此描述,連接就建立好了。而服務器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。
二、服務端程序
因為很喜歡看三體,所以這個服務端就起名叫紅岸,紅岸基地的主要作用就是作為server端來使用,轉發雙方的通信,現在是調試階段,先使用socket寫單線程的,以后會使用socketserver或者多線程來重新寫一個
先建立一個連接列表
# -*- coding: utf-8 -*- import select import socket inBufSize = 4096 outBufSize = 4096 CONNECTION_LIST = []
構造函數
def __init__(self,port=5247): # todo 使用socketserver來寫 self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.serverSocket.bind(('', port)) self.serverSocket.listen(5) print "server wait for connect...." self.socketsMap = {} # socket session字典 id : socket self.idMap = {} #socket session 字典 socket:id CONNECTION_LIST.append(self.serverSocket)
socketsMap和idMap是分別建立這id和socket之間的對應字典,P2P聊天的時候通過socket來找發送者id和通過接受者id來找socket
主要的處理函數是這樣的
def socet_handle(self): while 1: # Get the list sockets which are ready to be read through select read_sockets, write_sockets, error_sockets = select.select(CONNECTION_LIST, [], []) for sock in read_sockets: # New connection if sock == self.serverSocket:#用戶通過主socket(即服務器開始創建的 socket,一直處於監聽狀態)來登錄 # Handle the case in which there is a new connection recieved through server_socket sockfd, addr = self.serverSocket.accept() id = sockfd.recv(100) self.login(id,sockfd) else: self.chat(sock)
通過select來監聽所有的連接,select是一個非阻塞的監聽程序,監聽文件的讀,寫,錯誤,函數用法是select.select(readable_iterable,writeble_iterable,error_iterable,timeout).
如果用戶是使用主socket(一直在監聽的端口,用戶登錄時要連接到這個端口,然后再在別的端口通信),就要登錄函數
登錄函數如下
def login(self,id,sock):#新用戶登錄 print "%s login"%id self.socketsMap[id] = sock self.idMap[sock] = id sock.send('hello %s,you login successed'%id) CONNECTION_LIST.append(sock)#要在這里把socket加進來才行
在CONNECTION_LIST中把會話加進去,然后返回一個問候信息
聊天和廣播程序
def chat(self,sock):#點對點聊天,發送消息格式id||信息 try: data = sock.recv(inBufSize) except Exception: sock.send("remote is offline") sock.close() else: remote_id = data.split('||')[0] message = data.split('||')[1] print "id = %s,message = %s"%(remote_id,message) local_id = self.idMap[sock] if remote_id == 'all': self.broadcast(local_id,message) else: self.p2psend(local_id,message,remote_id) def p2psend(self,local_id,message,remote_id): remote_socket = self.socketsMap[remote_id] message_send = "%s said : %s" % (local_id, message) try: remote_socket.sendall(message_send) except Exception,e: print e remote_socket.close() CONNECTION_LIST.remove(remote_socket) def broadcast(self,local_id,message): for sock in CONNECTION_LIST: if sock == self.serverSocket: continue else: try: message_send = "%s said : %s" % (local_id, message) sock.send(message_send) except Exception,e: print e sock.close() CONNECTION_LIST.remove(sock) continue
服務端的完全體如下
# -*- coding: utf-8 -*- import select import socket inBufSize = 4096 outBufSize = 4096 CONNECTION_LIST = [] class ChatServer: def __init__(self,port=5247): # todo 使用socketserver來寫 self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.serverSocket.bind(('', port)) self.serverSocket.listen(5) print "server wait for connect...." self.socketsMap = {} # socket session字典 id : socket self.idMap = {} #socket session 字典 socket:id CONNECTION_LIST.append(self.serverSocket) def login(self,id,sock):#新用戶登錄 print "%s login"%id self.socketsMap[id] = sock self.idMap[sock] = id sock.send('hello %s,you login successed'%id) CONNECTION_LIST.append(sock)#要在這里把socket加進來才行 def chat(self,sock):#點對點聊天,發送消息格式id||信息 try: data = sock.recv(inBufSize) except Exception: sock.send("remote is offline") sock.close() else: remote_id = data.split('||')[0] message = data.split('||')[1] print "id = %s,message = %s"%(remote_id,message) local_id = self.idMap[sock] if remote_id == 'all': self.broadcast(local_id,message) else: self.p2psend(local_id,message,remote_id) def p2psend(self,local_id,message,remote_id): remote_socket = self.socketsMap[remote_id] message_send = "%s said : %s" % (local_id, message) try: remote_socket.sendall(message_send) except Exception,e: print e remote_socket.close() CONNECTION_LIST.remove(remote_socket) def broadcast(self,local_id,message): for sock in CONNECTION_LIST: if sock == self.serverSocket: continue else: try: message_send = "%s said : %s" % (local_id, message) sock.send(message_send) except Exception,e: print e sock.close() CONNECTION_LIST.remove(sock) continue def socet_handle(self): while 1: # Get the list sockets which are ready to be read through select read_sockets, write_sockets, error_sockets = select.select(CONNECTION_LIST, [], []) for sock in read_sockets: # New connection if sock == self.serverSocket:#用戶通過主socket(即服務器開始創建的 socket,一直處於監聽狀態)來登錄 # Handle the case in which there is a new connection recieved through server_socket sockfd, addr = self.serverSocket.accept() id = sockfd.recv(100) self.login(id,sockfd) else: self.chat(sock) def main(self): self.socet_handle() self.serverSocket.close() if __name__ == '__main__': chat_server_obj = ChatServer() chat_server_obj.main()
三、客戶端程序
客戶端程序的名字是葉文潔和監聽員1379,不要回答!不要回答!不要回答!
主要就是使用select來監聽sys.stdin和socket,來活兒了就要及時處理
def socket_handler(self): while 1: rlist = [sys.stdin, self.client_socket] # 接收列表 read_list, write_list, error_list = select.select(rlist, [], [], 2) for sock in read_list: # incoming message from remote server if sock == self.client_socket: data = sock.recv(4096) if not data: print '\nDisconnected from chat server' sys.exit() else: # print data sys.stdout.write(data) self.prompt() # user entered a message else: msg = sys.stdin.readline() remote_id = raw_input("Please input remote id:") msg_send = "%s||%s"%(remote_id,msg) self.client_socket.send(msg_send) self.prompt()
快吃中午飯了,就不詳細說了,也沒什么好詳細說的,很簡單,客戶端完全體如下,葉文潔的id是1,監聽員1379的id是2,后邊可以改成手動指定的,在群聊里面加上托馬斯維德和程心
# -*- coding:utf-8 -*- import socket, select, string, sys HOST = '127.0.0.1' PORT = 5247 ID = '1' class ChatClient: def __init__(self): self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client_socket.settimeout(2) self.connect() def connect(self): try: self.client_socket.connect((HOST, PORT)) self.client_socket.send(ID) except Exception,e: print 'Unable to connect because of %s'%e sys.exit() else: print 'Connected to remote host. Start sending messages' self.prompt() def prompt(self): sys.stdout.write('\n<You> ') sys.stdout.flush() def socket_handler(self): while 1: rlist = [sys.stdin, self.client_socket] # 接收列表 read_list, write_list, error_list = select.select(rlist, [], [], 2) for sock in read_list: # incoming message from remote server if sock == self.client_socket: data = sock.recv(4096) if not data: print '\nDisconnected from chat server' sys.exit() else: # print data sys.stdout.write(data) self.prompt() # user entered a message else: msg = sys.stdin.readline() remote_id = raw_input("Please input remote id:") msg_send = "%s||%s"%(remote_id,msg) self.client_socket.send(msg_send) self.prompt() if __name__ == '__main__': chat_client_obj = ChatClient() chat_client_obj.socket_handler()
githu地址
https://github.com/wuxie2015/tri_body_chat
聊天效果如下


