PythonStudy——socket 网络编程

网络开发架构

1.C/S架构

即:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。

这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。

2.B/S架构

B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。

Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。

 SOCKET 概念

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

1.SOCKET 套接字的发展历史

套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

  基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

  基于网络类型的套接字家族

套接字家族的名字:AF_INET

(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

TCP协议和UDP协议

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

 

半连接数

三次握手没有完成 称之为半连接

原因1 恶意客户端没有返回第三次握手信息

原因2 服务器没空及时处理你的请求

socket中 listen(半连接最大数量)

粘包问题

TCP流式协议, 数据之间没有分界, 就像水 一杯水和一杯牛奶倒在一起了!

UDP 用户数据报协议

粘包 仅发生在TCP协议中

  1. 发送端 发送的数据量小 并且间隔短 会粘

  2. 接收端 一次性读取了两次数据的内容 会粘

  3. 接收端 没有接收完整 剩余的内容 和下次发送的粘在一起

无论是那种情况,其根本原因在于 接收端不知道数据到底有多少

解决方案就是 提前告知接收方 数据的长度

粘包解决方案

先发长度给对方 再发真实数据

#发送端

1.使用struct 将真实数据的长度转为固定的字节数据

2.发送长度数据

3.发送真实数据

接收端

1.先收长度数据 字节数固定

2.再收真实数据 真实可能很长 需要循环接收

发送端和接收端必须都处理粘包 才算真正的解决了

自定义报头

当需要在传输数据

发送端

        1.发送报头长度

  1. 发送报头数据

  2. 发送文件内容

接收端

接收报头长度

接收报头信息

接收文件内容

基于TCP的远程CMD服务器

服务器端

import socket
import subprocess
import struct
import smallTool

# 生成基于TCP的套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 套接字对象绑定
server.bind(("127.0.0.1",8888))
# 设置套接字对象的监听器
server.listen(5)

# 双层循环
while True:

    client,ip_port = server.accept()
    print("客户端连接成功!!!")
    while True:

        try:
            # 接收客户端传入的cmd命令
            cmd = smallTool.recv_data(client)

            if not cmd:
                break
            print("客户端发起命令>>> %s " %cmd)

            # 将二进制的cmd数据转成字符串形式
            p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

            # 不要先读取管道内的错误信息,会卡住!!
            cmd_data = p.stdout.read()
            cmd_err = p.stderr.read()

            # 服务器产生的信息
            len_size = len(cmd_data) + len(cmd_err)
            print("服务器产生并且即将打印 %s 字节的数据!!!"%len_size)

            # 将即将发送的信息长度 提前告知 客户端,做 struct二进制转化
            len_bytes = struct.pack('q',len_size)
            client.send(len_bytes)

            client.send(cmd_data + cmd_err)
        except ConnectionAbortedError as e:
            print("客户端断开了连接!!!",e)
            break
    client.close()

客户端

'''
客户端输入指令
服务器端接收指令 最后返回执行结果
'''


import socket
import struct
import smallTool

client = socket.socket()

try:
    client.connect(('127.0.0.1',8888))
    print('成功建立连接!!!!')

    while True:
        msg = input("请输入要执行的指令>>>>>>").strip()

        # 输入q 则退出
        if msg == 'q': break
        # 空消息则重新输入
        if not msg : continue

        # 1. 发送指令的长度  struct将指令的长度(utf-8)转换成网络字节序,并发送
        len_bytes = struct.pack('q',len(msg.encode("utf-8")))
        client.send(len_bytes)

        # 2. 发送指令
        client.send(msg.encode("utf-8"))

        # 调用工具模块的接收数据方法
        data = smallTool.recv_data(client)
        print(data.decode("GBK"))

    client.close()

# 抛出异常
except ConnectionAbortedError as e:
    print("服务器连接失败了!!!",e)
except ConnectionResetError as e:
    print("服务器挂了!", e)
    client.close()

except Exception as e:
    raise e

模块

import struct


# 定义一个方法实现从socket对象拿到其所拥有的数据,
# 此数据经过struct模块处理,是一个元组信息,需要根据索引提取值
def recv_data(client):

    # 建立一个表示接收对端全部原始数据长度的变量,8个字节 足矣
    client_info_bytes = client.recv(8)

    # 转换为整形
    SERVSIZ = struct.unpack("q",client_info_bytes)[0]
    print("服务器返回了 %s 长度的数据"% SERVSIZ)

    # 以上操作完成之后再进行真实数据的接收
    # 循环分片式接收

    # 定义socket缓冲区大小
    BUFSIZ = 1024
    # 已接收的临时文件大小
    RECVSIZ = 0

    # 最终数据的初始状态
    data = b''

    # 循环接收数据
    while True:

        # 如果剩余没有接收到的数据长度 大于 缓存区的大小
        # 则以缓存区大小接收尚未发送完成的数据(大段大段接收)
        if SERVSIZ - RECVSIZ >= BUFSIZ:
            temp_data = client.recv(BUFSIZ)
        else:
            # 剩余一小部分未接收的数据 长度大小小于缓冲区大小 全部接收
            temp_data = client.recv(SERVSIZ - RECVSIZ)

        # 已经接收的数据长度 一直在变化
        RECVSIZ += len(temp_data)

        # 最终数据也在一直变化
        data += temp_data

        # 跳出循环读取条件:已经接收的数据的长度与远端数据长度一致
        if RECVSIZ == SERVSIZ:
            break

    # 返回最终接收到的完整的数据
    return data

网络文件下载服务器

服务器端

import socket
import os
import struct
import json

# 创建server的socket对象
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8888))
server.listen(5)

while True:
    client, ip_port = server.accept()
    print("客户端连接服务器成功!!!")

    # 文件对象的初始化
    f = None

    while True:

        try:
            # 提供待下载文件的绝对路径
            file_path = r"G:PythonWorkStation文件传输半链接数.mp4"

            # 获取当前文件的大小
            file_size = os.path.getsize(file_path)

            # 获取当前文件的名字 并且文件的大小与其组合 生成一个信息字典
            file_info_dic = {"file_name": "半链接数.mp4", "file_size": file_size}

            # 文件信息字典转换成json格式用于将其作为包头信息发送
            file_info_dic_json = json.dumps(file_info_dic).encode("utf-8")

            # 发送报头长度,以告知客户端完整接收报头
            client.send(struct.pack('q', len(file_info_dic_json)))

            # 真正发送报头阶段
            client.send(file_info_dic_json)

            # 进入正题!!!发送文件(读取发送)
            f = open(file_path, "rb")

            # 进入循环
            while True:

                # 文件对象读取比特位
                temp = f.read(2048)
                if not temp:
                    break

                # 读完立即发送出去!
                client.send(temp)
            print("文件发送完毕!!!")

        except Exception as e:
            print("发现错误:  %s" %e)
        finally:
            if  f:f.close()

        # 关闭之后直接关闭套接字
        client.close()

客户端

import socket
import struct
import json

client = socket.socket()

try:
    client.connect(("127.0.0.1",8888))

    print("连接服务器成功!!!")

    # !!!接收文件内容阶段!!!

    # 1.先接收表示文件信息字典长度的报头  8个字节可接收到很多信息
    head_size = struct.unpack("q",client.recv(8))[0]

    # 2.以接收到的8字节报头长度信息去接收报头数据
    # 以接收到的长度报头读取,此时还是json格式的文件信息字典
    head_str = client.recv(head_size).decode("utf-8")
    # json数据转换为python数据类型
    file_info_dic = json.loads(head_str)

    # 测试打印报头数据
    print("报头信息: ",file_info_dic)
    # 文件大小
    file_size = file_info_dic.get("file_size")
    # 文件名
    file_name = file_info_dic.get("file_name")


    # !!!接收文件内容阶段!!!
    # 接收到的临时文件的大小值(动态变化)
    recv_size = 0
    # 缓冲区大小
    BUFSIZ = 2048

    # 获取写文件对象f
    f = open(file_name,"wb")

    # 进入循环
    while True:
        if file_size - recv_size >= BUFSIZ:
            temp = client.recv(BUFSIZ)
        else:
            temp = client.recv(file_size - recv_size)

        # 收到就写
        f.write(temp)

        # 接收到的临时文件的大小值时时在变化!
        recv_size += len(temp)

        # 下载进度实时显示
        print("进度>>> %s%%" % (recv_size / file_size * 100))

        # 证明接收完毕
        if recv_size == file_size:
            break

    f.close()

except ConnectionAbortedError as e:
    print("连接服务器失败!!!",e)
原文地址:https://www.cnblogs.com/tingguoguoyo/p/10940336.html