osi模型
学习socket需要了解一些网络知识,其中osi模型为基础~~
互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层
我们将应用层,表示层,会话层并作应用层,从tcp/ip五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议
理解网络之中的TCP通信之三次握手四次挥手
交互
通过使用python的socket模块实现简单的客户端与服务端的交互
#服务端,我们把网络交互看作是打电话 import socket #买手机 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#socket.AF_INET基于网络的套接字,sock_stream流式套接字 tcp,sock_SOCK_DGRAM 数据报协议 utp #插卡 phone.bind(('127.0.0.1',8080))#唯一标识软件up+端口 #开机 phone.listen(5)#监听,由于单线程会有1个正常通信,最大5个半连接 #等电话连接 while True: conn,client_addr = phone.accept()#接收 print(conn,client_addr) while True: try:#当客户端当方面断开连接时为避免服务端异常报错使用异常处理 #基于建立的连接,收发消息 client_data = conn.recv(1024) print(client_data) if not client_data:break#不能为空,收到空服务端不会返回消息 conn.send(client_data.upper()) except Exception: break #挂电话 conn.close() #关机 phone.close()
#客户端 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#sock_stream流式套接字 tcp,sock_SOCK_DGRAM 数据报协议 utp phone.connect(('127.0.0.1',8080)) while True: msg = input('>>>').strip() if not msg:continue phone.send(msg.encode('utf-8')) server_data = phone.recv(1024) print(server_data.decode('utf-8')) phone.close()
解决粘包问题
由于tcp协议是流式协议,所以连续传输会出现数据粘连的情况,要想准确接收想收到的数据,需要在发送数据前发送个报文头,来标识数据的长度。
打包模块struck
该模块可以把一个类型,如数字,转成固定长度的bytes
>>> struct.pack('i',1111111111111)
解决粘包的流程为:
客户端订制报头
head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}#报头包括data的一些信息 head_json=json.dumps(head_dic)#为了能够传输需转化为json对象 head_json_bytes=bytes(head_json,encoding=self.coding)#转化为字节
客户端将报文头打包成4个字节
head_struct=struct.pack('i',len(head_json_bytes))
客户端发送报文头长度
self.socket.send(head_struct)
客户端发送报文头
self.socket.send(head_json_bytes)
客户端发送数据
with open(filename,'rb') as f: for line in f: self.socket.send(line)
服务端接收头长度4
head_struct = self.conn.recv(4)
服务端解报文头包得到字典中需要的key,真是发送数据的长度
head_len = struct.unpack('i', head_struct)[0]
服务端接收真实数据长度,并以utf-8解码,解json
head_json = self.conn.recv(head_len).decode('utf-8')
head_dic = json.loads(head_json)
socketserver实现并发
服务端代码
#服务端固定格式 class FtpServer(socketserver.BaseRequestHandler): def handle(self):#链路循环每来一个链接变产生一个链接对象 pass ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer) ftpserver.serve_forever()
import socketserver class FtpServer(socketserver.BaseRequestHandler): coding = 'utf-8' def handle(self): while True: res = self.request.recv(1024) res = res.decode('utf-8') res = json.loads(res) if hasattr(self,res['cmd']):#可在传输中定义要执行的功能 func = getattr(self, res['cmd']) func(res) print(res) def login(self,res): ''' 登录接口 ''' pass def register(self,res): ''' 注册接口 :param res: :return: ''' pass ftpserver = socketserver.ThreadingTCPServer(('127.0.0.1',8088),FtpServer) ftpserver.serve_forever()
基于UDP的套接字
- TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
- UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
- tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
#基于udp的server端 from socket import * udp_server = socket(AF_INET,SOCK_DGRAM)#数据报 udp_server.bind(('127.0.0.1',8080)) #不需要监听连接listen #不需要链接循环accpt while True:
#不会产生粘包,当一条消息没有接收完整,就会丢失 data,client_addr = udp_server.recvfrom(1024)#最大接受512字节 例如:dns传输,qq消息传输 print(data,client_addr) udp_server.sendto(data.upper(),client_addr) #发消息不会等客户端确认,便会删除自身缓存中的内容
#基于udp的client端 from socket import * client_socket = socket(AF_INET,SOCK_DGRAM) #不需要conn服务端 while True: msg = input('>>>') client_socket.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) data,server_addr = client_socket.recvfrom(1024) print(data.decode('utf-8'))
利用socket实现并发
import socketserver #并发udp客户端 class MyUdpHandler(socketserver.BaseRequestHandler): def handle(self): print(self.request) self.request[1].sendto(self.request[0].upper(),self.client_address)#客户端地址 if __name__ == '__main__': s = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyUdpHandler) s.serve_forever()
#客户端 from socket import * client_socket = socket(AF_INET,SOCK_DGRAM) #不需要conn服务端 while True: msg = input('>>>') client_socket.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) data,server_addr = client_socket.recvfrom(1024) print(data.decode('utf-8'))
paramiko模块
1、paramiko是一个用于做远程控制的模块,使用该模块可以对远程服务器进行命令或文件操作,值得一说的是,fabric和ansible内部的远程管理就是使用的paramiko来现实。
下载:pip3 install paramiko #在python3中
pycrypto,由于 paramiko 模块内部依赖pycrypto,所以先下载安装pycrypto #在python2中 pip3 install pycrypto pip3 install paramiko 注:如果在安装pycrypto2.0.1时发生如下错误 command 'gcc' failed with exit status 1... 可能是缺少python-dev安装包导致 如果gcc没有安装,请事先安装gcc 在python2中
2、用于连接远程服务器并执行基本命令
基于用户名密码连接
import paramiko # 创建SSH对象 ssh = paramiko.SSHClient() # 允许连接不在know_hosts文件中的主机 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接服务器 ssh.connect(hostname='120.92.84.249', port=22, username='root', password='xxx') # 执行命令 stdin, stdout, stderr = ssh.exec_command('df') # 获取命令结果 result = stdout.read() print(result.decode('utf-8')) # 关闭连接 ssh.close()
import paramiko transport = paramiko.Transport(('120.92.84.249', 22)) transport.connect(username='root', password='xxx') ssh = paramiko.SSHClient() ssh._transport = transport stdin, stdout, stderr = ssh.exec_command('df') res=stdout.read() print(res.decode('utf-8')) transport.close() SSHClient 封装 Transport
3 基于公钥密钥连接
客户端文件名:id_rsa
服务端必须有文件名:authorized_keys(在用ssh-keygen时,必须制作一个authorized_keys,可以用ssh-copy-id来制作)
import paramiko private_key = paramiko.RSAKey.from_private_key_file('/tmp/id_rsa') # 创建SSH对象 ssh = paramiko.SSHClient() # 允许连接不在know_hosts文件中的主机 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接服务器 ssh.connect(hostname='120.92.84.249', port=22, username='root', pkey=private_key) # 执行命令 stdin, stdout, stderr = ssh.exec_command('df') # 获取命令结果 result = stdout.read() print(result.decode('utf-8')) # 关闭连接 ssh.close()
import paramiko private_key = paramiko.RSAKey.from_private_key_file('/tmp/id_rsa') transport = paramiko.Transport(('120.92.84.249', 22)) transport.connect(username='root', pkey=private_key) ssh = paramiko.SSHClient() ssh._transport = transport stdin, stdout, stderr = ssh.exec_command('df') result=stdout.read() print(result.decode('utf-8')) transport.close() SSHClient 封装 Transport
SFTPClient
基于用户名密码上传下载
import paramiko transport = paramiko.Transport(('120.92.84.249',22)) transport.connect(username='root',password='xxx') sftp = paramiko.SFTPClient.from_transport(transport) # 将location.py 上传至服务器 /tmp/test.py sftp.put('/tmp/id_rsa', '/etc/test.rsa') # 将remove_path 下载到本地 local_path sftp.get('remove_path', 'local_path') transport.close()
import paramiko private_key = paramiko.RSAKey.from_private_key_file('/tmp/id_rsa') transport = paramiko.Transport(('120.92.84.249', 22)) transport.connect(username='root', pkey=private_key ) sftp = paramiko.SFTPClient.from_transport(transport) # 将location.py 上传至服务器 /tmp/test.py sftp.put('/tmp/id_rsa', '/tmp/a.txt') # 将remove_path 下载到本地 local_path sftp.get('remove_path', 'local_path') transport.close()