1.socket之简单的ssh功能

2.socket之简单的ftp服务器

3.socketserver的用法

4.socketserver的多并发的实现

1.socket实现ssh服务

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

 import socket,os
server = socket.socket()
server.bind(('localhost',6969))
server.listen()
conn, addr = server.accept()
while True:
data = conn.recv(1024)
if not data:
print("client lost!")
break
print("cmd:",data)
res = os.popen(data.decode()).read()
if len(res) == 0:
res = 'cmd has no output'
print(res)
conn.send(res.encode('utf-8'))
server.close()

server端

 import socket
client = socket.socket()
client.connect(('localhost',6969))
while True:
msg = input(">>>").strip()
if len(msg) == 0:
continue
client.send(msg.encode("utf-8"))
data = client.recv(1024)
print(data.decode())
client.close()

client端

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

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

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

思路:。

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

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

更改后的代码

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

server端

 import socket
client = socket.socket()
client.connect(('localhost',6969))
while True:
msg = input(">>>").strip()
if len(msg) == 0:
continue
client.send(msg.encode("utf-8"))
data_size = int(client.recv(1024).decode())#先获得传输的数据大小
received_size = 0
data = ''
while received_size <data_size:
data += client.recv(1024).decode('GBK')
received_size = len(data)
print(data)
client.close()

client端

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

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

看一下服务器端,在接受数据时候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!再发个最终的代码:

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

server端

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

client端

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


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

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

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

获取MD5值

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.关闭文件

下面是代码

 import socket
client = socket.socket()
client.connect(('localhost',6969))
while True:
msg = input(">>>").strip()
if len(msg) == 0:
continue
client.send(msg.encode("utf-8"))
data_size = int(client.recv(1024).decode())#先获得传输的数据大小
received_size = 0
data = ''
while received_size <data_size:
data += client.recv(1024).decode('GBK')
received_size = len(data)
print(data)
client.close()

ftp_server端

 #Author__Aaron
import socket,hashlib
client = socket.socket()
client.connect(("localhost",9696))
while True:
cmd = input('cmd:')
if len(cmd) == 0:
continue
if cmd.startswith("get"):
m = hashlib.md5()
client.send(cmd.encode())
totle_size = client.recv(1024).decode()
totle_size = int(totle_size)
print("文件大小:",totle_size)
client.send(b"ready to recv file")
reveived_size = 0
cmd = cmd.split()[1]
filename = cmd.split(".")[0]+"_new."+cmd.split(".")[1]#新文件名是原文件名+‘new’
f = open(filename,"wb")
while reveived_size < totle_size:
if totle_size - reveived_size > 1024:
get_size = 1024 #防止粘包
else:
get_size = totle_size-reveived_size
data = client.recv(get_size)
f.write(data)
m.update(data)
reveived_size += len(data)
else:
print("received done")
f.close()
file_md5 = client.recv(1024).decode()
new_file_md5 = m.hexdigest()
if file_md5 == new_file_md5:
print("文件校验正确,md5=%s,文件接受完毕!"%file_md5)
else:
print("文件校验失败")
print("源文件校验码:",file_md5)
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
 import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):#继承BaseRequestHandler类。
def handle(self): #重构父类里的handle()
self.data = self.request.recv(1024).strip()
print('{}wrote:'.format(self.client_address[0]))
print(self.data)
self.request.send(self.data.upper()) #把接收的数据upper后返送 if __name__ == '__main__':
HOST,PORT = 'localhost',9999 #定义IP和端口
server = socketserver.TCPServer((HOST,PORT),MyTCPHandler) #对建立的类实例化,并传递IP和请求的类
server.serve_forever()

socket_server服务器端

客户端用最基础的就可以

 import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
msg = input('>>>')
if len(msg) == 0:
continue
client.send(msg.encode())
data = client.recv(1024).decode()
print(data)

socket_server客户端

执行,发现第一次发送,正常,第二次就报错了

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

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

socket_server服务器端改进版

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

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

多并发版

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

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

Python之socket编程进阶版的更多相关文章

  1. Python 8 - Socket编程进阶

    本节内容:    1.Socket语法及相关    2.SocketServer实现多并发 Socket语法及相关 socket 概念 socket本质上就是在2台网络互通的电脑之间架设一个通道,两台 ...

  2. Python自动化 【第八篇】:Python基础-Socket编程进阶

    本节内容: Socket语法及相关 SocketServer实现多并发 1. Socket语法及相关 sk = socket.socket(socket.AF_INET,socket.SOCK_STR ...

  3. Python之路,Day8 - Socket编程进阶

    Python之路,Day8 - Socket编程进阶   本节内容: Socket语法及相关 SocketServer实现多并发 Socket语法及相关 socket概念 socket本质上就是在2台 ...

  4. Python 3 socket 编程

    Python 3 socket编程 一 客户端/服务器架构 互联网中处处是C/S架构 1.C/S结构,即Client/Server(客户端/服务器)结构 2.在互联网中处处可见c/s架构 比如说浏览器 ...

  5. 转:Python 的 Socket 编程教程

    这是用来快速学习 Python Socket 套接字编程的指南和教程.Python 的 Socket 编程跟 C 语言很像. Python 官方关于 Socket 的函数请看 http://docs. ...

  6. 最基础的Python的socket编程入门教程

    最基础的Python的socket编程入门教程 本文介绍使用Python进行Socket网络编程,假设读者已经具备了基本的网络编程知识和Python的基本语法知识,本文中的代码如果没有说明则都是运行在 ...

  7. python之socket编程(一)

    socket之前我们先来熟悉回忆几个知识点. OSI七层模型 OSI(Open System Interconnection)参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标 ...

  8. Python:socket编程教程

    ocket是基于C/S架构的,也就是说进行socket网络编程,通常需要编写两个py文件,一个服务端,一个客户端. 首先,导入Python中的socket模块: import socket Pytho ...

  9. python学习------socket编程

    一 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务端为你提供视频 ...

随机推荐

  1. django1.10使用本地静态文件

    django1.10使用本地静态文件方法 本文介绍的静态文件使用,是指启动web站点后,访问静态资源的用法,实际静态资源地址就是一个个的url 如果没有启动web站点,只是本地调试html页面,那直接 ...

  2. 由于html元素加载导致的问题

    js中要求执行的事件是在完全加载完,但由于本地环境测试一直没发现出问题,在上线后由于网络延迟导致元素加载慢,而事件执行完,没达到预期目标. 这时就需要用到属性 readyState readyStat ...

  3. pm2-zabbix 安装与配置

    官方GITHUB路径 https://github.com/greatcare/pm2-zabbix 环境要求,zabbix-agent zabbix-sender需要安装 npm安装要求 npm i ...

  4. CUDA 编程的基本模式

    reproduced from: http://www.cnblogs.com/muchen/p/6306747.html 前言 本文将介绍 CUDA 编程的基本模式,所有 CUDA 程序都基于此模式 ...

  5. CentOS 7安装Zabbix 3.4

    01.最小化安装操作系统 02.升级系统组件到最新版本 yum -y update 03.关闭 SELinux sed -i “s/SELINUX=enforcing/SELINUX=disabled ...

  6. mysql数据库导出CSV乱码问题

    一.导出汉字为乱码 1. 鼠标右键点击选中的 csv 文件,在弹出的菜单中选择“编辑”,则系统会用文本方 式(记事本)打开该 csv 文件: 2. 打开 csv 文件后,进行“另存为”操作,在弹出的界 ...

  7. 浅谈Java代理二:Cglib动态代理-MethodInterceptor

    浅谈Java代理二:Cglib动态代理-MethodInterceptor CGLib动态代理特点: 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生 ...

  8. python学习-(__new__方法和单例模式)

    class Dog(object): __instance = None __init_flag = False def __new__(cls, name): if cls.__instance = ...

  9. bootstrap 折叠collapse失效

    手动点击折叠,然后调用折叠全部以后,在手动点击折叠项,折叠失效. 方法,折叠项是通过添加或删除".in"来实现,实现如下 $(".collapse.in").c ...

  10. 在pom.xml中引入jar包坐标的依赖范围

    A依赖B,需要在A的pom.xml文件中添加B的坐标,添加坐标时需要指定依赖范围,依赖范围包括: compile:编译范围,指A在编译时依赖B,此范围为默认依赖范围.编译范围的依赖会用在编译.测试.运 ...