一. 软件开发的架构:
C/S架构 客户端和服务端 (硬件:京东,淘宝,qq,微信,) (硬件:打印机)
client: 客户端 享受服务端提供服务
server: 服务端 给客户提供给服务
B/S架构 浏览器和服务端 (chrom,火狐,360,sogou,IE)
brower: 浏览器 (所有的B/S架构也是C/S架构 web编程 都是B/S架构的)
二.网络基础:
1.俩台机器之间的通信:
2.多台机器之间的通信:
通过交换机
网卡: 电脑插网线的地方就叫做网卡,网卡提供了网络的接口,用mac地址可以找到网卡
mac地址:(物理地址) 8C - EC - 4B - 87 - 99 - D7 长度为48位2进制,通常由16进制的6组数表示,前三位厂商编号,后三位生产流水号,全球唯一的,相当于身份证,
发送端和接收端的地址便是指网卡的地址,即mac地址.
查看mac地址的方法:windows下在cmd窗口输入ipconfig指令,下面显示的这个物理地址就是mac地址.
ip地址:
每台机器在网络中除了有一个mac地址生效,
还有一个ip地址,虚拟地址,是一个随着时间/空间变化而改变的
ip地址的俩种协议:
ipv4 : 0.0.0.0 - 255.255.255.255 4位点分十进制
ipv6 : 0.0.0.0.0.0 - 255.255.255.255.255.255 6位:(冒号)分十六进制
特殊的俩种ip地址:
127.0.0.1 回环地址 测试地址 只有我自己能找到我 不过交换机直接找到自己 访问不到别人
0.0.0.0
arp协议功能:
通过要找的哪台机器的ip地址获取到mac地址的过程
交换机的工作方式:
交换机: 只认识mac地址,不认识ip地址(升级版的集线器)
单播 : 单独发给某个人或者说某个设备
广播 : 将要找的机器的信息发给局域网内的所有机器
组播: IP网段 : 192.168.15.0 - 192.168.15.255 属于同一子网
广播风暴: 不安全,容易拥堵网络
集线器:将所有插上集线器的电脑连通起来
内网ip和公网ip:
内网ip :内网就是我们平常说的局域网 校园网 公司的员工网
保留字段,专门给内网使用的,公网ip永远不会占用内网ip的地址
192.168.0.1 - 192.168.255.255
172.168.0.1 - 172.168.255.255
10.0.0.1 - 10.255.255.255
永远不会出现在公网里面
公网ip : 外网也就是广域网 外网又称为公网无论你在哪儿都可以使用的ip
0.0.0.0 - 255.255.255.255 全球的所有ip地址的集合
152.168.0.1 - 对应着一个固定的服务
局域网 : 局域网可以是独立封闭运行的,也可以是和外网相连接的
广域网 : 地域跨度非常大的网络集合,由无数个局域网+独立服务器构成的 此处所说的局域网既可以是小型的广域网,也可以 是局域网。
内网和外网的区别:
局域网内 IP地址是唯一的 但是在另外一个局域网这个IP地址仍然能够使用。我们就说这2台机器分别在2个局域网里
广域网内 所有的IP地址都是唯一的 山西电信的DNS服务器IP地址在全世界都是唯一的,不可重复的
如果只有内网的ip地址你是不能上网的,你上网肯定要通过一个外网地址,这个外网地址又称为公网地址,这个公网地址是全球唯一的,他在你的对外出口的路由器上,也就是你的外 网网关地址。
路由协议: 计算最优路径
路由器:
路由表
认识ip地址
管理网络,联通外网,并且路由转发,就是转发消息
机器a -->机器b
机器a -->交换机 -(网关ip)-> 路由器 -...-> 路由器 -(网关ip)-> 交换机 --> 机器b
我怎么知道机器a和机器b不是一个网段的?
子网掩码 :
1.所谓”子网掩码”,就是表示子网络特征的一个参数。它在形式上等同于IP地址也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。比如,IP地址172.16.10.1,如果已 知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。
2. 计算目标IP地址是否和咱们是同一网段 同一网段的:广播发送 不同网段的:发送给路由器
机器a和子网掩码 进行一个按位与 网段信息
机器b和子网掩码 进行一个按位与 网段信息
两个网段信息一致说明是一个局域网的,不一致说明不在一个局域网
3. 网段: (AND)按位与运算: 1和任何数与都得任何数 0和任何数与都是0
255.255.255.0
print(bin(192))
print(bin(168))
print(bin(11))
print(bin(229))
11000000.10101000.00001011.11100101
11000000.10101000.00001011.10101000
11111111.11111111.11111111.00000000
网关:
把关用的,公网IP或者外网IP,也可以说是路由器的IP地址
端口:
标识电脑上某个应用程序,范围0-65535 0-1024 内部程序用的,我们一般使用的都是8000以后的 通 过IP地址+端口:我就能唯一确定一台电脑上的某个应用程序
192.168.11.229:3306 qq
DNS服务器:
1. 记录着所有的域名和他网站对应的那台服务器的IP地址的对应关系,理 解为一个字典 {'www.jd.com':192.168.15.12}
2. DNS : 是计算机域名系统它是由域名解析器和域名服务器组成的 域名必须对应一个IP地 址,一个IP地址可以有多个域名,而IP地址不一定有域名
3. 它主要有两种形式:主服务器和转发服务器。将域名映射为IP地址的过程就称为“域名解析”
DHCP协议: 自动分配IP地址
NAT: 网络地址转换,将你局域网的IP地址转换为公网的IP地址,也就是网关的IP地址
三.osi七层模型
人们按照分工不同把互联网协议从逻辑上划分了层级:
每层运行常见的物理设备:
每层运行常见的协议:
osi五层:
应用程 : 是最靠近用户的OSI层,这一层为用户的应用程序
传输层 : 建立端口到端口的通信(端对端通信) 端口范围0-65535,0-1023为系统占用端口
网络层 : 引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址(IP协议,子网掩码,IP数据包,APP协议)
数据链路层 : 定义了电信号的分组方式 (以太网协议,mac地址,广播)
物理层: 主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
socket : 在抽象层 应用层和运输层的中间
四. socket 概念
socket层:
理解socket:
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也可以将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。
server 服务端
import socket #引入网络编程模块
server = socket.socket() #创建一个socket对象 相当于创建了一部电话
ip_port = ('192.168.15.50',8001) #创建了一张电话卡
#绑定IP地址和端口
server.bind(ip_port) #插上电话卡
#监听IP地址和端口
server.listen() #开机,后面等待链接的客户端个数为个
#等待客户端的链接
conn, addr = server.accept() #阻塞住,一直等到有人连接我,
# 连接之后得到一个元祖,里面是连线通道conn和对方的地址(ip+端口)
while True:
#接收消息
from_client_msg = conn.recv(1024) #1024为消息大小,单位B,MB = 1024KB,1KB = 1024B
#接收的消息是bytes类型,需要转换为字符串
from_client_msg = from_client_msg.decode('utf-8')
print(from_client_msg)
msg = input('我:')
conn.send(msg.encode('utf-8'))
#关闭链接
conn.close()
server.close()
client 客户端
import socket #引入网络编程模块
client = socket.socket()
server_ip_port = ('192.168.15.50',8001)
#链接服务端
client.connect(server_ip_port)
while True:
msg = input('客户想说:')
#发消息
client.send(msg.encode('utf-8')) #send里面的消息必须是字节类型的
from_server_msg = client.recv(1024) #阻塞住,等待接收消息
from_server_msg = from_server_msg.decode('utf-8')
print(from_server_msg)
client.close()
五.tcp协议和udp协议
tcp协议:
当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地 址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。
这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止
建立连接: 三次握手
断开连接:四次挥手
六.tcp协议和udp协议的对比
TCP:(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
UDP:(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
基于TCP协议的socket
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)
client端
import socket
sk = socket.socket() # 创建客户套接字
sk.connect(('127.0.0.1',8898)) # 尝试连接服务器
sk.send(b'hello!')
ret = sk.recv(1024) # 对话(发送/接收)
print(ret)
sk.close() # 关闭客户套接字
1.连接资源有限
同一时刻只能和一个客户端沟通
2.每一次数据的发送都需要一个回执来确保数据可靠
数据发送的效率不高
server端
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(("192.168.11.117",9000))
sk.listen()
while True:
conn,addr = sk.accept()
while True:
msg = conn.recv(1024)
print(msg.decode("utf-8"))
message = input(">>>")
conn.send(message.encode("utf-8"))
if message =="q":
break
conn.close()
sk.close()
client 端
import socket
sk = socket.socket()
sk.connect(("192.168.11.117",9000))
while True:
message = input(">>>")
sk.send(message.encode("utf-8"))
ret = sk.recv(1024)
if ret == b"q":
break
print(ret.decode("utf-8"))
sk.close()
问题:有的同学在重启服务端时可能会遇到
解决方法:
#加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 # 只在测试的时候添加,到正式的生产环境中应该去掉
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)
端口被占用的原因:
bind 已经向操作系统申请了一个9000的端口
如果9000没有被占用,那么就批准给你使用
直到sk.close的时候,这个端口才归还给操作系统
程序结束但是忘记close关闭,
那么操作系统就不能及时发现这个端口已经被归还
当你重启程序再次申请使用9000端口的时候
操作系统告诉你这个端口被占用了
网络上是否连通决定了两台电脑能否通信
和tcp建立了连接之后才能通信完全是两回事儿
基于UDP协议的socket
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接
1先把udp协议的cs调通
2 让cs之间通信多说点儿话
3 让多个client和server进行通信
4在server端根本就不应该有input
server端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM) #写上这句话表示是一个udp协议
#什么都不写就是tcp协议,
addr =("192.168.11.117",8000)
sk.bind(addr)
#udp来说 第一个信息 必须是先接受
while True:
msg,cli_addr = sk.recvfrom(1024)
print(msg.decode("utf-8"))
sk.sendto("收到".encode("utf-8"),cli_addr)
sk.close()
client 端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
while True:
message = input(">>>")
sk.sendto(message.encode('utf-8'), ('192.168.11.117', 8000))
msg,ser_ader = sk.recvfrom(1024)
print(msg.decode("utf-8"))
sk.close()
七.黏包现象
一.黏包现象:
只有TCP有粘包现象,UDP永远不会粘包
udp协议:面向数据包的,无连接的:对方的ip+port+mac整条信息的长度+msg信息不会产生黏包现象,不能传输过大的文件和数据
server端
import socket sk = socket.socket() sk.bind(("127.0.0.1",8000)) sk.listen() conn,addr = sk.accept() conn.send("hello,".encode("utf-8")) conn.send("world".encode("utf-8")) conn.close() sk.close()
client 端
import socket sk = socket.socket() sk.connect(("127.0.0.1",8000)) print(sk.recv(3)) print(sk.recv(10)) sk.close()
二.黏包现象是如何产生的:
黏包现象并不是一个bug ,而是各种tcp协议的特点和算法导致了现在的问题
合包机制 Nagle算法
拆包机制
流式传输无边界
三.黏包一定是坏现象么:
对于应用层.的程序来说.大部分时候都不是好事,所以我们才要解决它
但是从程序的传递过程中的角度:
解决了大量的短数据大量回执的问题
长数据由于网络限制的拆包在我们的接收端可以自动拼接
一.合包机制:
1.黏包现象一:在发送端,由于俩个数据短,发送时间间隔短,所以在发送端形成了黏包
2.黏包现象二.在接收端,由于俩个数据同时被发送到操作系统的缓存中,所以在接收端形成了黏包
3. 核心问题: 只要是在tcp的连接后发送的所有数据之间是没有边界的
4. 优势:可以任意send大小的数据
二.tcp协议的流式传输:
就是这些信息源源不断的在网线当中通过高低电压来传递
这种状况就叫做流式传输
还有一种是因为tcp协议无边界的特点,对于这种无边界的源源不断的通过连接
就能传输过来的数据就叫做流式传输
当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。
三. 面向流的通信特点和Nagle算法
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。
可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
黏包现象基础的解决:
import struct #struct 模块 [s chua k t] 将所有的数字通过pack转换成一个4字节的固定的值
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
conn,addr = sk.accept()
inp = input('msg :')
msg = (inp).encode('utf-8')
ret = struct.pack('i',len(msg)) # 将所有的数字 通过pack 转换成一个4字节的固定的值
conn.send(ret)
conn.send(msg)
conn.send('world'.encode('utf-8'))
conn.recv(1024)
conn.close()
sk.close()
# 发送两条连续的数据
# 第一条数据
# 转成bytes
# 计算bytes类型数据的长度
# 将长度转换成一个4个字节的格式
# 发送4个字节的长度
# 再发送信息
import time
import struct
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
time.sleep(0.1)
# 'bytehello...,world'
n = sk.recv(4)
ret = struct.unpack('i',n)[0] #得到的结果是一个元祖,永远需要用res[0]取值
print(ret)
print(sk.recv(ret)) #'hello,worl' 'hello,world'
print(sk.recv(10)) # 'd' '一直等待接收'
sk.send(b'hahaha')
sk.close()
# 先接收4个字节
# 接收进来的就是字节格式的数据长度
# 在根据工具进行转换得到int数据类型的长度
# 根据得到的长度recv
什么时候需要解决黏包: 连续send且不是在大文件传输过程中
发送大文件 不需要解决黏包问题
常用的解决黏包的场景:
文件下载你是客户端,客户端下载从server端发送一个文件到客户端client端接收信息知道接收的内容和文件大小一致
过程 :
server 要想发送具体的文件数据,先要将文件的大小\文件的名称都要发送给client端
{'filename': 'movie.mp4','filesize':123849875}要发送的信息转成str之后转成bytes
再将上面的信息计算长度,转成struct格式-->4个字节
发送4个字节
再发送bytes字典信息
end(字典信息)
open('movie.mp4','rb')
循环读,send(一部分bytes类型的文件内容)
socketserver 模块
socket端
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# 这个handle方法是每有一个客户端发起connect之后,就会执行handle
# 在建立连接之后的所有内容都在handle中实现就可以了
# ThreadingTCPServer帮助我们完成了tcp协议的server端的并发
conn = self.request
while True:
msg = conn.recv(1024).decode('utf-8')
print(msg)
conn.send(msg.upper().encode('utf-8'))
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)
server.serve_forever()
client端
import time
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
sk.send(b'hello')
msg = sk.recv(1024)
print(msg)
sk.close()
效验客户端合法性:
server端
import os
import socket
import hashlib
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
conn,addr = sk.accept()
# 检验客户端是否合法
# 秘钥
secret_key = b'alex_sb'
# server端给client端发送一个随机的字符串
rand_code = os.urandom(32)
conn.send(rand_code)
# 对这个随机字符串和secret_key进行摘要运算
md5obj = hashlib.md5(secret_key)
md5obj.update(rand_code)
str_ret = md5obj.hexdigest()
# 接收从client端发回的摘要结果
str_msg = conn.recv(1024).decode('utf-8')
# 对比如果两个结果是一致的
if str_msg == str_ret:
print('是合法的客户端')
print('和client端互相通信')
conn.close()
else:
print('是不合法的客户端')
conn.close()
# 只有程序认可的用户才能使用我的server端
# 1.登陆
# 2.随意的一个server
# 只要是我写的client端都可以使用我的server
# 公司通用业务
# 在建立和client端的连接之后
# 有一种检测这个客户端是否合法的机制
# 如果合法 再继续通讯
# 如果不合法 直接关闭
client端
import socket
import hashlib
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
secret_key = b'alex_sb'
# 收到随机字符串
rand_code = sk.recv(32)
# 对这个随机字符串和secret_key进行摘要运算
md5obj = hashlib.md5(secret_key)
md5obj.update(rand_code)
str_ret = md5obj.hexdigest()
# 把结果发回server
sk.send(str_ret.encode('utf-8'))
print('校验完成之后可以再做其他的很多事')
sk.close()
效验客户端合法性的进阶:
server端
import os import socket import hashlib def check_conn(conn): secret_key = b'alex_sb' rand_code = os.urandom(32) conn.send(rand_code) md5obj = hashlib.md5(secret_key) md5obj.update(rand_code) str_ret = md5obj.hexdigest() str_msg = conn.recv(1024).decode('utf-8') if str_msg == str_ret: return True else: return False sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() conn,addr = sk.accept() if check_conn(conn): '''写你本身要写的代码''' conn.send('你是合法的连接'.encode()) msg = conn.recv(1024) print(msg.decode()) conn.close()
client端
import socket import hashlib def check_conn(sk): secret_key = b'alex_sb' rand_code = sk.recv(32) md5obj = hashlib.md5(secret_key) md5obj.update(rand_code) str_ret = md5obj.hexdigest() sk.send(str_ret.encode('utf-8')) sk = socket.socket() sk.connect(('127.0.0.1',9000)) check_conn(sk) # 以下部分你可以自由发挥 print(sk.recv(1024).decode()) sk.send('那么愉快的开始沟通吧'.encode('utf-8')) sk.close()
效验客户端合法性的继续进阶:
server端
import os import socket import hmac def check_conn(conn): secret_key = b'alex_sb' rand_code = os.urandom(32) conn.send(rand_code) obj = hmac.new(secret_key,rand_code) byte_ret = obj.digest() byte_msg = conn.recv(1024) if byte_ret == byte_msg: return True else: return False sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() conn,addr = sk.accept() if check_conn(conn): '''写你本身要写的代码''' conn.send('你是合法的连接'.encode()) msg = conn.recv(1024) print(msg.decode()) conn.close()
client端
import socket import hmac def check_conn(sk): secret_key = b'alex_sb' rand_code = sk.recv(32) obj = hmac.new(secret_key,rand_code) byte_ret = obj.digest() sk.send(byte_ret) sk = socket.socket() sk.connect(('127.0.0.1',9000)) check_conn(sk) # 以下部分你可以自由发挥 print(sk.recv(1024).decode()) sk.send('那么愉快的开始沟通吧'.encode('utf-8')) sk.close()
锦上添花:
arp协议 :
1.这是一个通过ip找mac地址的协议
2.由于有了socket,用户在使用网络的时候,只需要关心对方用户的ip地址就可以了
3.如果用户即将和这个ip进行通信,那么还需要知道它的mac地址
4.这个时候就需要由你的机器发起一个arp请求
5.由交换机进行广播
6.对应的机器会回应这个arp请求
7.通过交换机单播发给你的机器
tcp协议和udp协议的特点?
1.tcp
面向连接的可靠的流式传输 适合传输比较大的文件,
对稳定性要求比较高的
扩展的说 为什么 可靠?
2.udp
无连接的 快速 但不可靠
适合传输对效率要求比较高的短消息
你了解4层交换机么?
了解,4层就是osi协议中的第4层,传输层
这一层封装的是端口的信息和tcp协议以及udp协议
所以4层交换机就是可以直接识别传输层协议和端口信息的机器
能够实现信息输出直接到端口
神笔马良:
应用层 http https ftp smtp
socket
传输层 tcp/udp协议 端口 4层交换机\4层路由器
网络层 ipv4/ipv6协议 ip 路由器\三层交换机
数据链路层 arp协议 mac地址 网卡\交换机
物理层 网线
以下哪些硬件设备是属于网络层的?
什么是socket?
网络协议的大接口,帮助我们完成网络传输过程中的osi4层以及一下信息的封装
tcp协议 : 要先建立连接 占线 用socketserver解决
udp协议: 不需要建立连接 且可同时和多个客户端进行交互
黏包现象怎么产生的
首先,我们发出的信息不是立即通过网络传送到另一端
而是我们发到操作系统的缓存中,
tcp协议首先流式传输无边界,第二是可靠所以每一条数据都有回执
那么为了节省网络上延迟的时间
连续发送出的多个短信息就会黏在一起
由发送端的缓存发送除去,所以接收到的就是黏在一起的数据了
而是发到了对方操作系统的缓存中
如果连续发送的数据在对方的缓存中没有被及时取走
那么也会发生黏包现象
自定义协议
先发送即将发送数据的长度,然后再发送数据
先接收数据的长度,再根据接收的长度接收数据
用到了struct模块,来控制第一次发送数据长度的这条信息的长度