目标是写一个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') 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
聊天效果如下