网络编程基础---网络通讯原理、ssh远程执行命令、粘包问题处理、文件传输处理

一、网络通信原理

  osi七层:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

  osi七层也称tcp/ip五层(应用层、传输层、网络层、网络接口层【数据链路层、物理层】),或ip四层(应用层、传输层、网络层、网络接口层)

  物理层:主要基于电信号特性发送高低压(电信号),高电压---1,低电压---0。

  数据链路层:定义电信号的分组方式。遵循以太网协议(ethernet):一组电信号构成一个数据包,叫做帧;每一帧分成‘报头’head’ 和 数据‘data’

    【head---data】

    报头’head’ :(固定的18个字节),发送者/源地址,6字节;接受者/目标地址,6字节;数据类型,6字节。 

    数据‘data’:(最短46字节,最长1500字节)

    mac地址:发送端和接收端的地址就是mac地址。

    可以通过广播的方式进行计算机的通信。

  网络层:功能:引入一套新的地址来区分不同的广播域和子网,这套地址就是网络地址。

    规定网络地址的协议叫IP协议,它定义的地址为ip地址(ipv4版本)

    ip地址分为两部分:网络部分(标识子网)和主机部分(标识主机)

    ip数据包:

      ip数据包也分为head和data部分,无须为ip包定义单独的栏位,直接放入以太网包的data部分

      head:长度为20到60字节

      data:最长为65,515字节。

      而以太网数据包的”数据”部分,最长有1500字节。因此,当IP数据包超过了1500字节,它就需要分割成几个以太网数据包,分开发送了。

    ARP协议:功能-----根据ip地址,解析出mac地址。

  传输层: 通过端口来标识主机上应用程序。功能-----建立端口和端口之间的通信。(tcp协议,udp协议)  

    tcp协议:可靠传输,效率高,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。

    【以太网头---ip头---tcp头---数据】

    udp协议:不可靠传输,效率低,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。

    【以太网头---ip头---udp头---数据】

  应用层:功能----规定应用程序的数据规格。

  stocket

    标识网络进程:【ip地址+协议+端口】

    stocket(套接字),是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中的通讯。

    工作原理:如图

            

简单的套接字使用:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买手机
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 尽可能块的回收端口,防止端口被占用
phone.bind(('127.0.0.1',8000))  # 插手机卡
phone.listen(5)  # 规定一次最多接几个电话
print('等待连接')
conn,addr= phone.accept()  # 接电话

while True:
    try:
        msg = conn.recv(1024)  # 规定传输最多的bytes数量
        print('来自客户端的数据',msg)
        conn.send(msg.upper())  # 发信息
    except ConnectionResetError:
        break

conn.close()  # 挂电话
phone.close()  # 关机
服务端
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8000))

while True:
    cmd = input('>>>').strip()
    if not cmd: continue
    phone.send(cmd.encode('utf-8'))  # 传过去的必须指定字符编码的格式,因为最后需要操控计算机硬件的需要二进制
    msg=phone.recv(1024)
    print(msg)

phone.close()
客户端

加上连接循环和通信循环的服务端,保持对多个客户端的操作

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买手机
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 尽可能块的回收端口,防止端口被占用
phone.bind(('127.0.0.1',8000))  # 插手机卡
phone.listen(5)  # 规定一次最多接几个电话
print('等待连接')
while True:
    conn,addr= phone.accept()  # 接电话
    while True:
        try:
            msg = conn.recv(1024)  # 规定传输最多的bytes数量
            print('来自客户端的数据',msg)
            conn.send(msg.upper())  # 发信息
        except ConnectionResetError:
            break
    
    conn.close()  # 挂电话
phone.close()  # 关机
服务端(链接循环)

 ssh远程执行命令

  命令windows:

    dir:查看某一个文件夹下的子文件夹名与子文件夹名

    ipconfig:查看本地网卡的ip信息

    tasklist:查看运行的结果

  linux:

    ls:查看某一个文件夹下的子文件夹名与子文件夹名

    ifconfig:查看本地网卡的ip信息

    ps  aux:查看运行的结果

 实现代码

import socket
import subprocess  # 执行系统命令的模块

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1', 9991))  # 0-65535:0-1024给操作系统使用
phone.listen(5)

print('starting...')
while True:  # 链接循环
    conn, client_addr = phone.accept()
    print(client_addr)

    while True:  # 通信循环
        try:
            # 1、收命令
            cmd = conn.recv(1024)
            if not cmd: break  # 适用于linux操作系统

            # 2、执行命令,拿到结果
            obj = subprocess.Popen(cmd.decode('GBK'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            # 3、把命令的结果返回给客户端
            print(len(stdout) + len(stderr))
            conn.send(stdout + stderr)  # +是一个可以优化的点

        except ConnectionResetError:  # 适用于windows操作系统
            break
    conn.close()

phone.close()
服务端
import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.connect(('127.0.0.1', 9991))

while True:
    # 1、发命令
    cmd = input('>>: ').strip()  # ls /etc
    if not cmd: continue
    phone.send(cmd.encode('GBK'))

    # 2、拿命令的结果,并打印
    data = phone.recv(1024)  # 1024是一个坑
    print(data.decode('GBK'))

phone.close()
客户端

  粘包现象:1.发送端需要等缓冲区满才会把数据发送出去,造成粘包(发送数据间隔很短,数据很小)。2.接收方不及时接收缓冲区的包,造成多个包接收(部分数据遗留在缓冲区)

  解决粘包问题:

import json
import socket
import struct
import subprocess

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1', 9909))  # 0-65535:0-1024给操作系统使用
phone.listen(5)

print('starting...')
while True:  # 链接循环
    conn, client_addr = phone.accept()
    print(client_addr)

    while True:  # 通信循环
        try:
            # 1、收命令
            cmd = conn.recv(8096)
            if not cmd: break  # 适用于linux操作系统

            # 2、执行命令,拿到结果
            obj = subprocess.Popen(cmd.decode('GBK'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            # 3、把命令的结果返回给客户端
            # 第一步:制作固定长度的报头
            header_dic = {
                'filename': 'a.txt',
                'md5': 'xxdxxx',
                'total_size': len(stdout) + len(stderr)
            }

            header_json = json.dumps(header_dic)

            header_bytes = header_json.encode('GBK')

            # 第二步:先发送报头的长度
            conn.send(struct.pack('i', len(header_bytes)))

            # 第三步:再发报头
            conn.send(header_bytes)

            # 第四步:再发送真实的数据
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionResetError:  # 适用于windows操作系统
            break
    conn.close()

phone.close()
服务端
import json
import socket
import struct

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 9909))

while True:
    # 1、发命令
    cmd = input('>>: ').strip()  # ls /etc
    if not cmd: continue
    phone.send(cmd.encode('GBK'))

    # 2、拿命令的结果,并打印

    # 第一步:先收报头的长度
    obj = phone.recv(4)
    header_size = struct.unpack('i', obj)[0]

    # 第二步:再收报头
    header_bytes = phone.recv(header_size)

    # 第三步:从报头中解析出对真实数据的描述信息
    header_json = header_bytes.decode('GBK')
    header_dic = json.loads(header_json)
    print(header_dic)
    total_size = header_dic['total_size']

    # 第四步:接收真实的数据
    recv_size = 0
    recv_data = b''
    while recv_size < total_size:
        res = phone.recv(1024)  # 1024是一个坑
        recv_data += res
        recv_size += len(res)

    print(recv_data.decode('utf-8'))

phone.close()
客户端

  文件传输

import socket
import subprocess
import struct
import json
import os

share_dir=r'#文件路径'

def get(conn,cmds):
    filename = cmds[1]

    # 3、以读的方式打开文件,读取文件内容发送给客户端
    # 第一步:制作固定长度的报头
    header_dic = {
        'filename': filename,  # 'filename':'1.mp4'
        'md5': 'xxdxxx',
        'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename))
    }

    header_json = json.dumps(header_dic)

    header_bytes = header_json.encode('utf-8')

    # 第二步:先发送报头的长度
    conn.send(struct.pack('i', len(header_bytes)))

    # 第三步:再发报头
    conn.send(header_bytes)

    # 第四步:再发送真实的数据
    with open('%s/%s' % (share_dir, filename), 'rb') as f:
        # conn.send(f.read())
        for line in f:
            conn.send(line)

def put(conn,cmds):
    pass

def run():
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8912)) #0-65535:0-1024给操作系统使用
    phone.listen(5)

    print('starting...')
    while True: # 链接循环
        conn,client_addr=phone.accept()
        print(client_addr)

        while True: #通信循环
            try:
                #1、收命令
                res=conn.recv(8096) # b'put 1.mp4'
                if not res:break #适用于linux操作系统

                #2、解析命令,提取相应命令参数
                cmds=res.decode('utf-8').split() #['put','1.mp4']
                if cmds[0] == 'get':
                    get(conn,cmds)
                elif cmds[0] == 'put':
                    input(conn,cmds)


            except ConnectionResetError: #适用于windows操作系统
                break
        conn.close()

    phone.close()


if __name__ == '__main__':
    run()
服务端
import json
import socket
import struct

download_dir = r'下载路径'


def get(phone, cmds):
    # 2、以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户的新文件
    # 第一步:先收报头的长度
    obj = phone.recv(4)
    header_size = struct.unpack('i', obj)[0]

    # 第二步:再收报头
    header_bytes = phone.recv(header_size)

    # 第三步:从报头中解析出对真实数据的描述信息
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    '''
            header_dic={
                'filename': filename, #'filename':'1.mp4'
                'md5':'xxdxxx',
                'file_size': os.path.getsize(filename)
            }
    '''
    print(header_dic)
    total_size = header_dic['file_size']
    filename = header_dic['filename']

    # 第四步:接收真实的数据
    with open('%s/%s' % (download_dir, filename), 'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = phone.recv(1024)  # 1024是一个坑
            f.write(line)
            recv_size += len(line)
            print('总大小:%s   已下载大小:%s' % (total_size, recv_size))


def put(phone, cmds):
    pass


def run():
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    phone.connect(('127.0.0.1', 8912))

    while True:
        # 1、发命令
        inp = input('>>: ').strip()  # get a.txt
        if not inp: continue
        phone.send(inp.encode('utf-8'))

        cmds = inp.split()  # ['get','a.txt']
        if cmds[0] == 'get':
            get(phone, cmds)
        elif cmds[0] == 'put':
            put(phone, cmds)

    phone.close()


if __name__ == '__main__':
    run()
客户端

  基于UDP协议的介绍

    发送:sendto('msg','ip')

    接收:recvfrom()

原文地址:https://www.cnblogs.com/Holmes-98/p/14460822.html