网络编程

1.C/S B/S架构

C: client端,客户端

B: Browser,浏览器

S: server 服务端

C/S 客户端与服务器之间的架构: QQ,微信,游戏,App的都属于C/S架构.

​ 优点: 安全性高,个性化设置,功能全面.响应速度快.

​ 缺点: 开发成本高,维护成本高.(基于App),面向的客户固定.

B/S 浏览器与服务器之间的架构:它属于C/S架构,最近几年比较流行的特殊的C/S架构.

​ 优点: 开发维护成本低,,面向用户广泛.

​ 缺点: 安全性相对低,响应速度相对慢,个性化的设置单一.

2.互联网通信的原理

打电话示例:

穿越时空: 80年代初期,固定电话,座机.

  1. 一堆物理连接介质将两个部电话连接起来.
  2. 拨号.
  3. 交流.

那时候没有普通话,河南,山西,广西,广东,福建等等.....

推广了普通话.

与国外一些国家去交流,统一英语.

互联网通信:

  1. 一堆物理连接介质将两个部电话连接起来.
  2. 拨号.
  3. 统一的通信标准. 一揽子协议,

这些互联网协议: 就是一个一个标准,最终就可以通信.

3.osi 七层协议(五层协议)

1.物理层:

一系列的物理连接介质: 网线,光纤,电缆等等等.

发送的数据就是010101010110比特数据流,这些数据连续不断地收发,010110,拿到010101没有用,你不知道数据代表的意义, 数据要进行分组(按照一定规则), 数据分组这件事物理层做不了.

2.数据链路层: 以太网协议

是按照一定的协议对比特流数据进行分组.

以太网协议.:就是对数据进行分组.

写信: 数据交流. 收件人地址, 发件人的地址.信件类型(加急,普通,工作上信封等等.....).

以太网协议:

​ head(源mac地址,目标mac地址) 数据类型 | data

​ head 固定18个字节. 46<data<1500

  • 一组电信号构成一个数据报,叫做‘帧’

  • 每一数据帧分成:报头head和数据data两部分

    数据头(head) | data数据

    数据头: 固定长度18个字节.

    ​ 源地址,目的地址,数据类型.

    data数据: 46字节 <= data <=1500字节

    1. 问题一: 为什么数据头要固定?

      固定就是一个标准,统一,为了提取源地址以及目标地址.

    2. 问题2: 以太网协议中源目标地址如何设置唯一?

      网线直接接触的硬件就是网卡.网卡上有一个地址,mac地址,确定计算机的唯一性的物理地址.

      网卡上: 12位 16进制组成的一串数字: 前六位 厂商编号: 后六位:流水线号.

只做好了信件: head(源地址,目标地址,数据类型) | data(今晚你请我吃饭)

广播: 计算机最原始的通信方式就是吼.

数据的分组(源地址目标地址) + 广播: 理论上我的计算机就可以通信了.效率太低,每台计算机都需要接收广播的消息,查看是否是给自己的数据.比广播风暴还要严重.

所以: 广播它是有范围的,在同一子网,局域网内是通过广播的方式,发消息.

3.网络层: IP协议:确定对方的局域网的位置.

广播,mac地址,+ ip == 可以找到世界上任意一台计算机.

计算机的通信: 计算机的软件与服务器的软件进行的通信.

4.传输层: 端口协议.

tcp协议:

三次握手和三次挥手

udp协议

广播,mac地址,+ ip + 端口 == 可以找到世界上任意一台计算机对应的软件.

5.应用层: 软件自己定义的协议.

qq : 发送数据; '今晚请我吃饭...' ---> {id: '念', 'content': '今晚请我吃饭...'}

将数据按照自己定义的协议进行分装, http FTP协议.

五层协议重新梳理:

​ 服务器: 大黑盒子, 机房声音很大,对温度,湿度,等环境都有要求,双电源,双网卡,系统linux.

详细解释中间环节一些特殊的功能:

数据经过以太网协议封装后,先要从局域网内进行吼.每次发消息,每次都要吼,这样效率也是很低的.(数据给交换机,交换机在分发出去.)

交换机的自主学习功能:

广播: 吼.

单播: 单线直接联系.

物理层---> 数据链路层(以太网协议(mac地址)) ---->网络层(IP协议) ----> 传输层(端口协议(TCP,UDP协议)) ---> 应用层:

mac地址 + 广播形式 + ip地址 + 端口 == 锁定全世界范围内的任意一个计算机的某软件的位置.

ip地址 + 端口 == 锁定全世界范围内的任意一个计算机的某软件的位置.

传输层: 端口协议.

TCP协议,UDP协议.

端口: 0~65535端口号.

​ 1~1023系统占用的端口号.

​ 1024~8000之内:一般的是有软件占用.

4.TCP的三次握手四次挥手

建立的链接不能一直连接着.

TCP协议: 好人协议,不会拒绝别人.

syn洪水攻击: 黑客会虚拟很多的假IP,然后访问你的服务器,半连接池,缓冲效果.

四次挥手:

5.udp与tcp

tcp协议:

​ 优点:好人协议,流式协议.稳定,安全,

​ 缺点: 效率低,

使用TCP的应用:Web浏览器;文件传输程序。

udp协议:

​ 优点: 效率高,传输快.

​ 缺点: 不安全,不是面向连接的,不可靠

使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP),微信qq。

6.socket套接字.

五层协议: 从传输层包括传输层一下,都是操作系统帮助我们封装的各种head.你不用去关心.

# content = input('>>>')
# print(content)
# 怎么交给操作系统( ⊙o⊙ )?
# 模块,或者内置函数 必须有方法内置的一些代码接收数据,然后在底层交由操作系统.

# socket套接字充当的就是内置模块的角色.

# 你说一下socket的套接字?
'''
socket 套接字,它存在于传输层与应用层之间的抽象层,
    1. 避免你学习各层的接口,以及协议的使用, socket已经封装好了所有的接口.
        直接使用这些接口或者方法即可,使用起来方便,提升开发效率.
    2. socket就是一个模块.通过使用学习模块提供的功能,
        建立客户端与服务端的通信,使用方便.
'''

1.基于TCP协议的socket通信.

2.单个客户与服务端通信.

服务端:

import socket

phone = socket.socket()

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

phone.listen(5)

# 4. 接收连接
while 1:
    print('start')
    conn, addr = phone.accept()  # 程序夯住
    print(conn,addr)
    while 1:
        try:
            from_client_data = conn.recv(1024)  # 至多接收1024个字节
            if from_client_data == b'q':
                break
            print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
            to_client = input('>>>')
            conn.send(to_client.encode('utf-8'))
        except ConnectionResetError:
            break
    conn.close()
phone.close()

客户端:

import socket

# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写

# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8848))

# 发消息
to_server = input('>>>').strip()
phone.send(to_server.encode('utf-8'))
# 接收消息
from_server_data = phone.recv(1024)  # 夯住,等待服务端的数据传过来
print(f'来自服务端消息:{from_server_data.decode("utf-8")}')

# 关机
phone.close()

3.通信循环.

服务端:

import socket

phone = socket.socket()

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

phone.listen(5)

# 4. 接收连接
while 1:
    print('start')
    conn, addr = phone.accept()  # 程序夯住
    print(conn,addr)
    while 1:
        try:
            from_client_data = conn.recv(1024)  # 至多接收1024个字节
            if from_client_data == b'q':
                break
            print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
            to_client = input('>>>')
            conn.send(to_client.encode('utf-8'))
        except ConnectionResetError:
            break
    conn.close()
phone.close()

客户端:

import socket

# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写

# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8888))

# 发消息
while 1:
    to_server = input('>>>').strip()
    if to_server.upper() == 'Q':
        phone.send('q'.encode('utf-8'))
        break
    phone.send(to_server.encode('utf-8'))
    # 接收消息
    from_server_data = phone.recv(1024)  # 夯住,等待服务端的数据传过来
    print(f'来自服务端消息:{from_server_data.decode("utf-8")}')

# 关机
phone.close()

4.通信,连接循环.

服务端:

# import socket
#
# # phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# # 1. 创建socket对象(买手机)
# phone = socket.socket() # 可以默认不写
#
# # 2. 绑定ip地址和端口(办卡)
# phone.bind(('127.0.0.1', 8848))  # 本地回环地址
#
# # 3. 监听.(开机状态)
# phone.listen(5)
#
# # 4. 接收连接
# print('start')
# conn, addr = phone.accept()  # 程序夯住
# # print(conn,addr)
# while 1:
#     from_client_data = conn.recv(1024)  # 至多接收1024个字节
#     print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
#     to_client = input('>>>')
#     conn.send(to_client.encode('utf-8'))
#
# conn.close()
# phone.close()

# 无论你的客户端是否正常关闭,服务端都应该正常关闭,而不是报错.


import socket

# phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写

# 2. 绑定ip地址和端口(办卡)
phone.bind(('127.0.0.1', 8888))  # 本地回环地址

# 3. 监听.(开机状态)
phone.listen(5)

# 4. 接收连接
print('start')
conn, addr = phone.accept()  # 程序夯住
# print(conn,addr)
while 1:
    try:
        from_client_data = conn.recv(1024)  # 至多接收1024个字节
        if from_client_data == b'q':
            break
        print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
        to_client = input('>>>')
        conn.send(to_client.encode('utf-8'))
    except ConnectionResetError:
        break
conn.close()
phone.close()

客户端:

import socket

# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写

# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8888))

# 发消息
while 1:
    to_server = input('>>>').strip()
    if to_server.upper() == 'Q':
        phone.send('q'.encode('utf-8'))
        break
    phone.send(to_server.encode('utf-8'))
    # 接收消息
    from_server_data = phone.recv(1024)  # 夯住,等待服务端的数据传过来
    print(f'来自服务端消息:{from_server_data.decode("utf-8")}')

# 关机
phone.close()

5.利用socket完成获取远端命令的示例.

服务端

import socket
import subprocess
phone=socket.socket()

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

phone.listen(5)

print("start")
conn, addr = phone.accept()
print(conn)
while 1:

    from_client = conn.recv(1024)
    print(from_client)
    obj = subprocess.Popen(from_client.decode("utf-8"),
                           shell=True,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           )
    ret=obj.stdout.read()+obj.stderr.read()
    conn.send(ret)

conn.close()



phone.close()

客户端

import socket


phone=socket.socket()
phone.connect(("127.0.0.1", 8848))
while 1:
    info=input(">>>>>")
    phone.send(info.encode("utf-8"))
    from_server=phone.recv(1024)
    print(from_server.decode("gbk"))


phone.close()

6.黏包现象.

1.黏包现象.

                      服务端:                  客户端:
第一次 dir             数据418 < 1024            接收418数据
第二次 ipconfig        数据1517 > 1024           接收1024个字节
第三次 dir             数据418 < 1024            接收493个字节

黏包现象的根本原因:缓冲区.
recv是一旦发送就会在接收数据缓存区等待抓取,一旦上次发的数据过大,一次抓取不完,就会剩余在接收数据缓冲区,
那么在第二次发送后继续抓取时就会发现还有数据,那就会直接抓取,其实是剩余的而不是新发送来的
send同理

2.系统缓冲区.

缓冲区的作用?

没有缓冲区:如果你的网络出现短暂的异常或者波动,接收数据就会出现短暂的中断,影响你的下载或者上传的效率.

但是 凡是都是双刃剑,缓冲区解决了上传下载的传输效率的问题,带来了黏包问题.

减少与磁盘的交互

3.什么情况下产生黏包

  1. recv会产生黏包(如果recv接受的数据量(1024)小于发送的数据量,第一次只能接收规定的数据量1024,第二次接收剩余的数据量)
  2. send 也可能发生粘包现象.(连续send少量的数据发到输出缓冲区,由于缓冲区的机制,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络)

4.解决黏包的方案

1.错误实例:

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

2.思路:

分析一下功能:

send多次,recv一次.(不是一收一发制)

recv的工作原理:pass

3.解决粘包现象的思路分析:

  1. 当我第二次给服务器发送命令之前,我应该循环recv直至将所有的数据全部取完.

    问题:

    result 3000bytes recv 3次

    result 5000bytes recv 5次

    result 30000bytes recv ?次 ---> 循环次数相关

    1. 如何限制循环次数?

    当你发送的总bytes个数,与接受的总bytes个数相等时,循环结束.

  2. 如何获取发送的总bytes个数: len() --- > 3400个字节 int

    总数据 result = b'fdsafdskfdskfdsflksdfsdlksdlkfsdjf'

    所以:

    服务端:

    send(总个数)

    send(总数据)

  3. 总个数是什么类型? int() 3400,send需要发送 bytes类型.

    send(总个数)

    ​ 将int 转化成bytes 即可. b'3400'

    ​ 方案一:

    ​ str(3400) -- > '3400' -----> bytes('3400') -----> b'3400' ---> 几个字节? 4个字节

    send(总数据)

你现在继续解决的问题!!!!!

无论总字节个数是多多少? 129 3400 10000 30000 转化成固定长度的bytes.

将不固定长度的int类型,转化成固定长度bytes类型.方便获取头部信息.

(recv的工作原理)

第二种优化版

用一个字典承载

#自定义字典的数据头
import json
import struct
head_dict = {
    'MD5': 'fdsaf2345544324dfs',
    'file_name': '婚前视频',
    'file_size': 15436540006555555555555555556556546546565654654654654645555555555555555555555555555555555555,
}

head_len = len(json.dumps(head_dict).encode('utf-8'))
print(head_len)

ret = struct.pack('i', head_len)
print(ret)

示例:

服务端

import socket
import subprocess
import struct
import json
phone = socket.socket()

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

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()
        result = result.decode('gbk').encode('utf-8')
        # print(f'服务端发送的总字节数{len(result)}')

        # 1. 制作报头

        head_dict = {
            'MD5': 'fdsaf2345544324dfs',
            'file_name': '婚前视频',
            'file_size': len(result),
        }

        # 2. 将报头字典转化成json字符串
        head_dict_json = json.dumps(head_dict)

        # 3. 将json字符串 转化成bytes
        head_dict_json_bytes = head_dict_json.encode('utf-8')

        # 4. 获取报头的长度
        head_len = len(head_dict_json_bytes)

        # 5.将长度转化成固定的4个字节
        head_len_bytes = struct.pack('i',head_len)

        # 6. 发送固定的4个字节
        conn.send(head_len_bytes)

        # 7. 发送报头
        conn.send(head_dict_json_bytes)

        # 8. 发送原数据
        conn.send(result)

    except ConnectionResetError:
        break
conn.close()
phone.close()

客户端

import socket
import struct
import json
phone=socket.socket()
phone.connect(('127.0.0.1', 8888))



while 1:
    cmd=input(">>>")

    phone.send(cmd.encode("utf-8"))

    head=phone.recv(4)
    head_len=struct.unpack("i",head)[0]
    print(head_len)

    dic_josn=phone.recv(head_len).decode("utf-8")
    dic=json.loads(dic_josn)
    print(dic)
    a=b""
    while dic["file_size"] > len(a):
        a+=phone.recv(1024)

    print(a.decode("utf-8"))

phone.close()

7.udp

服务端

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)
    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])

客户端

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")}')

多用户连接服务器

import socketserver

class MyServer(socketserver.BaseRequestHandler): # 继承的类固定的


    def handle(self):  # 必须是这个handle名字.

        while 1:

            from_client_data = self.request.recv(1024).decode('utf-8')  # self.request == conn管道
            print(from_client_data)

            to_client_data = input('>>>').strip()
            self.request.send(to_client_data.encode('utf-8'))



if __name__ == '__main__':

    ip_port = ('127.0.0.1',8848)
    server = socketserver.ThreadingTCPServer(ip_port,MyServer)
    # server.allow_reuse_address = True
    # print(socketserver.ThreadingTCPServer.mro())
    # [ThreadingTCPServer, ThreadingMixIn,TCPServer, BaseServer]
    server.serve_forever()

    # 1. 入口点:ThreadingTCPServer()

客户端

import socket

# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写

# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8848))

# 发消息
while 1:
    content = input('>>>').strip()

    phone.send(f'MC骚强:{content}'.encode('utf-8'))
    # 接收消息
    from_server_data = phone.recv(1024)  # 夯住,等待服务端的数据传过来
    print(f'来自服务端消息:{from_server_data.decode("utf-8")}')

# 关机
phone.close()
原文地址:https://www.cnblogs.com/nieice/p/11317856.html