网络编程 socket tcp与udp协议
1.# ip 192.168.30.44
11000000 10101000 00011110 00101100
子网掩码 255.255.255.0
11111111 11111111 11111111 00000000
ip & 子网掩码
11000000 10101000 00011110 00000000
ip2网段: 192.168.30.0
ip1的网段和ip2的网段不同,不一样,所以不再同一个网段不能通信
案例二 改变子网掩码
ip 192.168.31.43
11000000 10101000 00011111 00101011
子网掩码 255.255.0.0
11111111 11111111 00000000 00000000
ip & 子网掩码
11000000 10101000 00000000 00000000
ip1的网段: 192.168.0.0
ip 192.168.30.44
11000000 10101000 00011110 00101100
子网掩码 255.255.0.0
11111111 11111111 00000000 00000000
ip & 子网掩码
11000000 10101000 00000000 00000000
ip2网段: 192.168.0.0
ip1的网段和ip2的网段完全相同,所以再同一个网段可以通信
ping 域名 可以查看网络是否通畅(顺便可以拿到ip)
(3)端口:某个程序与外界通讯的出口
通过ip+端口 可以找到世界上任何一台电脑的任何一个软件
端口的范围: 0 ~ 65535 形式: 192.168.2.1:8000
https://blog.csdn.net/qq_34646546/article/details/88545165 知名端口
自定义端口时,起8000以上
20 端口:FTP 文件传输协议(默认数据口)
21 端口:FTP 文件传输协议(控制)
22 端口:SSH 远程登录协议
23 端口:telnet(终端仿真协议),木马 Tiny Telnet Server 开放此端口
25 端口:SMTP 服务器所开放的端口,用于发送邮件
80 端口:http,用于网页浏览,木马 Executor 开放此端口
443 端口:基于 TLS/SSL 的网页浏览端口,能提供加密和通过安全端口传输的另一种 HTTP
3306 端口:MySQL 开放此端口
### 3.osi 网络七层模型
应用层(应用层,表示层,会话层)
封装数据
依据不同的协议,封装对应格式的数据消息
HTTP [超文本传输协议]
HTTPS[加密传输超文本传输协议]
FTP [文件传输协议]
SMTP [电子邮件传输的协议]
传输层:
封装端口
指定传输的协议(TCP协议/UDP协议)
网络层:
封装ip
版本ipv4 / ipv6
数据链路层:
封装mac地址
指定链路层的协议(arp协议(ip->mac)/rarp协议(mac->ip))
物理层:
打成数据包,变成二进制字节流,通过网络进行传输.
### 4.交换机与路由器
#交换机:对同一网段的不同机器之间进行数据转发的设备 [每一台机器和交换机相连,形成通信]
交换机从下到上拆2层,拆物理层和数据链路层,可以找到mac
#路由器:对不同网段的不同机器之间进行数据转发的设备 [每一个局域网和路由器相连,形成通信]
路由器从下到上拆3层,拆物理层和数据链路层和网络层,可以找到ip
arp协议 : 通过ip -> mac (arp地址解析协议)
“”“通过交换机的一次广播和一次单播找到对应的mac”""
电脑a先发送一个arp的广播包,把mac标记一个全FF-FF-FF-FF-FF-FF的广播地址
交换机接收到arp的广播包,从下到上进行拆包,拆2层,到数据链路层得到mac
发现mac是全F的广播地址,重新打包,交换机开始广播,所有连接在交换的设备都会收到arp广播包
各个主机开始拆包,对应FF广播地址可以跳过,继续向上找,发现ip不符合,直接舍弃
路由器允许从下到上拆三层,拆到ip,得到对应的网段
打开路由器的对照表 网关->网段,重新打包,找到对应的接口发送数据包
对应的交换机得到数据包,重新从下到上拆包,2层,发现全F,开始广播
数据库的主机接收到广播包,从下到上拆包,ip正确,符合自己的条件
数据库主机会把ip->mac的映射关系的数据发送回当前的交换机,
此时,交换机通过单播,把对应的ip和mac发送回原来请求的主机
数据在通过路由器,交换机发送回去
原主机在得到了ip 和 mac 之后,
重新把真实数据进行打包,从而完成发送
如果没有mac,主机一开始会发送arp请求包(发出找mac的请求)
各大主机在接受arp请求包的时候,都会去对照自己本机当中的arp解析表(ip->mac)
如果没有,这个arp得请求包舍弃,
如果有,会把自己的ip和mac封装在arp的响应包当中给交换机进行单播
在回来的过程中,所有相应的主机都会拿响应包中的数据更新自己的arp解析表,方便下次使用.
arp协议: 通过ip -> mac
###1. TCP/UDP协议
三次握手
SYN 创建连接
ACK 确认响应
FIN 断开连接
三次握手
客户端发送一个请求,与服务端建立连接
服务端接受这个请求,并且响应与客户端建立连接的请求
(服务端的响应和请求是在一次发送当中完成的)
客户端接受服务端的请求之后,把消息在响应给服务端
接下来客户端和服务端可以发送数据了.
每发送一个数据出去,对应的主机都会有一个回执消息,确认数据的接受情况,
如果没有得到回执消息,该数据会重发一次,保证数据的完整.
不会一直不停的发下去,有时间最大允许周期.
四次挥手
客户端向服务端发送一个请求消息,断开连接(代表客户端没有数据传输了)
服务端接受请求 , 发出响应
等到服务端所有数据收发完毕之后
服务端向客户端发送断开连接的请求
客户端接受请求 , 发出响应
等到2msl,最大报文生存时间之后
客户端与服务端彻底断开连接
2.socket
socket的意义:通络通信过程中,信息拼接的工具(中文:套接字)
开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉)
在bind方法之前加上这句话,可以让一个端口重复使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
3.tcp基本语法
复制代码
####客户端
import socket
#1.创建一个socket对象
sk = socket.socket()
#2.创建连接
sk.connect((‘127.0.0.1’,9000))
#3.发送数据(二进制字节流)
sk.send(“今天天气真好”.encode(‘utf-8’))
#4.关闭连接
sk.close()
复制代码
复制代码
####服务端
‘’’
客户端和服务端在收发数据时
一发一收是一对,否则会导致数据异常
send 发送 recv接收
‘’’
import socket
#1.创建socket对象
sk = socket.socket()
#2.绑定对应的ip和端口(注册网络,让其他主机能够找到)
‘’‘127.0.0.1 代表本地ip’’’
sk.bind((‘127.0.0.1’,9000))
#3.开启监听
sk.listen()
#4.创建三次握手
conn,addr = sk.accept()
print(conn,addr)
“”"
conn = <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1’, 9000), raddr=(‘127.0.0.1’, 61176)>
addr = (‘127.0.0.1’, 61176)
“”"
#5.收发数据(recv里面的参数单位是字节,代表一次最多接受多少数据)
res = conn.recv(1024)
print(res.decode(‘utf-8’))
#6.四次挥手
conn.close()
#7.退还端口
sk.close()
复制代码
4.tcp循环发消息
复制代码
####客户端
### tcp客户端
import socket
#1.创建socket对象
sk = socket.socket()
#2.创建连接
sk.connect((‘127.0.0.1’,9000))
#3.发送数据
‘’’
res = sk.recv(1024) #一次接收最大字节1024
print(res)
‘’’
while True:
strvar = input(‘请输入内容’)
sk.send(strvar.encode(‘utf-8’))
res = sk.recv(1024)
if res == b'q':
break
print(res.decode('utf-8'))
#4.关闭连接
sk.close()
复制代码
复制代码
####服务端
### 服务端
import socket
#1.创建socket对象
sk = socket.socket()
#2.注册主机绑定ip及端口
sk.bind((‘127.0.0.1’,9000))
#3.监听
sk.listen()
#4.三次握手
conn,addr = sk.accept()
#5.接发收数据
‘’’
数据类型:二进制的字节流
b修饰的字符串 => 代表的是二进制的字节流
里面的字符必须是ascii编码中的字符,不能是中文,否则报错
‘’’
while True:
conn,addr = sk.accept()
while True:
res = conn.recv(1024)
print(res.decode(‘utf-8’))
strvar = input('请输入内容')
conn.send(strvar.encode('utf-8'))
#退出
if strvar == 'q':
break
#6.四次挥手
conn.close()
#7.退还端口
sk.close()
复制代码
5.udp基本语法
复制代码
客户端
import socket
#type = socket.SOCK_DGRAM => 返回udp协议对象
#1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)
#2.发送数据
msg = ‘大妹纸,你好啊’
#sendto (二进制字节流,(ip,端口))
sk.sendto(msg.encode(‘utf-8’),(‘127.0.0.1’,9000))
#客户端接收服务端发过来的数据
msg,ser_addr = sk.recvfrom(1024)
print(msg.decode())
print(ser_addr)
#3.关闭连接
sk.close()
复制代码
复制代码
####服务端
import socket
#type = socket.SOCK_DGRAM => 返回udp协议对象
#1.创建对象
sk = socket.socket(type=socket.SOCK_DGRAM)
#2.绑定地址端口号
sk.bind((‘127.0.0.1’,9000))
#3.接收消息(udp作为服务端的时候,第一次一定是 接收消息)
msg,cli_addr = sk.recvfrom(1024)
print(msg.decode())
print(cli_addr) # (‘127.0.0.1’, 56184)
#服务端给客户端发消息
msg = ‘我是老爷们,我不好’
sk.sendto(msg.encode(),cli_addr)
#.关闭连接
sk.close()
复制代码
6.黏包
tcp协议在发送数据时,会出现黏包现象.
(1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,
缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。
(2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包
7.黏包出现的两种情况
复制代码
#黏包现象一:
在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包
#黏包现象二:
在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包
#总结:
发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包
核心是因为tcp对数据无边界截取,不会按照发送的顺序判断
复制代码
8.黏包对比:tcp和udp
复制代码
#tcp协议:
缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包
优点:不限制数据包的大小,稳定传输不丢包
#udp协议:
优点:接收时候数据之间有边界,传输速度快,不黏包
缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包
#tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包
复制代码
9.解决黏包的几种办法
复制代码
### 客户端
### 针对于tcp协议,会出现黏包现象
import socket
import time
#1.创建对象
sk = socket.socket()
#2.创建连接
sk.connect((‘127.0.0.1’,9000))
time.sleep(0.1)
#处理收发数据逻辑
‘’’
res1 = sk.recv(1024)
res2 = sk.recv(1024)
print(res1)
print(res2)
‘’’
#先接收数据的总大小
res = sk.recv(1)
num = int(res.decode(‘utf-8’))
#接收num这么多的字节数
res1 = sk.recv(num) #第一次
res2 = sk.recv(1024) #第二次
print(res1)
print(res2)
#4.关闭连接
sk.close()
复制代码
复制代码
####服务端
import socket
#1.创建对象
sk = socket.socket()
把这句话写在bind之前,让一个端口绑定多个程序,可以重复使用(仅用在测试环节)
#sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
#2.注册主机绑定ip和端口
sk.bind((‘127.0.0.1’,9000))
#3.监听
sk.listen()
#4.三次握手
conn,addr = sk.accept()
#在发送真实数据之前,提前告诉接收端数据的大小
conn.send(‘5’.encode())
#处理收发数据逻辑
conn.send(‘hello’.encode()) #第一次发送
#time.sleep(0.1)
conn.send(’,world’.encode()) #第二次发送
#6.四次挥手
conn.close()
#7.退还端口
sk.close()
复制代码
复制代码
####客户端
### 针对于tcp协议,会出现黏包现象
import socket
import time
#1.创建对象
sk = socket.socket()
#2.创建连接
sk.connect((‘127.0.0.1’,9000))
time.sleep(0.1)
处理收发数据逻辑
‘’’
黏包的现象## 标题
res1 = sk.recv(1024)
res2 = sk.recv(1024)
print(res1)
print(res2)
‘’’
#先接收数据的总大小
res = sk.recv(8)
num = int(res.decode(‘utf-8’))
#接收num这么多的字节数
res1 = sk.recv(num)
res2 = sk.recv(1024)
print(res1)
print(res2)
#关闭连接
sk.close()
复制代码
复制代码
### 服务端
import socket
#1.创建对象
sk = socket.socket()
#2.注册主机绑定ip和端口
sk.bind((‘127.0.0.1’,9000))
#3.监听
sk.listen
#4.三次握手
conn,addr = sk.accept()
#5.接发收数据处理逻辑
conn.send(‘00000100’.encode()) #8字节
#处理收发数据逻辑
msg = ‘hello’ * 20
conn.send(msg.encode())
#time.sleep(0.1)
conn.send(’,world’.encode())
#6.四次挥手
conn.close()
#7.退还端口
sk.close()
复制代码
复制代码
### 客户端 用struct解决
### 针对于tcp协议,会出现黏包现象
import socket
import time
import struct
#1.创建对象
sk = socket.socket()
#2.创建连接
sk.connect((‘127.0.0.1’,9000))
#处理收发数据逻辑
#接受第一次发送过来的数据(数据的大小)
n = sk.recv(4) #都会压缩成4个字节来接收
tup = struct.unpack(“i”,n) #unpack解包,'i’表示整型int,返回元组
print(tup) #(24,)
n = tup[0]
print(n)
#第二次接收的真实的数据
res1 = sk.recv(n)
print(res1.decode())
#第三次接收的真实的数据
res2 = sk.recv(1024)
print(res2.decode())
#关闭连接
sk.close()
复制代码
复制代码
### 服务端
import socket
import struct
#1.创建对象
sk = socket.socket()
#2.注册主机绑定ip和端口
sk.bind((‘127.0.0.1’,9000))
#3.监听
sk.listen()
#4三次握手
conn,addr = sk.accept()
#5.处理收发数据逻辑
strvar = input(‘请输入’)
msg = strvar.encode()
length = len(msg)
#第一次把长度先发送过去
res = struct.pack(‘i’,length)
conn.send(res)
#第二次在发送真实数据
conn.send(msg)
#第三次在发送一个数据
conn.send(’,world’.encode())
#6.四次挥手
conn.close()
#7.退还端口
sk.close()
复制代码
10.struct用法(解决黏包)
复制代码
import struct
‘’’
pack:
把任意长度的数字转化成具有4个字节的固定长度的字节流
unpack:
把4个字节值恢复成原本的数字,返回是元组
‘’’
#i => int 要转化的当前数据是整型
‘’‘pack的数值范围不是无限的,上限大概在21个亿左右,不要超过这个值’’’
res = struct.pack(‘i’,999999999)
res = struct.pack(“i”,1234343433)
res = struct.pack(“i”,2100000011)
print(res)
print(len(res))
#i => 把对应的数据转换成int,最后返回元组
tup = struct.unpack(‘i’,res)
print(tup[0]) ## (2100000011,)
‘’’
解决黏包场景:
应用场景在实时通讯时,需要阅读此次发的消息是什么
不需要黏包解决场景
下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓
‘’’## 标题
上一篇: python模块之psutil
下一篇: java设计模式之单例模式
推荐阅读
-
C#开发之Socket网络编程TCP/IP层次模型、端口及报文等探讨
-
python网络编程 使用UDP、TCP协议收发信息详解
-
Python3网络编程--socket编程之Tcp编程
-
Java网络编程实现HTTP协议(Socket API)
-
Java编程实现基于TCP协议的Socket聊天室示例
-
python网络编程——使用UDP、TCP协议收发信息
-
详解Android 基于TCP和UDP协议的Socket通信
-
python 网络通信协议/TCP,UDP区别
-
javaNIO实战-- java NIO的非阻塞式IO网络编程UDP协议实战
-
【Socket网络编程】-UDP辅助TCP实现点到点传输Java