网络编程

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的套接字


  1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
  2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
  3. 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()
 
原文地址:https://www.cnblogs.com/kunixiwa/p/7401821.html