Loading

Python基础5(网络)

网络协议篇

1.C/S B/S架构

C: client端

B: browse 浏览器

S: server端

C/S架构:

基于客户端与服务端之间的通信

QQ, 游戏, 皮皮虾, 快手, 抖音

优点:

个性化设置, 响应速度快

缺点:

开发成本, 维护成本高, 占用空间, 用户固定

B/S架构:

基于浏览器与服务端之间的通信

谷歌浏览器, 360浏览器, 火狐浏览器等等

常用谷歌, 或 火狐,广告少, 源码易解读

优点:

开发维护成本低, 占用空间相对低, 用户不固定

缺点:

功能单一, 没有个性化设置, 相应速度相对慢一些

2.网络通信原理

80年代, 固定电话联系(还没有推广普通话)

  1. 两台电话之间一堆物理连接介质连接

  2. 拨号, 锁定对方电话的位置.

由于当时没有统一普通话, 所以你如果和河南, 山西, 广 西, 福建等朋友进行友好的沟通交流, 你必须学当地的方言.

推广普通话, 统一交流方式:

  1. 两台电话之间一堆物理连接介质连接.

  2. 拨号, 锁定对方电话的位置

  3. 统一交流方式

全球范围内交流:

  1. 两台电话之间一堆物理连接介质连接.

  2. 拨号, 锁定对方电话的位置.

  3. 统一交流方式.(英语)

互联网如何通信:

现在想和一个美国人联系, 如何利用计算机联系:

  1. 两台计算机要有一堆物理连接介质连接.

  2. 找到对方计算机软件位置

  3. 遵循一揽子互联网通信协议

3. osi 七层协议

img

![img](file:///E:/%E4%B8%8B%E8%BD%BD/%E6%9C%89%E9%81%93%E4%BA%91/weixinobU7Vjs9Qg7e6rojUIA8HWUqTb-g/55bda47d884148ef8050236a441079af/clipboard.png)

简单串联五层协议以及作用

1.物理层

物理层指的就是网线, 光纤, 双绞线等等物理连接介质.物理层发送的是比特流(01010101010),所以有一个问题,数据应该有规律的分组, 分组是数据链路层做的事情

2.数据链路层

数据链路层对比特流进行分组. 最开始从事互联网企业的就是美国的几家公司, 各家有各家自定的分组标准. 后来统一了标准: 对数据分组的标准.

以太网协议

对比特流进行合理的分组. 一组数据01010101 叫做一帧, 数据报. 例如:

head | data(晚上约么)

head 是固定的长度: 18个字节

源地址: 6个字节

目标地址: 6个字节

数据类型: 6个字节

data: 最少是46个字节, 最大1500字节.

一帧数据: 最少64个字节, 最大1518个字节

一帧数据 | 一帧数据 | 一帧数据........

mac地址

每个电脑上都有一个网卡, 网卡上都记录着一个独一无二的地址. mac地址就是你的计算机上网卡标注的地址.

12位16进制数组成: 前六位是厂商编号, 后六位是流水线号.

源mac地址 目标mac地址 数据类型 | data

"FF - FF - FF - FF - FF - FF"

'1C - 1B - 0D - A4 - E6 - 44'

计算机的通信方式:

同一个局域网内, 通过广播的形式通信.

消息一经广播发出, 局域网所有的计算机都能接收到消息, 分析消息, 是否是找我的, 不是就丢弃

计算机只能在局域网内进行广播: 范围大了 广播风暴, 效率极低

不同局域网如何通信

软件与软件的通信, 而不是计算机之间的通信

3.网络层

IP协议:

确定局域网(子网)的位置

找到具体软件的位置, 上一层的事情

4.传输层

端口协议

确定软件在计算机的位置

5.应用层

自己定义的协议

广播(局域网内) + mac地址(计算机位置) + ip(局域网的位置) + 端口(软件在计算机的位置).

有了以上四个参数: 你就可以确定世界上任何一个计算机的软件的位置

img

4.对五层协议详细的补充说明

数据链路层补充:

同一个局域网通过广播的形式发送数据

交换机的mac地址学习功能:

一个交换机的5个接口: 5个计算机

1: FF-FF-FF-FF-FF-FF

2: FF-FF-FF-FF-FF-FF

3: FF-FF-FF-FF-FF-FF

4: FF-FF-FF-FF-FF-FF

5: FF-FF-FF-FF-FF-FF

接口1: 源mac 1C - 1B - 0D - A4 - E6 - 44 目标1C - 1C - 0D - A4 - E5 - 44 |数据 以广播的形式发出2,3,4,5口都会接收到消息, 5口是最终的目标地址, 交换机就会将5口与mac地址对应上

1: 1C-1B-0D-A4-E6-44

2: FF-FF-FF-FF-FF-FF

3: FF-FF-FF-FF-FF-FF

4: FF-FF-FF-FF-FF-FF

5: 1C-1C-0D-A4-E5-44

我们的前提是什么? 你必须知道对方的mac地址你才可以以广播的形式发消息.实际上,网络通信中,你只要知道对方的IP与自己的IP即可.

网络层:

IP协议:

ip地址:

四段分十进制 192.168.0.12 取值范围 0255.0255.0255.0255 子网掩码: C类子网掩码: 255.255.255.0

ip地址 + 子网掩码 按位与运算 计算出是否在统一局域网(子网,网段).

计算172.16.10.1 与 172.16.10.128

172.16.10.1: 10101100.00010000.00001010.00000001 255.255.255.0: 11111111.11111111.11111111.00000000 从属于的局域网: 172.16.10.0

172.16.10.128: 10101100.00010000.00001010.10000000 255.255.255.0: 11111111.11111111.11111111.00000000 从属于的局域网: 172.16.10.0

172.16.10.1 ~172.16.10.255

C类子网掩码 一个网段最多可以承载多个IP地址?

172.16.10.0 被占用.

172.16.10.255 广播地址 被占用.

172.16.10.1 被占用. 253台计算机.

如果你要想给另一个计算机发数据, 你一定要知道对方的ip地址.

ARP协议:
源mac 目标mac 源ip 目标ip 数据部分
发送端主机 发送端mac FF:FF:FF:FF:FF:FF 192.16.10.1 192.16.10.156

源mac 08-D2-3E-23-B6-62

目标mac FF:FF:FF:FF:FF:FF:

源ip 192.16.10.1

目标ip 192.16.10.156

判断在不在同一网段,

第一次发消息:发送到交换机 路由器 广播发出

目标计算机收到消息:回消息

源mac 1C-1B-0D-A4-E6-44

目标mac 08-D2-3E-23-B6-62

源ip 192.16.10.1

目标ip 192.16.10.156

img

img

5.TCP UDP

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

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

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

tcp三次握手

一、TCP报文格式

img

    在了解三次握手和四次挥手之前,先知道TCP报文内部包含了哪些东西。

  1. TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。TCP在发送数据前必须在彼此间建立连接,这里连接意思是:双方需要内保存对方信息(例如:IP,Port…)
  2. 报文主要段的意思

    序号:表示发送的数据字节流,确保TCP传输有序,对每个字节编号

    确认序号:发送方期待接收的下一序列号,接收成功后的数据字节序列号加 1。只有ACK=1时才有效。

    ACK:确认序号的标志,ACK=1表示确认号有效,ACK=0表示报文不含确认序号信息

    SYN:连接请求序号标志,用于建立连接,SYN=1表示请求连接

    FIN:结束标志,用于释放连接,为1表示关闭本方数据流

二、“三次握手”

2.1 三次握手过程

建立TCP连接时,需要客户端和服务器共发送3个包。

  • 第一次:客户端发送初始序号x和syn=1请求标志
  • 第二次:服务器发送请求标志syn,发送确认标志ACK,发送自己的序号seq=y,发送客户端的确认序号ack=x+1
  • 第三次:客户端发送ACK确认号,发送自己的序号seq=x+1,发送对方的确认号ack=y+1

img

2.2 三次握手过程分析:

  • 第一次:客户端发送请求到服务器,服务器知道客户端发送,自己接收正常。SYN=1,seq=x
  • 第二次:服务器发给客户端,客户端知道自己发送、接收正常,服务器接收、发送正常。ACK=1,ack=x+1,SYN=1,seq=y
  • 第三次:客户端发给服务器:服务器知道客户端发送,接收正常,自己接收,发送也正常.seq=x+1,ACK=1,ack=y+1

上面分析过程可以看出,握手两次达不到让双方都得出自己、对方的接收、发送能力都正常的结论的。

三、 “四次挥手”

3.1 四次挥手过程

  • 第一次挥手:客户端发出释放FIN=1,自己序列号seq=u,进入FIN-WAIT-1状态
  • 第二次挥手:服务器收到客户端的后,发出ACK=1确认标志和客户端的确认号ack=u+1,自己的序列号seq=v,进入CLOSE-WAIT状态
  • 第三次挥手:客户端收到服务器确认结果后,进入FIN-WAIT-2状态。此时服务器发送释放FIN=1信号,确认标志ACK=1,确认序号ack=u+1,自己序号seq=w,服务器进入LAST-ACK(最后确认态)
  • 第四次挥手:客户端收到回复后,发送确认ACK=1,ack=w+1,自己的seq=u+1,客户端进入TIME-WAIT(时间等待)。客户端经过2个最长报文段寿命后,客户端CLOSE;服务器收到确认后,立刻进入CLOSE状态。

img

3.2四次挥手过程分析

  • 第一次:客户端请求断开FIN,seq=u
  • 第二次:服务器确认客户端的断开请求ACK,ack=u+1,seq=v
  • 第三次:服务器请求断开FIN,seq=w,ACK,ack=u+1
  • 第四次:客户端确认服务器的断开ACK,ack=w+1,seq=u+1

四、其他问题

4.1为什么三次握手和四次挥手?

  • 三次握手时,服务器同时把ACK和SYN放在一起发送到了客户端那里
  • 四次挥手时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和 FIN 一般都会分开发送。

4.2为什么客户端最后还要等待2MSL?

  • 客户端需要保证最后一次发送的ACK报文到服务器,如果服务器未收到,可以请求客户端重发,这样客户端还有时间再发,重启2MSL计时。

img

路由:

选取最右路线

别的计算机只能访问到外网

外网(公网)IP

内网(局域网)IP,DHCP协议:路由器自动分发的ip地址,网关等等

DNS域名解析服务器:

NAT:ip置换技术 www.baudi.com 置换 123.125.26.56:8080

socket 网络套接字

img

socket是处于应用层与传输层之间的抽象层,他是一组操作简单的接口, 接口接受数据之后,交给操作系统。如果直接与操作系统数据交互非常麻烦,socket对这些繁琐的操作高度封装,简化。在python就是一个模块

连接浏览器

import socket 
server=socket.socket() 
server.bind(("127.0.0.1",8001)) 
server.listen()
while 1: 
  conn,addr=server.accept() 
  fron_bowers_msg=conn.recv(1024) 
  print(fron_bowers_msg.decode('utf-8')) 
  data=b'HTTP/1.1 200 ok\r\n\r\n' with open('test.html',"rb") as f:
  sen_msg=f.read() 
  conn.send(data) 
  conn.send(sen_msg) 
conn.close()

基于TCP协议的socket简单通信

客户端(cliend)

import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 默认基于TCP协议的socket
phone.connect(("127.0.0.1", 8848))
data = input("请输入")
phone.send(date.encode("utf-8"))
from_server_data = phone.recv(1024)
print(f"来自服务端的消息:{from_server_data.decode('utf-8')}")
phone.close()

服务端(server)

import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 默认基于TCP协议的socket
phone.bind(("192.168.14.198", 8848))
phone.listen(5)
print(111)
conn,addr = phone.accept() # 阻塞
print(conn, addr)
from_client_data = conn.recv(1024)
print(f"来自客户端{addr[0]的消息:{from_client_data.decode("utf-8")}")
to_client_data = input(">>>")
conn.send(to_client_data.encode("utf-8"))
conn.close()
phone.close()

基于TCP协议的socket 链接+循环 通信

server(服务器端)
import socket
phone = socket.socket()
phone.bind(("172.0.0.1", 8848))
phone.listen()
while 1:
    conn, addr = phone.accept()
    print(conn, addr)
    while 1:
        from_client_data = conn.recv(1024)
        print(f"客户端传输进来的信息:{from_client_data.strip().decode('utf-8')}")
        to_client_data = input("传给客户端的信息:")
        conn.send(to_client_data)
    except ConnectionResetError:
        print("客户端中断")
        break
    conn.close()
phone.close()
client(客户端)
import socket
phone = socket.socket()
phone.connect(("127.0.0.1", 8848))
while 1:
    data = input("传向服务器端的信息:").strip().encode("utf-8")
    if not data:
        print("输入不得为空,会双向阻塞出bug")
        continue
    phone.send(data)
    if data.upper() == b"Q":
        print("退出成功")
        break
    from_server_data = phone.recv(1024)
    print(f"服务端传输进来的信息{from_server_data.strip().decode('utf-8')}")
phone.close()

基于TCP协议的socket通信 实例: 远程执行命令

server(服务器端)
import socket
import subprocess
phone = socket.socket()
phone.bind(("172.0.0.1", 8848))
phone.listen()
while 1:
    conn, addr = phone.accept()
    print(conn, addr)
    while 1:
        from_client_data = conn.recv(1024)
        # print(f"客户端传输进来的信息:{from_client_data.strip().decode('utf-8')}")
        obj = subprocess.Popen(from_client_data.decode('utf-8'),
                 shell=True,
                 stdout=subprocess.PIPE.
                 stderr=subprocess.PIPE)
        to_client_data = obj.stdout.read() + obj.stderr.read()
        conn.send(to_client_data)
    except ConnectionResetError:
        print("客户端中断")
        break
    conn.close()
phone.close()
client(客户端)
import socket
phone = socket.socket()
phone.connect(("127.0.0.1", 8848))
while 1:
    data = input("传向服务器端的信息:").strip().encode("utf-8")
    if not data:
        print("输入不得为空,会双向阻塞出bug")
        continue
    phone.send(data)
    if data.upper() == b"Q":
        print("退出成功")
        break
    from_server_data = phone.recv(1024)
    print(f"服务端传输进来的信息{from_server_data.strip().decode('gbk')}")
phone.close()

Socketserver

# 服务端
import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        while 1:
            from_client_msg = self.request.recv(1024)
            print(from_client_msg.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", 8002), MyServer)
    server.serve_forever()
# 客户端
import socket

client = socket.socket()
server_ip_port = ("127.0.0.1", 8002)

client.connect(server_ip_port)
while 1:
    to_server_msg = input(">>>").encode("utf-8")
    client.send(to_server_msg)
    from_server_msg = client.recv(1024).decode("utf-8")
    print(">>>", from_server_msg)
 

缓冲区

每个 socket 被创建后, 都会分配两个缓冲区, 输入缓冲区和输出缓冲区

write( )/send( ) 并不立即向网络中传输数据, 而是先将数据写入缓冲区中, 再由TCP协议将数据从缓冲区发送到目标机器. 一旦将数据写入到缓冲区, 函数就可以成功返回, 不管他们有没有到达目标机器, 也不管它们何时被发送到网络, 这些都是TCP协议负责的事情.

TCP协议独立于 write( )/send( ) 函数, 数据有可能刚被写入缓冲区就发送到网络, 也可能在缓冲区中不断积压, 多次写入的数据被一次性发送到网络, 这取决于当时的网络情况, 当前线程是否空闲等诸多因素, 不由程序员控制.

read( )/recv( ) 函数也是如此, 也从输入缓冲区中读取数据, 而不是直接从网络中读取.

I/O 缓冲区特性

  1. I/O 缓冲区在每个TCP套接字中单独存在

  2. I/O 缓冲区在创建套接字时自动生成

  3. 即使关闭套接字也会继续传送输出缓冲区中遗留的数据

  4. 关闭套接字将丢失输入缓冲区中的数据

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

缓冲区的作用

暂时存储一些数据.

缓冲区存在, 如果你的网络波动, 保证数据的收发稳定, 匀速

缺点: 造成了粘包现象之一

粘包

发生粘包的两种情况

接收方没有及时接收缓冲区的包, 造成多个包接收(客户端发送了一段数据, 服务端只收了一小部分, 服务端下次再收的时候还是从缓冲区拿上次遗留的数据, 产生粘包)

server(服务器端)
import socket
import subprocess
phone = socket.socket()
phone.bind(("172.0.0.1", 8848))
phone.listen()
while 1:
    conn, addr = phone.accept()
    print(conn, addr)
    while 1:
        from_client_data = conn.recv(1024)
        # print(f"客户端传输进来的信息:{from_client_data.strip().decode('utf-8')}")
        obj = subprocess.Popen(from_client_data.decode('utf-8'),
                 shell=True,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE)
        to_client_data = obj.stdout.read() + obj.stderr.read()
        conn.send(to_client_data)
    except ConnectionResetError:
        print("客户端中断")
        break
    conn.close()
phone.close()
client(客户端)
import socket
phone = socket.socket()
phone.connect(("127.0.0.1", 8848))
while 1:
    data = input("传向服务器端的信息:").strip().encode("utf-8")
    if not data:
        print("输入不得为空,会双向阻塞出bug")
        continue
    phone.send(data)
    if data.upper() == b"Q":
        print("退出成功")
        break
    from_server_data = phone.recv(1024)
    print(f"服务端传输进来的信息{from_server_data.strip().decode('gbk')}")
phone.close()

当客户端发的命令获取的结果大小已经超过客户端recv上限的1024, 那么下次输入命令时, 会继续取上次残留到缓存区的数据

发送数据时间间隔很短, 数据也很小时, 会合到一起, 产生粘包

server服务端
import socket
phone = socket.socket()
phone.bind(("127.0.0.1", 8848))
phone.listen()
conn,addr = phone.accept()
print(conn, addr)
from_client_data = conn.recv(1024)
print(f"来自客户端的消息:{from_client_data.decode('utf-8')}")
to_client_data = input(">>>")
conn.send(to_client_data.encode("utf-8"))
conn.close()
phone.close()
client客户端
import socket
phone = socket.socket()
phone.connect(("127.0.0.1", 8848))
phone.send(b"he")
phone.send(b"llo")
from_server_data = phone.recv(1024)
print(f"来自服务端的消息:{from_server_data.decode('utf-8')}")
phone.close()

如何解决粘包现象

思路

服务端发一次数据, 10000字节, 客户端接收数据时, 循环接收, 每次(至多) 接收 1024 个字节, 直至将所有的字节全部接收完毕, 将接收的数据拼凑在一起, 最后解码.

遇到的问题: recv的次数无法确定

发送具体的总数据之前, 先发送一个总数据的长度: 例如 5000个字节, 然后在发送总数据.

客户端: 先接收一个长度(5000个字节), 然后我再循环recv, 控制循环的条件就是只要你接受的数据 < 5000 , 就一直接收.

遇到的问题: 总数据的长度转化成的字节数不固定

server服务端
conn.send(total_size)  # 总数据长度
conn.send(result)       # 总数据
client客户端
total_size_bytes = phone.recv(4)
total_size
data = b''
while len(data) < total_size:
    data = data + phone.recv(1024)

要将 total_size int类型 转化成bytes类型 才可以发送

387 -----> str(387) --> "387" --->bytes b'387' 长度 3 bytes

4185 -----> str(4185) --> "387" --->bytes b'4185' 长度 4 bytes

	18000 ----->  str(18000) --> "18000" --->bytes b'18000'  长度 5 bytes

解决方法

我们要解决:

将不固定的长度的 int类型 转化成固定长度的bytes 并且还可以翻转回来

所以用struct模块

import struct
# 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret))

# 通过unpack反解回来
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1), len(ret1))


# 但是通过struct 处理不能处理太大

ret = struct.pack('l', 4323241232132324)
print(ret, type(ret), len(ret))  # 报错
具体解决方法(加报头)
server服务端

import socket
import struct
import subprocess

phone = socket.socket()

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

while 1:
    conn, addr = phone.accept()
    print(conn, addr)
    while 1:
        try:
            from_client_data = conn.recv(1024)
            print(f"{from_client_data.strip().decode('utf-8')}")
            obj = subprocess.Popen(from_client_data.decode("utf-8"),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
            total_size = len(obj.stdout.read()) + len(obj.stderr.read())
            
            header = struct.pack("i", total_size)
            # 制作表头
            
            conn.send(header)
            # 发送表头
            
            to_client_data = obj.stdout.read() + obj.stderr.read()
            conn.send(to_client_data)
            # 发送总数据
            
        except ConnectionResetError:
            print("客户端中断")
            break
    conn.close()
phone.close()
client客户端

import socket
import struct

phone = socket.socket()

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

while 1:
        data = input(">>>").strip().encode("utf-8")
        if not data:
            print("输入不能为空")
            continue
        phone.send(data)
        if data.upper() == b"Q":
            print("退出成功")
            break
        header = phone.recv(4)
        # 接收报头
        
        total_size = struct.unpack("i", header)[0]
        # 解析报头
        
        recv_size = 0
        from_server_data = b''
        while recv_size < total_size:
            recv_data = phone.recv(1024)
            from_server_data += recv_data
            recv_size += len(recv_data)
        # 根据报头信息, 拼接总数据
        
        print(f"{from_server_data.decode('gbk')}")
        # 一次性打印总数据, 解决粘包

phone.close()

自定制报头

1.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.
	关闭远程端并读取所有数据后,返回空字符串。

2.高大上版解决粘包方式(自定义报头)

# server服务端
import socket
import struct
import json
import subprocess

phone = socket.socket()

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

while 1:
    conn, addr = phone.accept()
    print(conn, addr)
    while 1:
        try:
            from_client_data = conn.recv(1024)
            # print(f"{from_client_data.strip().decode('utf-8')}")
            obj = subprocess.Popen(from_client_data.decode("utf-8"),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
            to_client_data = obj.stdout.read() + obj.stderr.read()
            total_size = len(to_client_data)

            head_dic = {"file_name": "test", "md5": 646846, "total_size": total_size}
            # 报头字典
            
            head_dic_json_bytes = json.dumps(head_dic).encode("utf-8")
            # 报头字典str然后转字节

            len_head_dic_json_bytes = len(head_dic_json_bytes)
            # 报头字典str字节数
            
            four_head_bytes = struct.pack("i", len_head_dic_json_bytes)
            # 制作报头字典字节int的固定字节数

            conn.send(four_head_bytes)
            # 发送报头字典字节int的固定字节数

            conn.send(head_dic_json_bytes)
            # 发送报头字典
            
            conn.send(to_client_data)
            # 发送总数据

        except ConnectionResetError:
            print("客户端中断")
            break
    conn.close()
phone.close()
# client客户端
import socket
import struct
import json

phone = socket.socket()

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

while 1:
    to_server_data = input(">>>").strip().encode("utf-8")
    if not to_server_data:
        print("输出不能为空")
    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':
        print("客户端退出")

    four_head_bytes = phone.recv(4)
    # 接收报头字典的固定字节

    len_head_dic_json_bytes = struct.unpack("i", four_head_bytes)[0]
    # 报头字典的字节数

    head_dic_json_bytes = phone.recv(len_head_dic_json_bytes)
    # 接收报头字典字节

    head_dic = json.loads(head_dic_json_bytes.decode("utf-8"))
    # 报头字典

    recv_size = 0
    from_server_data = b''
    while recv_size < head_dic["total_size"]:
        recv_data = phone.recv(1024)
        from_server_data += recv_data
        recv_size += len(recv_data)

    print(from_server_data.decode("gbk"))

3.基于UDP协议的socket通信

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),recvform接收消息,这个消息有两项,消息内容和对方客户端的地址,然后回复消息时也要带着你收到的这个客户端的地址,发送回去,最后关闭连接,一次交互结束

发消息必须带着自己的地址,这就是UDP不一样的地方,不需要建立连接,但是要带着自己的地址给服务端,否则服务端无法判断是谁给我发的消息,并且不知道该把消息回复到什么地方,因为我们之间没有建立连接通道

# server端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
phone.bind(("127.0.0.1", 8848))
while 1:
    from_msg, addr = phone.recvfrom(1024)
    print(from_msg.decode("utf-8"), addr)

    to_msg = input(">>>").strip().encode("utf-8")
    phone.sendto(to_msg, addr)
phone.close()
# client端
import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_client = ("127.0.0.1", 8848)
while 1:
    to_msg = input(">>>").strip().encode("utf-8")
    phone.sendto(to_msg, ip_client)
    if to_msg.upper() == b'Q' :
        print("退出成功")
        break

    from_msg, addr = phone.recvfrom(1024)
    print(from_msg.decode("utf-8"), addr)
phone.close()
posted @ 2021-02-24 22:10  丨渍丨  阅读(310)  评论(0)    收藏  举报