网络编程
Posted wateligx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程相关的知识,希望对你有一定的参考价值。
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()
以上是关于网络编程的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段14——Vue的axios网络请求封装
VSCode自定义代码片段14——Vue的axios网络请求封装