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. Python 图示集绵

    http://nbviewer.jupyter.org/github/pyecharts/pyecharts-users-cases/blob/master/notebook-users-cases/ ...

  2. maven 配置 阿里仓库

    <mirror><id>nexus-aliyun</id><mirrorOf>central</mirrorOf><name>N ...

  3. Spring @ControllerAdvice @ExceptionHandler 全局处理异常

    对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚. 如此一来, ...

  4. STOP OUR NEGATIVE THOUGHTS

    Do you ever feel like you're in over your head and at any moment you're going to burst? You're not a ...

  5. mac通过命令行获取证书和配置文件过期时间

      背景:ios打包证书的profile配置文件过期了,导致以前已经打完的测试包不能安装.所以需要加上检测机制,在打包时提示证书是否将要过期,如果要过期了给出提示   方案: 1.查找profile配 ...

  6. PHP使用UTF8编码读取ACCESS的乱码问题解决方案(转)

    PHP使用UTF8编码读取ACCESS的乱码问题解决方案 http://it.xwstudy.com/readnews.php?id=627 来源:本站编辑 发布日期:2013-05-27 已有 17 ...

  7. 165. Compare Version Numbers (String)

    Compare two version numbers version1 and version2. If version1 > version2 return 1, if version1 & ...

  8. 5. Longest Palindromic Substring (DP)

    Given a string S, find the longest palindromic substring in S. You may assume that the maximum lengt ...

  9. Matches Game

    Matches Game http://poj.org/problem?id=2234 Time Limit: 1000MS   Memory Limit: 65536K Total Submissi ...

  10. c中堆栈的理解

    一直对堆栈的使用以及全局变量.静态全局变量.局部变量.静态局部变量.初始化的全局变量.未初始化的全局变量.初始化的局部变量.未初始化的局部变量理解的不是很清楚,今天抽个时间来总结以下这方面的知识: 1 ...