Python之socket编程进阶版

Stella981
• 阅读 987

1.socket之简单的ssh功能

2.socket之简单的ftp服务器

3.socketserver的用法

4.socketserver的多并发的实现

1.socket实现ssh服务

 1.1我们现在Windows环境下试一下ssh的功能

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socket,os
 2 server = socket.socket()
 3 server.bind(('localhost',6969))
 4 server.listen()
 5 conn, addr = server.accept()
 6 while True:
 7     data = conn.recv(1024)
 8     if not data:
 9         print("client lost!")
10         break
11     print("cmd:",data)
12     res = os.popen(data.decode()).read()
13     if len(res) == 0:
14         res = 'cmd has no output'
15     print(res)
16     conn.send(res.encode('utf-8'))
17 server.close()

server端

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',6969))
 4 while True:
 5     msg = input(">>>").strip()
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode("utf-8"))
 9     data = client.recv(1024)
10     print(data.decode())
11 client.close()

client端

运行一下dir,没问题,运行一下ipconfig,貌似和跟cmd下运行的结果不一样

Python之socket编程进阶版

注意看结尾的部分,是不是少了一点,重新运行一下dir

Python之socket编程进阶版

我擦,怎么全乱了???想一想,我们在client端收的数据大小是1024,也就是说每次只能接受1024的数据。超过的部分会在一个类似buff的缓存内被阻塞等待下一次的接收。这种情况叫做“半包”。那怎么改一改?

思路:。

        1.服务端在发送数据前先获得数据大小,并发送给客户端。

   2.客户端先接受数据大小,再对根据数据大小接收数据。

更改后的代码

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socket,os
 2 server = socket.socket()
 3 server.bind(('localhost',6969))
 4 server.listen()
 5 conn, addr = server.accept()
 6 while True:
 7     data = conn.recv(1024)
 8     if not data:
 9         print("client lost!")
10         break
11     print("cmd:",data)
12     res = os.popen(data.decode()).read()
13     data_size = len(res)
14     if data_size == 0:
15         res = 'cmd has no output'
16     conn.send(str(data_size).encode())
17     conn.send(res.encode('GBK'))   #这里在调试时先用的时UTF-8,但出现unicodeDecodeError,
18                                     # 改成GBK就好了,问题原因未解               
19 server.close()

server端

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',6969))
 4 while True:
 5     msg = input(">>>").strip()
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode("utf-8"))
 9     data_size = int(client.recv(1024).decode())#先获得传输的数据大小
10     received_size = 0
11     data = ''
12     while received_size <data_size:
13         data += client.recv(1024).decode('GBK')
14         received_size = len(data)
15     print(data)
16 client.close()

client端

这里有个问题还待解:原先数据在传输的时候用的转码格式时UTF-8,但在超出1024的时候会出现UnicodeDecodeError,改用GBK就好了,不知道为什么。

现在我们把他们放在linux里试一下,报错了!

Python之socket编程进阶版

看一下服务器端,在接受数据时候891后面跟着ifconfig的内容,891是ifconfig得到的数据的size。

(这种情况在linux环境下很容易复现,3.0以上版本在windows下不太容易出现,2.7是很容易出现的。所以我把它放在linux中演示一下。)

这里就需要了解一个概念:粘包

我们看一下服务端的代码

#服务端
conn.send(str(data_size).encode())
conn.send(res.encode('GBK'))

 在数据的传输中,如果有两个数据包是先后紧挨着发送时,并不是按顺序一次次的接收,很有可能在接收端第一次接受时把两个包粘在一起接收了。这就是“粘包”。

在码代码的时候,一定要防止出现这种情况。 

最简单粗暴的方法,就是在两个send过程中加一个time.sleep,让缓冲区超时,在这个时间里客户端会先把第一个包取出来,第二个包再过来等着客户端的读取。可是这种思路也太low了,在对时间要求比较高的环境中也是个找骂的方法(做个交易软件,你要是敢sleep个0.5秒那可是几千万上下啊!!!)

所以改一下思路,我们让服务器端发送第一个包的时候等客户端返回个响应,再发数据包。只要两个包不先后一起发送就OK!再发个最终的代码:

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socket,os
 2 server = socket.socket()
 3 server.bind(('localhost',6969))
 4 server.listen()
 5 conn, addr = server.accept()
 6 while True:
 7     data = conn.recv(1024)
 8     if not data:
 9         print("client lost!")
10         break
11     print("cmd:",data)
12     res = os.popen(data.decode()).read()
13     data_size = len(res)
14     if data_size == 0:
15         res = 'cmd has no output'
16     print(str(data_size))
17     conn.send(str(data_size).encode())
18     client_ack = conn.recv(1024)   #等待服务器响应,服务器无响应的话就阻塞在这里
19     conn.send(res.encode('GBK'))   #这里在调试时先用的时UTF-8,但出现unicodeDecodeError,
20                                     # 改成GBK就好了,问题原因未解
21 server.close()

server端

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',6969))
 4 while True:
 5     msg = input(">>>").strip()
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode("utf-8"))
 9     data_size = int(client.recv(1024).decode())#先获得传输的数据大小
10     client.send('准备接收数据'.encode())#在这里给服务器发一个状态,服务器接收后开始发数据
11     received_size = 0
12     data = ''
13     while received_size <data_size:
14         data += client.recv(1024).decode('GBK')
15         received_size = len(data)
16     print(data)
17 client.close()

client端

服务器在发送第一条指令后,用一个接收指令的代码让他阻塞住,客户端在接收到第一条指令后给服务器发送一条信息,服务器接收后再发送后面的数据。


2.下面,我们把ssh服务的代码稍微改一下,就可以做一个ftp的服务器了!!

       首先,在用ftp传输文件时,有个功能要了解一下:md5,就是一个文件的数字签名,我们在传输前获得文件的md5编码,在传输后再获取编码,把两个编码对比一下可获得文件的一致性。作为文件校验的功能使用。所以这里我们要回顾一下hashlib模块里md5的用法

Python之socket编程进阶版 Python之socket编程进阶版

 1 import hashlib
 2 data1 = 'test1'
 3 data2 = 'test2'
 4 data ='test1test2'
 5 m1 = hashlib.md5()
 6 m2 = hashlib.md5()
 7 m1.update(data1.encode())#先对data1进行编码,获得第一个字符串的md5文件
 8 m1.update(data2.encode())#在第一个文件的MD5上直接对第二个字符串编码
 9 m2.update(data.encode())#直接字符串进行编码
10 print('m1:',m1.hexdigest())

获取MD5值

Python之socket编程进阶版 Python之socket编程进阶版

m1: beff3fcba56f29677c5d52b843df365e
m2: beff3fcba56f29677c5d52b843df365e

运行结果

我们在对文件进行MD5编码时候,直接把文件用readlines读出来是不大现实的(数据是先读在内存中,小文件没问题,超过几个G的文件就把内存撑爆了)所以需要用readline把数据一行行读出来,然后不停用update把MD5值更新出来。

  其次,我们来捋一下ftp server端的工作顺序

  1.读取文件名

  2.检测文件是否存在

  3.打开文件

  4.检测文件大小并发送给客户端

  5.发送文件

  6.等待客户端确认

  7.发送md5给客户端。

客户端的工作流程

  1.输入指令

  2.对指令解耦并发送给服务器

  3.接收文件大小

  4.建立新的空白文件

  5.逐行接收文件并生成相应的MD5

  6.接收原文件的MD5,和新文件进行校验

  7.关闭文件

下面是代码

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',6969))
 4 while True:
 5     msg = input(">>>").strip()
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode("utf-8"))
 9     data_size = int(client.recv(1024).decode())#先获得传输的数据大小
10     received_size = 0
11     data = ''
12     while received_size <data_size:
13         data += client.recv(1024).decode('GBK')
14         received_size = len(data)
15     print(data)
16 client.close()

ftp_server端

Python之socket编程进阶版 Python之socket编程进阶版

 1 #Author__Aaron
 2 import socket,hashlib
 3 client = socket.socket()
 4 client.connect(("localhost",9696))
 5 while True:
 6     cmd = input('cmd:')
 7     if len(cmd) == 0:
 8         continue
 9     if cmd.startswith("get"):
10         m = hashlib.md5()
11         client.send(cmd.encode())
12         totle_size = client.recv(1024).decode()
13         totle_size = int(totle_size)
14         print("文件大小:",totle_size)
15         client.send(b"ready to recv file")
16         reveived_size = 0
17         cmd = cmd.split()[1]
18         filename = cmd.split(".")[0]+"_new."+cmd.split(".")[1]#新文件名是原文件名+‘new’
19         f = open(filename,"wb")
20         while reveived_size < totle_size:
21             if totle_size - reveived_size > 1024:
22                 get_size = 1024             #防止粘包
23             else:
24                 get_size = totle_size-reveived_size
25             data = client.recv(get_size)
26             f.write(data)
27             m.update(data)
28             reveived_size += len(data)
29         else:
30             print("received done")
31             f.close()
32         file_md5 = client.recv(1024).decode()
33         new_file_md5 = m.hexdigest()
34         if file_md5 == new_file_md5:
35             print("文件校验正确,md5=%s,文件接受完毕!"%file_md5)
36         else:
37             print("文件校验失败")
38             print("源文件校验码:",file_md5)
39             print("新文件校验码:",new_file_md5)

ftp_client

客户端在接收数据防止粘包的地方做了些小改进:接收数据量用变量来处理,最后一个循环直接剩下的数据量,前期变量值都是1024。

这里还存在一些问题待改进:

1.在服务器端只判定了有没有文件的存在,如果有能正常工作,如果没有的话就会卡死。

2.客户端接收MD5后只进行校验,如果错误只提示错误并无相应的处理。

3.只做了数据的单项传输(get),其实还应该有从客户端上传数据给服务器(put)。

留在后面解决把!


3.socketserver模块的用法

https://docs.python.org/2/library/socketserver.html#SocketServer.BaseServer.handle_request

socketserver模块有四个类

1.TCPServer

class socketserver.TCPServer(server_address,RequestHanddlerClass,bind_and_active=True)

2UDPServer

class socketserver.UDPServer(server_address,RequestHanddlerClass,bind_and_active=True)

3. UnixStreamServer,类似于TCPServer提供面向数据流的套接字连接,但是旨在UNIX平台上可用;

4. UnixDatagramServer,类似于UDPServer提供面向数据报的套接字连接,但是旨在UNIX平台上可用;

其中常用的是前两个。

+------------+
| BaseServer |
+------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+

这里引用一下python官网上对SocketServer使用方法的描述Creating a server requires several steps.Frist,you must creat a request handler class by subclassing the BaseRequestHandler class and overriding its Handle()method;this method will process incoming requests.Second,you must instantiate one of the server classes,passing it the server's address and the request handler class.Then call the handle_request() or serv_forever() method of the server object to process ond or many requests.Finally,call server_close() to close the socket首先,必须创建一个请求处理类,并且这个类要继承BaseRequestHandler,并且还要重构父类里的handle()handle里处理所有和客户端的交互其次,要实例化一个server class(四个中的一个,举例TCPServer),并且把server的ip和第一步创建的请求处理类当作参数传递给TCPServer。接下来可以调用server.handle_request()(只处理一个请求)或server.server_forever()(处理多个请求,永远执行)最终,调用server_close()关闭。现在我们写一个最简单的socket server

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socketserver
 2 class MyTCPHandler(socketserver.BaseRequestHandler):#继承BaseRequestHandler类。
 3     def handle(self):  #重构父类里的handle()
 4         self.data = self.request.recv(1024).strip()
 5         print('{}wrote:'.format(self.client_address[0]))
 6         print(self.data)
 7         self.request.send(self.data.upper()) #把接收的数据upper后返送
 8 
 9 if __name__ == '__main__':
10     HOST,PORT = 'localhost',9999   #定义IP和端口
11     server = socketserver.TCPServer((HOST,PORT),MyTCPHandler)   #对建立的类实例化,并传递IP和请求的类
12     server.serve_forever()

socket_server服务器端

客户端用最基础的就可以

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',9999))
 4 while True:
 5     msg = input('>>>')
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode())
 9     data = client.recv(1024).decode()
10     print(data)

socket_server客户端

执行,发现第一次发送,正常,第二次就报错了Python之socket编程进阶版

原因就是handle里处理完一次就结束了,客户端就断开了。所以要在handle里加while。

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socketserver
 2 class MyTCPHandler(socketserver.BaseRequestHandler):#继承BaseRequestHandler类。
 3     def handle(self):  #重构父类里的handle()
 4         while True:
 5             try:
 6                 self.data = self.request.recv(1024).strip()
 7                 print('{}wrote:'.format(self.client_address[0]))
 8                 print(self.data)
 9                 self.request.send(self.data.upper()) #把接收的数据upper后返送
10             except ConnectionResetError as e:#在这里处理了客户端掉线
11                 print('err',e)
12                 break
13 
14 if __name__ == '__main__':
15     HOST,PORT = 'localhost',9999   #定义IP和端口
16     server = socketserver.TCPServer((HOST,PORT),MyTCPHandler)   #对建立的类实例化,并传递IP和请求的类
17     server.serve_forever()

socket_server服务器端改进版

最后,我们试一下socketserver的多并发

Python之socket编程进阶版 Python之socket编程进阶版

 1 import socketserver
 2 class MyTCPHandler(socketserver.BaseRequestHandler):#继承BaseRequestHandler类。
 3     def handle(self):  #重构父类里的handle()
 4         while True:
 5             try:
 6                 self.data = self.request.recv(1024).strip()
 7                 print('{}wrote:'.format(self.client_address[0]))
 8                 print(self.data)
 9                 self.request.send(self.data.upper()) #把接收的数据upper后返送
10             except ConnectionResetError as e:#在这里处理了客户端掉线
11                 print('err',e)
12                 break
13 
14 if __name__ == '__main__':
15     HOST,PORT = 'localhost',9999   #定义IP和端口
16     server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)   #对建立的类实例化,并传递IP和请求的类
17     server.serve_forever()

多并发版

把TCPServer改成ThreadingTCPServer,就实现了多并发。可以多开几个客户端试一下

Python之socket编程进阶版

可以看到服务器端可以同时接收多个客户端的信息。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这