16、Python之socket网络编程
一、socket
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。基本上,Socket 是任何一种计算机网络通讯中最基础的内容。例如当你在浏览器地址栏中输入 http://www.cnblogs.com/ 时,你会打开一个套接字,然后连接到http://www.cnblogs.com/ 并读取响应的页面然后然后显示出来。而其他一些聊天客户端如 gtalk 和 skype 也是类似。任何网络通讯都是通过 Socket 来完成的。
Python 官方关于 Socket 的函数请看 http://docs.python.org/library/socket.html
socket和file的区别:
1、file模块是针对某个指定文件进行【打开】【读写】【关闭】
2、socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
根据上图,下面我们就用python来写一个socket的服务端。
1 import socket 2 #第一步 创建流套接字描述符 3 server = socket.socket() 4 #第二步 命名套接字(协议,地址,端口) 5 server.bind(("localhost",8081)) 6 #第三步 监听客户端socket请求 7 server.listen() 8 #建立连接 9 conn,address = server.accept() 10 #第四部 数据传输 11 rec_data = conn.recv(1024) 12 print(rec_data) 13 conn.send(rec_data.upper()) 14 #第五步骤 关闭 15 server.close()
上面服务端的逻辑就是将客户端发送过来的数据转转换为大写再发送给客户端,下面我们再写一个客户端的程序。
1 import socket 2 #第一步 创建流套接字描述符 3 client = socket.socket() 4 #第二步 连接远程socket服务器 5 client.connect(("localhost",8081)) 6 #第四步 数据交换 7 user_data = "abcdefg" 8 client.send(user_data.encode("utf-8")) #数据传输必须采用二进制的格式 9 print("发送的数据:",user_data) 10 recv_data = client.recv(1024) 11 print("返回的数据:",recv_data.decode("utf-8")) 12 client.close()
先启动服务端,再执行客户端的代码,执行结果为:
但是,我们发现当我们执行完程序后,客户端和服务端的程序全都运行结束了,而我们知道服务端的程序应该是一直运行的,因此我们对服务端的程序进行改造,增加while循环,使得服务端的代码一直处于运行状态。改造如下:
1 import socket 2 #第一步 创建流套接字描述符 3 server = socket.socket() 4 #第二步 命名套接字(协议,地址,端口) 5 server.bind(("localhost",8081)) 6 while True: 7 #第三步 监听客户端socket请求 8 server.listen() 9 #建立连接 10 conn,address = server.accept() 11 #第四部 数据传输 12 rec_data = conn.recv(1024) 13 print(rec_data) 14 conn.send(rec_data.upper()) 15 #第五步骤 关闭 16 server.close()
这样,服务端的代码就一直处于等待服务状态。好了,现在让我们把焦点转到数据交换上,conn.recv(1024)这句代码的意思是接受1024个字节的数据,那么问题来了,如果服务端或客户端发送的数据超过1024个字节怎么办?其实,客户端和服务端的数据交换,大致原理是这样的滴:服务端把数据发送到一个队列中,客户端每次从这个队列中取数据。这就好比,A把苹果放在一个篮子里面,B去取,但是B每次只能取4个,如果A放了5个苹果在篮子里面,那B也只能取4个,剩下的1个,就会被其他的人拿走(可能这个人想要的是香蕉)。这样整个流程就乱了。为了解决这样的问题,我们可以先让A告诉B他放了几个苹果,然后B就知道他要取多少个了,为了突显效果,我们现在让客户端和服务端每次只接受4个字节的数据(将客户端和服务端conn.recv(1024)改成conn.recv(4))。
运行结果表明,客户端发送了5个字节是数据,服务端只取了4个字节,现在我们改造代码,让客户端和服务都循环去接收数据。改造后接近完美的服务端:
1 import socket 2 #第一步 创建流套接字描述符 3 server = socket.socket() 4 #第二步 命名套接字(协议,地址,端口) 5 server.bind(("localhost",8081)) 6 while True: 7 #第三步 监听客户端socket请求 8 server.listen() 9 # 建立连接 10 conn, address = server.accept() 11 while True: 12 try: 13 # 第四部 数据传输 14 rec_data_szie = conn.recv(4) # 先拿到要发送的数据大小 15 conn.send(b"200") # 回复客户端已经收到 16 b_real_size = 0 17 rec_data = b'' 18 while b_real_size != int(rec_data_szie.decode("utf-8")): # 循环接收数据 19 rec_data += conn.recv(4) 20 b_real_size = len(rec_data) 21 print(rec_data) 22 conn.send(str(len(rec_data.upper())).encode("utf-8")) # 发送给客户端 服务端要发送的数据大小 23 conn.recv(5) 24 conn.send(rec_data.upper()) 25 print("一次数据交换完成") 26 except ConnectionResetError as e: 27 print("客户端断开") 28 break 29 #第五步骤 关闭 30 server.close()
改造后接近完美的客户端:
1 import socket 2 #第一步 创建流套接字描述符 3 client = socket.socket() 4 #第二步 连接远程socket服务器 5 client.connect(("localhost",8081)) 6 #第四步 数据交换 7 while True: 8 user_data = input("输入数据:") 9 b_user_data = user_data.encode("utf-8") 10 b_user_data_size = str(len(b_user_data)).encode("utf-8") 11 client.send(b_user_data_size) #数据传输必须采用二进制的格式 12 client.recv(4) 13 client.send(b_user_data) 14 print("发送的数据:",user_data) 15 server_data_size = int(client.recv(4).decode()) 16 client.send(b'200') 17 real_data_size = 0 18 recv_data = b'' 19 while real_data_size!= server_data_size: 20 print(real_data_size,server_data_size) 21 recv_data += client.recv(4) 22 real_data_size = len(recv_data) 23 print("返回的数据:",recv_data.decode("utf-8")) 24 client.close()
上面的客户端和服务端就可以进行无畅通的数据交换了。
二、SocketServer
socketServer是对服务端进行封装的一个类,与我们上面自己写的服务端的代码相比有两个优点,第一,使用方便(不需要自己写循环啥的了),第二,支持多用户同时进行通信(咱们上面服务端的代码是不行的,你可以踹一踹)。
下面介绍一下如何使用socketServer来写服务端代码,步骤如下:
1、创建MyTCPHandler类(自定义类),继承socketserver.BaseRequestHandler)
2、重写handle方法,此方法为服务端处理客户端的代码逻辑
3、通过server = socketserver.TCPServer((HOST,PORT),MyTCPHandler)创建server对象
server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)(多线程)
4、server.serve_forever()等待处理请求
经过上面的步骤后,我们就可以基于SocketServer编写服务端的代码,现在把上面socket编写的服务端的代码改造成基于SocketServer的。
1 import socketserver 2 3 class MyTCPHandler(socketserver.BaseRequestHandler):#自定义类 4 def handle(self): 5 while True: 6 try: 7 # 第四部 数据传输 8 rec_data_szie = self.request.recv(4) # 先拿到要发送的数据大小 9 self.request.send(b"200") # 回复客户端已经收到 10 b_real_size = 0 11 rec_data = b'' 12 while b_real_size != int(rec_data_szie.decode("utf-8")): # 循环接收数据 13 rec_data += self.request.recv(4) 14 b_real_size = len(rec_data) 15 print(rec_data) 16 self.request.send(str(len(rec_data.upper())).encode("utf-8")) # 发送给客户端 服务端要发送的数据大小 17 self.request.recv(5) 18 self.request.send(rec_data.upper()) 19 print("一次数据交换完成") 20 except ConnectionResetError as e: 21 print("客户端断开") 22 break 23 server = socketserver.ThreadingTCPServer(("localhost",8081),MyTCPHandler) 24 server.serve_forever()
三、简易FTP实战
通过上面socket的学习,我们可开发一个简易的ftp工具用于上传和下载文件,核心代码如下,服务端:
1 import socketserver,json,os 2 3 class MyTCPHandler(socketserver.BaseRequestHandler): 4 def handle(self): 5 while True: 6 try: 7 user_data = self.request.recv(1024).decode() 8 print("服务器收到的用户数据:", user_data) 9 user_data_dic = json.loads(user_data) 10 self.request.send(b"200") 11 getattr(self,user_data_dic["action"])(user_data_dic) 12 except (ConnectionResetError,TypeError) as e: 13 print(e) 14 break 15 # except: 16 # print("系统异常!") 17 # break 18 19 def upload(self,user_data_dic): 20 real_b_data_size = 0 21 file_data_size = user_data_dic["file_size"] 22 file_name = user_data_dic["path"] + "/" + user_data_dic["file_name"].split('/')[-1] 23 print("文件名:",file_name) 24 # b_data = b'' 25 upload_process, first_upload_process = 0, 0 26 while True: 27 first_upload_process = upload_process # 记录上一次的进度 28 f = open(file_name, "ab") 29 b_data = self.request.recv(4096) 30 f.write(b_data) 31 real_b_data_size = real_b_data_size + len(b_data) 32 upload_process = (real_b_data_size * 100 // file_data_size); 33 if first_upload_process != upload_process or upload_process == 100: 34 print("上传进度:%s\n" % upload_process) 35 if real_b_data_size >= file_data_size: 36 f.close() 37 # print("b_data:", b_data) 38 # self.request.send(b"ok") 39 break 40 def ls(self,user_data_dic): 41 b_list_dir = json.dumps(os.listdir(user_data_dic["path"])).encode("utf-8") 42 self.request.recv(1024) 43 self.request.send(b_list_dir) 44 def cd(self,user_data_dic): 45 re_data_dict = {"flag":False,"path":None} 46 self.request.recv(1024) 47 path = user_data_dic["path"] + "/" + user_data_dic["dir"] 48 dir = user_data_dic["dir"] 49 if os.path.isdir(path): 50 re_data_dict["flag"] = True 51 path_list = path.split('/') 52 if dir == "..": 53 del path_list[-1],path_list[-1] 54 path_str = "" 55 if len(path_list) >= 1: 56 for path_temp in path_list: 57 path_str = path_str + path_temp + "/" 58 path_str = path_str[:-1] 59 re_data_dict["path"] = path_str 60 else: 61 re_data_dict["flag"] = False 62 else: 63 re_data_dict["path"] = path 64 b_recv_data = json.dumps(re_data_dict).encode("utf-8") 65 self.request.send(b_recv_data) 66 67 68 port = ("localhost",8888) 69 server = socketserver.ThreadingTCPServer(port,MyTCPHandler) 70 server.serve_forever()
客户端代码如下:
1 import socket,os,json 2 3 class MyTCPClient(object): 4 def __init__(self,port,path): 5 self.__conn = socket.socket() 6 self.__conn.connect(port) 7 self.path = path 8 def upload(self,file_name): 9 if os.path.isfile(file_name): 10 user_data = { 11 "file_name":None, 12 "file_size":None, 13 "action": "upload", 14 "path":self.path 15 } 16 user_data["file_name"] = file_name #文件名 17 f = open(file_name,"rb") 18 file_size = os.stat(file_name).st_size 19 user_data["file_size"] = file_size 20 b_user_data = str(json.dumps(user_data)).encode("utf-8") 21 print("用户数据:",b_user_data) 22 self.__conn.send(b_user_data) 23 b_code = self.__conn.recv(1024) 24 print("b_code:",b_code) 25 for line in f: 26 self.__conn.send(line) #发送数据 27 else: 28 print("上传成功!") 29 f.close() 30 else: 31 print("查无此文件!") 32 def help(self): 33 print("1、upload fileName\n2、download fileName\n3、cd 命令") 34 def __del__(self): 35 self.__conn.close() 36 def __tradeCenter(self,user_data_dict): 37 b_user_data = json.dumps(user_data_dict).encode("utf-8") 38 self.__conn.send(b_user_data) 39 recv_data = self.__conn.recv(1024) 40 print(recv_data) 41 self.__conn.send(b"readdly") 42 return self.__conn.recv(1024) 43 def cd(self,dir): 44 user_data_dict = {"path":self.path,"action":"cd","dir":dir} 45 recv_data = self.__tradeCenter(user_data_dict).decode() 46 recv_data_dict = json.loads(recv_data) 47 if recv_data_dict["flag"]: 48 self.path = recv_data_dict["path"] 49 print(recv_data_dict) 50 else: 51 print("不存在此目录!") 52 def ls(self,dir): 53 user_data_dict = {"path":self.path,"action":"ls"} 54 dir_str = self.__tradeCenter(user_data_dict).decode("utf-8") 55 dir_list = json.loads(dir_str) 56 print("\033[041;1m目录列表:\033[0m") 57 for file in dir_list: 58 print(file) 59 def pwd(self,arge): 60 print(self.path) 61 62 my_ftp_client = MyTCPClient(("localhost",8888),"gwx") 63 while True: 64 print("欢迎使用ftp系统!") 65 my_ftp_client.help() 66 while True: 67 data = input(my_ftp_client.path+">>") 68 if data is not '': 69 break 70 if data == 'exit': 71 break 72 temp_data = data.split(" ") 73 print(temp_data) 74 try: 75 getattr(my_ftp_client,temp_data[0])(temp_data[1]) 76 except (AttributeError,IndexError) as e: 77 print("请输入正确的命令") 78 my_ftp_client.help()
注:由于时间及个人能力有限,只供参考,谢谢!