网络编程整合版

前言

本篇博客整合了以下知识点

C/S B/S架构

C/S B/S架构
C: client端,客户端
B: Browser,浏览器
S: server 服务端
C/S 客户端与服务器之间的架构: QQ,微信,游戏,App的都属于C/S架构.
     优点: 安全性高,个性化设置,功能全面.响应速度快.   
     缺点: 开发成本高,维护成本高.(基于App),面向的客户固定.
B/S 浏览器与服务器之间的架构:它属于C/S架构,最近几年比较流行的特殊的C/S架构.
    优点: 开发维护成本低,,面向用户广泛.
    缺点: 安全性相对低,响应速度相对慢,个性化的设置单一.

网络通信原理

#利用物理连接和一堆协议 osi 七层协议(5层协议)
# 1,首先要通过各种物理连接介质  连接。
# 2,确定对方计算机(准确到软件)的位置。
# 3,通过统一的标准(一揽子协议)进行数据的收发。

osi七层协议

img

img

img

物理层

一系列的物理连接介质: 网线,光纤,电缆等等等.
发送的数据就是010101010110比特数据流,这些数据连续不断地收发数据
因为不知道数据代表的意义,数据要进行分组(按照一定规则), 数据分组这件事物理层做不了 所以交给了数据链路层

数据链路层

以太网协议

mac地址| ip(双方的) |端口]dic = {flename,md5,filesize}数据
# 用到的协议: ARP协议 以太网协议  交换机的自主学习功能
# 常见物理设备:网桥、以太网交换机、网卡
# 从物理层拿到数据比特流 按照一定的协议对比特流数据进行分组.
# 以太网协议:
# 一组电信号构成一个数据报,叫做‘帧’
# 每一数据帧分成:报头head和数据data两部分
# 数据头(head)
    # 固定长度18个字节.包含了    源地址,目的地址,数据类型.
    # 为什么数据头要固定?
       #固定就是一个标准,统一,为了提取源地址以及目标地址.
    #以太网协议中源目标地址如何设置唯一
        #网线直接接触的硬件就是网卡.网卡上有一个地址,mac地址,确定计算机的唯一性的物理地址
        # (mac地址12位 16进制组成的一串数字: 前六位 厂商编号: 后六位:流水线号.).
# 数据data
    # 46字节 <= data <=1500字节

# 广播: 计算机最原始的通信方式就是吼.
# 数据的分组(源地址目标地址) + 广播: 理论上我的计算机就可以通信了.效率太低,每台计算机都需要接收广播的消息,查看是否是给自己的数据.比广播风暴还要严重.
# 所以: 广播它是有范围的,在同一子网,局域网内是通过广播的方式,发消息.
# 中间环节一些特殊的功能:
# 到这里先广播 在组播 单播

交换机的自主学习功能

# 交换机的自主学习功能:
#     交换机对照(网口与MAC地址的)表:
#     1:    1C-1B-0D-DA-E8-8F
#     2:    FF-FF-FF-FF-FF-FF
#     3:    FF-FF-FF-FF-FF-FF
#     4:    FF-FF-FF-FF-FF-FF
#     5:    1C-1B-0F-4A-E8-8F
#     网口1: 出来一条信息:
#     # 第一次广播的形式发出去.
#     网口1:源地址: 1C-1B-0D-DA-E8-8F 目标地址: 1C-1B-0F-4A-E8-8F  |   明天放假
#     2,3,4,5口接收到次消息,查看目标mac地址是否是自己的,
#     5口确定是自己的.
#     每个网口都广播发送消息一遍之后,对照表就构建好,下次在任意的网口在发消息,就直接以单播的形式发送.
#     目的: 避免局域网内每一次都广播的形式通信.以后就可以单播,提升效率.

ARP协议

#  ARP协议
# 就是将对方的ip地址获取到对方的MAC地址  对方接受到返回  源mac 目标mac  源ip:  目标ip  此时原mac 是自己的
# IP + ARP协议 获取对方的MAC地址
1、arp协议是⽤来完成 根据IP地址获取物理地址的⼀个ip地址 ⼯作的,⼯作在osi协议的第 2 层
(这⼀层的名字是 数据链路层 ),⽤到了 交换机 硬件设备,这个设备⼀共有三种⼯作
⽅式,分别是 ⼴播 、 单播 、 组播 ,arp协议⽤到了其中的 ⼴播 和  播 ,这⼀层还有 ⽹卡、⽹桥 物理设备。

网络层

ip(双方的) |端1 |dic = {ilename,md5,filesize}数据
封装了ip
# IP协议:确定对方的局域网的位置.
# 理论上到了这一层可以找到世界上任意一台计算机.
# 广播,mac地址,+ ip == 可以找到世界上任意一台计算机.
# 为了与计算机软件通讯给到传输层

IP协议:

IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。

# 四点分十进制(网络地址由32位2进制)网络部分:标识子网前24位  主机部分:标识主机 后8位
# 范围0.0.0.0-255.255.255.255
# 子网掩码(美国分的)255.255.255.0 C类子网掩码可以分配的ip数量: 254个
# 通过iP地址与子网掩码可以确定对方计算机是否和自己的计算机在同一子网.
# and运算
# ip地址+子网掩码确定计算机所在网段,子网,局域网的位置.
ip地址+子网掩码如何确定局域网的位置? and运算
一个ip:   172.16.10.1  :  10101100.00010000.00001010.00000001
子网掩码: 255.255.255.0 : 11111111.11111111.11111111.00000000
      从属于的局域网 网段,子网,局域网 :   172.16.10.0
C类子网掩码 一个网段最多可以承载多个IP地址?
172.16.10.0 被占用.
172.16.10.255 广播地址 被占用.
172.16.10.1 被占用.
253台计算机.

IP地址可以相同吗
不一样。
IP地址分为广域网和局域网。
一般家庭或公司内部IP范围在局域网内部是唯一的,不能相同,相同的话会造成IP冲突,这时是不能上网的。不同局域网之间,IP地址可能相同,但是他们上广域网的话 ,这个局域网对外的IP只有一个。
在广域网上IP是唯一的,也不是说就不能变 ,有变的情况和不变的情况,比如家庭上网一般用动态IP,这个IP是常变的。但是有的IP是不会变的,比如网站服务器的IP是唯一的。

如何区分两个IP地址是否属于同一个子网
子网掩码是用来判断任意两台计算机的IP地址是否属于同一子网络的根据。
最为简单的理解就是两台计算机各自的IP地址与子网掩码进行AND运算后,如果得出的结果是相同的,则说明这两台计算机是处于同一个子网络上的,可以进行直接的通讯。就这么简单。

注意:单纯的ip地址段只是标识了ip地址的种类,从网络部分或主机部分都无法辨识一个ip所处的子网

例:172.16.10.1与172.16.10.2并不能确定二者处于同一子网

中国可分配的ip数量
截至2017年12月,我国IPv6地址数量为23430块/32,年增长10.6%
全球 IPv4 地址数已于2011年2月分配完毕,全球IPv4总地址 2^32 大约40亿个,
其中,需除去一些保留A、B、C段,如 10.0.0.0、172.16.0.0-172.31.0.0、172.168.0.0 、 192.168.0.0
自2011年开始我国IPv4地址总数基本维持不变,截至2017年12月,共计有33870万个,全球占比 3.387/40 = 8.468%

传输层

传输层: TCP UDP:端口协议.封装 了端口
端口1 dic = {ilename,md5,filesize}数据
tcp udp协议
# 广播,mac地址,+ ip + 端口 == 可以找到世界上任意一台计算机对应的软件.
# 端口: 0~65535端口号.
# #   1~1023系统占用的端口号.
# #   1024~8000之内:一般的是有软件占用.
# 传输层: 端口协议.
TCP UDP:端口协议封装了端口
端口|dic = {filename,md5,filesize} 数据

# TCP协议,UDP协议.
# TCP协议
#     优点:好人协议,流式协议.稳定,安全,
#     缺点: 效率低,
# 建立的链接不能一直连接着.  耗内存
# syn洪水攻击: 黑客会虚拟很多的假IP,然后访问你的服务器,  解决方式 半连接池,缓冲效果.
# TCP的三次握手四次挥手
# 三次握手
# 发syn=1 seq=x 回ack=1+x
# 4次挥手
# 因为TCP连接是全双工的,因此每一个方向都必须单独进行关闭。
# fin=1 seq=x 回ack=1+x

# udp协议:
#     优点: 效率高,传输快.
#     缺点: 不安全,不是面向连接的,不可靠
# 使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP),微信qq。 直播等
# 面向数据报协议  无连接协议

端口协议: UDP协议,TCP协议

65535端口

1~1024操作系统专门使用的端口

举例: 3306 数据库

自己开发软件都是8080以后的端口号

抽象层

socket(抽象层)

应用层

# 软件自己定义的协议. FTP
自己定义报头 md5等等

各层用到的工具

# 服务器
# 大黑盒子, 机房声音很大,对温度,湿度,等环境都有要求,双电源,双网卡,系统linux
# 交换机(自主学习功能)
# 数据经过以太网协议封装后,先要从局域网内进行吼.每次发消息,每次都要吼,这样效率也是很低的.(数据给交换机,交换机在分发出去.)

应⽤层 ⽹络服务与最终⽤户的⼀个接⼝。
常⻅协议:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP
表示层 数据的表示、安全、压缩。(在五层模型⾥⾯已经合并到了应⽤层)
格式有,JPEG、ASCll、DECOIC、加密格式等
会话层 建⽴、管理、终⽌会话。(在五层模型⾥⾯已经合并到了应⽤层)
对应主机进程,指本地主机与远程主机正在进⾏的会话
传输层 定义传输数据的协议端⼝号,以及流控和差错校验。
常⻅协议:TCP UDP,数据包⼀旦离开⽹卡即进⼊⽹络传输层
常⻅的物理设备 :四层路由器、四层交换机
⽹络层 进⾏逻辑地址寻址,实现不同⽹络之间的路径选择。
常⻅协议:ICMP IGMP IP(IPV4 IPV6) ARP RARP
常⻅物理设备:路由器、三层交换机
数据链路层 建⽴逻辑连接、进⾏硬件地址寻址、差错校验 [2] 等功能。(由底层⽹络定义协议)
将⽐特组合成字节进⽽组合成帧,⽤MAC地址访问介质,错误发现但不能纠正。
常⻅协议:ARP协议
常⻅物理设备:⽹桥、以太⽹交换机、⽹卡
物理层 建⽴、维护、断开物理连接。(由底层⽹络定义协议)
常⻅物理设备:中继器、集线器、双绞线

UDP TCP 协议

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

UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
udp协议的特点是: 不可靠 、 ⽆连接 、 传输效率⾼ 、 ⾯向报⽂的 ,其中容易发⽣粘包现象的协议是 tcp ,原因是 tcp协议数据与数据之间没有边界 。 
在tcp协议三次握⼿的过程中请求链接的状态码是 syn ,回复的是 ack ,在四次挥⼿的过程中请求断开的状态码是 fin 。

TCP协议的三次握手和四次挥手

# 三次握手
# 发syn=1 seq=x 回ack=1+x
# 4次挥手
# 因为TCP连接是全双工的,因此每一个方向都必须单独进行关闭。
# fin=1 seq=x 回ack=1+x

socket套接字

Socket又称为套接字,它是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口提高了效率
socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。也
为什么存在socket抽象层?
如果直接与操作系统数据交互非常麻烦,繁琐,socket对这些繁琐的的操作高度的封装,简化.	
socket在python中就是一个模块.

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

套接字家族的名字:AF_UNIX

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

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

套接字家族的名字:AF_INET

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

tcp链接+循环 通信 服务端常驻

img

server端

import socket
phone=socket.socket()#买手机
phone.bind(('127.0.0.1',6666))#绑卡
phone.listen(5)#设置连接数一个连接加5个等待 缓存池
while 1:
    conn,addr=phone.accept()
    print(f'用户{addr}来了')
    while 1:
        try:
            from_client_data=conn.recv(1024)
            if from_client_data.decode('utf-8')=='Q':
                print('正常断开')
                break
            print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
            to_client_data=input('>>>>>>>')
            conn.send(to_client_data.encode('utf-8'))
        except Exception:
            break
    conn.close()
phone.close()

client端

# 自己的
import socket
phone=socket.socket()
phone.connect(('127.0.0.1',6666))
while 1:
    to_server_data=input('>>>>>>q退出')
    if not to_server_data:
        print('不可以输入空')
        continue
    phone.send(to_server_data.encode('utf-8'))#因为不可以让他发空 所以放在判断下
    if to_server_data.upper()=='Q':
        break
    from_server_data=phone.recv(1024)
    print(f'来自客户端消息{from_server_data.decode("utf-8")}')
phone.close()

tcp执行远端命令

server端

# 用到的模块
import subprocess
# import subprocess
# obj = subprocess.Popen('系统命令',
#                        shell=True,
#                        stdout=subprocess.PIPE,
#                        stderr=subprocess.PIPE,
#                        )
# print(obj.stdout.read().decode('gbk'))  # 正确命令
# print(obj.stderr.read().decode('gbk'))  # 错误命令
# 错误的是空字符串

import socket
import subprocess
phone = socket.socket()

phone.bind(('127.0.0.1',8848))

phone.listen(2)
while 1:
    conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
    print(f'链接来了: {conn,addr}')
    while 1:
        try:
            from_client_data = conn.recv(1024)  # 最多接受1024字节
            if from_client_data.upper() == b'Q':
                print('客户端正常退出聊天了')
                break
            obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
            result = obj.stdout.read() + obj.stderr.read()

            conn.send(result)
        except ConnectionResetError:
            print('客户端链接中断了')
            break
    conn.close()
phone.close()

client端

# 用到的模块
import subprocess
# import subprocess
# obj = subprocess.Popen('系统命令',
#                        shell=True,
#                        stdout=subprocess.PIPE,
#                        stderr=subprocess.PIPE,
#                        )
# print(obj.stdout.read().decode('gbk'))  # 正确命令
# print(obj.stderr.read().decode('gbk'))  # 错误命令
# 错误的是空字符串

import socket
import subprocess
phone = socket.socket()

phone.bind(('127.0.0.1',8848))

phone.listen(2)
while 1:
    conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
    print(f'链接来了: {conn,addr}')
    while 1:
        try:
            from_client_data = conn.recv(1024)  # 最多接受1024字节
            if from_client_data.upper() == b'Q':
                print('客户端正常退出聊天了')
                break
            obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
            result = obj.stdout.read() + obj.stderr.read()

            conn.send(result)
        except ConnectionResetError:
            print('客户端链接中断了')
            break
    conn.close()
phone.close()

利用udp建立通讯

udp是无链接的,先启动哪一端都不会报错

img

server端

import socket

udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)  # 基于网络,udp协议的socket

udp_server.bind(('127.0.0.1', 9000))#绑卡

while 1:
    from_client_data = udp_server.recvfrom(1024)#接收 前面消息 后面是ip端口
    print(f'来自{from_client_data[1]}的消息:{from_client_data[0].decode("utf-8")}')
    to_client_data = input('>>>').strip()
    udp_server.sendto(to_client_data.encode('utf-8'),from_client_data[1])

client端

import socket

udp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)  # 基于网络,udp协议的socket

while 1:
    to_server_data = input('>>>').strip()
    udp_client.sendto(to_server_data.encode('utf-8'),('127.0.0.1', 9000))
    from_server_data = udp_client.recvfrom(1024)#发送端 端口不知道
    print(f'来自{from_server_data[1]}的消息:{from_server_data[0].decode("utf-8")}')

tcp实现文件上传 客户端-->服务端

server端

# 1. 接收固定长度的4个字节 2. 利用struct反解 3. 接收bytes类型的报头 4. 将bytes类型的报头转化成json
# 5. 将json类型报头转化成字典形式的报头有一个MD5校验# 6. 接收原始数据
#在文件读取时同时进行md5效验 一举两得
import socket
import struct
import json
import os
import hashlib
MY_FILE = os.path.join(os.path.dirname(__file__),'my_file')#拼接出来的要存入的总路径

def socket_server(MY_FILE):
    server = socket.socket()
    server.bind(('127.0.0.1',8848))
    server.listen(5)
    conn, addr = server.accept()
    four_bytes = conn.recv(4)#1 接收固定长度的4个字节
    head_len = struct.unpack('i',four_bytes)[0]#2  利用struct反解
    head_dic_json_bytes = conn.recv(head_len)#3接收bytes类型的报头
    head_dic_json = head_dic_json_bytes.decode('utf-8')#4 将bytes类型的报头转化成json
    head_dic = json.loads(head_dic_json)#5将json类型报头转化成字典形式的报头
    with open(os.path.join(MY_FILE,head_dic['new_file_name']),mode='wb') as f1:#6接收原始数据

        total_size = 0
        ret = hashlib.md5()
        while total_size < head_dic['file_size']:
            every_data = conn.recv(1024)
            f1.write(every_data)
            total_size += len(every_data)
            ret.update(every_data)
        if ret.hexdigest()==head_dic['MD5']:
            print('md5效验成功')
        else:
            print('md5效验失败')
            os.remove(os.path.join(MY_FILE,head_dic['new_file_name']))
    conn.close()
    server.close()
socket_server(MY_FILE)

client端

# 1.制作字典形式的报头2. 获取json形式的报头 3. 获取bytes形式的报头4. 获取bytes报头的总字节数 5. 将bytes报头的总字节数转化成固定4个字节#
# 6. 发送固定的4个字节 7.发送报头 8. 发送总数据
# 循环条件设定:1. 根据总子节数: file_size: 493701, 每次循环1024个,2. 每次取数据不为空,即可.
import socket
import os
import json
import struct
import hashlib
FILE_PATH = os.path.join(os.path.dirname(__file__), 'demo.mp4')#拼接出来的总路径
def md5_file(path):
    ret = hashlib.md5()
    with open(path,mode='rb') as f1:
        while 1:
            content = f1.read(1024)
            if content:
                ret.update(content)
            else:
                return ret.hexdigest()
def socket_client(path,md5,name):
    client = socket.socket()
    client.connect(('127.0.0.1',8848))
    head_dic = {#1.制作字典形式的报头
        'MD5': md5,
        'file_name':path,#路径
        'file_size': os.path.getsize(FILE_PATH),#利用文件路径判断文件大小
        'new_file_name': name,#文件名
    }
    head_dic_json = json.dumps(head_dic)#2. 获取json形式的报头
    head_dic_json_bytes = head_dic_json.encode('utf-8')#3. 获取bytes形式的报头
    head_len = len(head_dic_json_bytes)#4. 获取bytes报头的总字节数
    four_bytes = struct.pack('i',head_len)#5. 将bytes报头的总字节数转化成固定4个字节
    client.send(four_bytes)#6. 发送固定的4个字节
    client.send(head_dic_json_bytes)#7.发送报头
    with open(FILE_PATH,mode='rb') as f1:# 8. 发送总数据
        while 1:
            every_data = f1.read(1024)
            if every_data:
                client.send(every_data)
            else:
                break
    client.close()
socket_client(FILE_PATH,md5_file(FILE_PATH),'demo1.mp4')

粘包现象

须知:只有TCP有粘包现象,UDP永远不会粘包
粘包不一定会发生
如果发生了:1.可能是在客户端已经粘了
      2.客户端没有粘,可能是在服务端粘了
所谓粘包问题主要还是因为接收方不知道消息之间的界限 还有系统缓存区的问题 时间差的原因,不知道一次性提取多少字节的数据所造成的。

socket收发消息的原理

img

什么是缓冲区

输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:1024字节=1k

![1565859303401](file:///C:/Users/86131/AppData/Roaming/Typora/typora-user-images/1565859303401.png)

缓冲区的作用?

存储少量数据
如果你的网络出现短暂的异常或者波动,接收数据就会出现短暂的中断,影响你的下载或者上传的效率.
但是 凡是都是双刃剑,缓冲区解决了上传下载的传输效率的问题,带来了黏包问题.

为什么出现粘包?

第一种.连续短暂的send多次(数据量很少),你的数据会统一发送出去,
(不可控)
第二种: send的数据过大,大于对方recv的上限时,对方第2次recv时,会接收上一次没有recv完的剩余的
1,接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)** recv会产生黏包(如果recv接受的数据量(1024)小于发送的数据量,第一次只能接收规定的数据量1024,第二次接收剩余的数据量)
2,发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)**send 也可能发生粘包现象.(连续send少量的数据发到输出缓冲区,由于缓冲区的机制,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络)

解决粘包现象

错误实例:

1. 可以扩大recv的上限. recv(10240000000000000000000000000) 不是解决这个问题的根本原因, 8g, 10G,这些都会直接放在内存中.
2. 故意延长recv的时间. sleep 这样会非常影响效率.

recv工作原理

源码解释:
Receive up to buffersize bytes from the socket.
接收来自socket缓冲区的字节数据,
For the optional flags argument, see the Unix manual.
对于这些设置的参数,可以查看Unix手册。
When no data is available, block untilat least one byte is available or until the remote end is closed.
当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
When the remote end is closed and all data is read, return the empty string.
关闭远程端并读取所有数据后,返回空字符串。

low版解决粘包现象

server

# 原因# 1. 粘包第一种: send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。
# 解决方式 服务端 发送时记录字节长度 在将不固定第字节长度 利用模块转化为固定4个长度bytes
# 先发送4个字节过去 在发送原内容
# 客户端 先接受固定4个字节 在利用固定的4个字节bytes利用模块转回原来的字节个数(int)
# 利用while循环接收

# 利用的模块
# import struct
# # 将一个数字转化成等长度的bytes类型。
# ret = struct.pack('i', 180000000)
# # print(ret, type(ret), len(ret))
#
# # 通过unpack反解回来
# ret1 = struct.unpack('i',ret)[0]
# # print(ret1)
# print(ret1, type(ret1))
# import struct
# import subprocess
# obj = subprocess.Popen('dir1',
#                        shell=True,
#                        stdout=subprocess.PIPE,
#                        stderr=subprocess.PIPE,
#                        )
# print(obj.stdout.read().decode('gbk'))  # 正确命令
# print(obj.stderr.read().decode('gbk'))  # 错误命令

import struct
import subprocess
import socket
phone=socket.socket()
phone.bind(('127.0.0.1',6666))
phone.listen(5)
while 1:
    coon, addr = phone.accept()
    while 1:
        try:
            from_client_data=coon.recv(1024)
            print(f'来自{addr}消息{from_client_data.decode("utf-8")}')
            obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
            to_client_data=(obj.stderr.read().decode('gbk')+obj.stdout.read().decode('gbk')).encode('utf-8')#总字节数
            total_size=len(to_client_data)#总字节数长度
            # print(total_size)
            # 将一个数字转化成等长度的bytes类型。
            size = struct.pack('i', total_size)
            coon.send(size)#发送固定4个字节
            coon.send(to_client_data)#发送总数据
        except Exception:
            break
    coon.close()
phone.close()

client

import socket
import struct
phone=socket.socket()
phone.connect(('127.0.0.1',6666))
while 1:
    to_cerver_data=input('????')
    if not to_cerver_data:
        # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
        print('发送内容不能为空')
        continue
    phone.send(to_cerver_data.encode('utf-8'))
    from_server_size=phone.recv(4)#接收固定4个字节
    from_server_total= struct.unpack('i',from_server_size)[0] # 反解报头
    tatal=b''
    while len(tatal)<from_server_total:
        tatal += phone.recv(1024)
    print(tatal.decode('utf-8'))

phone.close()

旗舰版解决黏包

优点

1.高大上版:自定制报头
2.高大上版:可以解决文件过大的问题.

server端

import socket
import struct
import json
# 1. 创建socket对象(买手机) 左边是 右边是
# phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone = socket.socket() # 可以默认不写
# 连接服务器ip地址与端口 左边服务器IP地址 右边端口
phone.connect(('127.0.0.1', 9999))
# 发消息
while 1:
    try:
        to_server = input('>>>').strip()
        if to_server.upper() == 'Q':
            phone.send('q'.encode('utf-8'))#发消息
            break
        phone.send(to_server.encode('utf-8'))#send 发送

        # 第1步 先读前4个 此时读的是字典长度
        head_4 = phone.recv(4)
        #第2步 将固定4个byres 转化为字典byes原长度数字 此时是数字类型
        ret1 = struct.unpack('i',head_4)[0]
        ##第3步 读取 字典长度byres
        head_dic_len = phone.recv(ret1)
        #第4步将字典 byres解码 转化为 特殊的字符串
        head_dic_json=head_dic_len.decode('utf-8')
        #第5步将 json转化为字典
        head_dic=json.loads(head_dic_json)
        #第6步获取 字典有用的信息 比如文件byres 长度
        place_len=head_dic['file_size']
        #第7步读取 原文件
        place = phone.recv(place_len)
        print(place.decode('utf-8'))
    except ConnectionResetError:
        print('对方服务器崩了。。。。')
        break
# 关机
phone.close()#关闭手机
#ipconfig

![1563352971686](file:///C:/Users/86131/AppData/Roaming/Typora/typora-user-images/1563352971686.png)

client端

#第一步制作报头 head 头  #第2步将字典转为json字符串 #第3步 将json字符串 转为 byes类型 #第4步将json的byres 长度转为固定4个 byres #第5步 发送 固定4个byres total_size_byres   #第6步 发送字典byres 给客服端  # 第7步 发送原内容 给客服端

import socket#建立通讯
import subpr1111ocess#远程返回
import struct#将该模块可以把一个类型,如数字,转成固定长度的bytes
import json
phone = socket.socket()#买手机
phone.bind(('127.0.0.1', 9999))#插卡
phone.listen(5)#开放监听
# 4. 接收连接
print('start')
conn, addr = phone.accept()
while 1:
    try:
        cmd = conn.recv(1024) #  接收发送端的消息
        obj = subprocess.Popen(cmd.decode('utf-8'),
                               shell=True,
                               stdout=subprocess.PIPE,#正确命令
                               stderr=subprocess.PIPE,#错误命令
                               )
        #返回数据  由于有黏包现象 客户端接收不了这么多
        result = obj.stdout.read() + obj.stderr.read()#gbk 格式
        result = result.decode('gbk').encode('utf-8')#解码成utf-8
        #第一步制作报头 head 头
        head_dict={'file_size':len(result)}
        #第2步将字典转为json字符串
        head_dict_json = json.dumps(head_dict)
        #第3步 将json字符串 转为 byes类型
        head_dict_byres=head_dict_json.encode('utf-8')
        #第4步将json的byres 长度转为固定4个 byres
        total_size_byres=struct.pack('i',len(head_dict_byres))
        #第5步 发送 固定4个byres total_size_byres
        conn.send(total_size_byres)
        #第6步 发送字典byres 给客服端
        conn.send(head_dict_byres)
        # 第7步 发送原内容 给客服端
        conn.send(result)
    except ConnectionResetError:
        break
conn.close()
phone.close()

基于UDP协议的socket通信

server

import socket
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 基于网络的UDP协议的socket
server.bind(('192.168.14.198',9000))

while 1:

    from_client_data = server.recvfrom(1024)  # 阻塞,等待客户来消息
    print(f'\033[1;35;0m来自客户端{from_client_data[1]}: {from_client_data[0].decode("utf-8")} \033[0m')
    # to_client_data = input('>>>').strip()
    # server.sendto(to_client_data.encode('utf-8'),from_client_data[1])


# 1. 基于udp协议的socket无须建立管道,先开启服务端或者客户端都行.
# 2. 基于udp协议的socket接收一个消息,与发送一个消息都是无连接的.
# 3. 只要拿到我的ip地址和端口就都可以给我发消息,我按照顺序接收消息.

client端

import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 基于网络的UDP协议的socket

while 1:

    to_server_data = input('>>>:').strip()
    client.sendto(to_server_data.encode('utf-8'),('127.0.0.1',9000))
    # data,addr = client.recvfrom(1024)
    # print(f'来自服务端{addr}消息:{data.decode("utf-8")}')

socketserver模块

server

固定写法
import socketserver
class MYserver(socketserver.BaseRequestHandler):
    def handle(self):
        """
        所有业务逻辑都在这
        :return:
        """
        while 1:
            from_client_data=self.request.recv(1024)
            print(f'来自客户端消息{from_client_data}.decode("utf-8")')
            to_client_msg=input('>>>>>>>>>').encode('utf-8')
            self.request.send(to_client_msg)
if __name__ == '__main__':
    server=socketserver.ThreadingTCPServer(('127.0.0.1',6666),MYserver)
    server.serve_forever()

client

# 自己的
import socket
phone=socket.socket()
phone.connect(('127.0.0.1',6666))
while 1:
    to_server_data=input('>>>>>>q退出')
    if not to_server_data:
        print('不可以输入空')
        continue
    phone.send(to_server_data.encode('utf-8'))#因为不可以让他发空 所以放在判断下
    if to_server_data.upper()=='Q':
        break
    from_server_data=phone.recv(1024)
    print(f'来自客户端消息{from_server_data.decode("utf-8")}')
phone.close()

socketserver 原理是什么

posted @ 2020-01-10 17:20  一起奥利给  阅读(138)  评论(0编辑  收藏  举报