计算机网络基础

计算机网络基础

一、网络开发两大架构

早期数据交互原理——>socket(套接字)的模型

socket(套接字)是收发数据的一个工具

a文件与b文件之间的交流是通过c文件实现的
a文件把数据放到c文件中,b文件从c文件中取
b文件把数据放到c文件中,a文件从c文件中取

出现网络后:

a文件中的数据,可以通过网络协议,转化成像101010这样的电信号,进行发送
a文件借助socket发送数据
b文件结束socket接收数据

1、C/S 架构

c => client 客户端
	是具体的一个软件,像QQ,微信这种
s => server 服务端
	天河三号 百亿亿次

2、B/S 架构

b => browser 浏览器
	通过输入网址,访问对方的服务器,对方服务器响应请求之后,吧对应的数据给你返回,就可以你的浏览器上看到想要的内容
s => server 服务端

3、未来的发展方向

相对于C/S架构,B/S架构更好,也是未来的发展方向
(1)省去复杂漫长的下载安装环节,节省收集或者电脑的硬盘空间
(2)利用手机的便捷性,随时随地可以访问到网站和相应的服务,提升效率,加快速度

二、网络的概念

1、IP地址

IP地址(Internet Protocol Address)是指互联网协议地址,又译为网际协议地址。

查看IP的两种方式:
	第一种:通过电脑中的网络和Internet设置查看本机网络信息
	第二种:通过cmd中执行ipconfig命令查看本机网络信息
ip地址的最后一位0或255,不能使用;
255(1111)是广播地址,0(0000)是网络地址(网段)

2、MAC地址

mac地址即物理地址是唯一的,它是一个用来确认网络设备位置的位址。

3、网段

网段(network segment)一般指一个计算机网络中使用同一物理层设备(传输介质,中继器,集线器等)能够直接通讯的那一部分。

判别依据:如果IP地址和子网掩码想与,得到的值相同就是同一网段
子网掩码:区分网段和主机和一串IP

案例一:
ip 192.168.31.43
11000000 10101000 00011111 00101011
子网掩码 255.255.255.0
11111111 11111111 11111111 00000000
IP & 子网掩码
11000000 10101000 00011111 00000000
ip1的网段:192.168.31.0

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.30.44
11000000 10101000 00011110 00101100
子网掩码 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)

4、端口

某个程序与外界通讯的出口

通过ip + 端口 可以找到世界上任何一台电脑的任何一个软件
端口的范围:0~65535  
形式:192.168.2.1:8000
自定义端口号时,起8000以上
前1024个端口被系统占用了

20  端口:FTP 文件传输协议(默认数据口)
21  端口:FTP 文件传输协议(控制)
22  端口:SSH 远程登录协议
23  端口:telent(终端仿真协议),木马Tiny Telnet Server
25  端口:SMTP 服务器所开放的端口,用于发送邮件
80  端口:http,用于网页浏览,木马Executor开放此端口
443 端口:基于 TLS/SSL 的网页浏览端口,能提供加密和通过安全端口传输的另一种HTTP
3306端口:Mysql 开放此端口

三、OSI 网络七层模型

应用层(应用层,表示层,会话层):
	封装数据
		依据不同的协议,封装对应格式的数据消息
		HTTP【超文本传输协议】
		HTTPS【加密传输超文本协议】
		FTP【文件传输协议】
		SMTP【电子邮件传输协议】
传输层:
	封装端口
		指定传输的协议(TCP协议/UDP协议)
网络层:
	封装IP
		版本IPV4或IPV6
数据链路层:
	封装mac地址
		指定链路层的协议(arp协议(ip=>mac)/rarp协议(mac=>ip))
物理层:
	打成数据包,变成二进制字节流,,通过网络进行传输

四、交换机与路由器

交换机:对同一网段的不同机器之间进行数据转发的设备(每一台机器和交换机相连,形成通信)
	交换机解析的时候从下到上拆两层,拆物理层和数据,可以找到mac地址
路由器:对不同网段的不同机器之间进行数据转发的设备(每一个局域网和路由器相连,形成通信)
	路由器解析的时候从下到上拆三层,拆物理层、数据链路层和网络层,可以找到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

五、TCP/UDP协议

三次握手

SYN 创建连接
ACK 确认响应
FIN 断开连接

三次握手
	客户端发送一个请求,与服务端建立连接
	服务端接受这个请求,并且响应与客户端建立连接的请求
	(服务端的响应和请求是在一次发送当中完成的)
	客户端接受服务端的请求之后,把消息在响应给服务端
	
	接下来客户端和服务端可以发送数据了.
	每发送一个数据出去,对应的主机都会有一个回执消息,确认数据的接受情况,
	如果没有得到回执消息,该数据会重发一次,保证数据的完整.
	不会一直不停的发下去,有时间最大允许周期.
	
TCP(Transmission Control Protocol)一种面向链接的、可靠的、传输层通信协议(比如:打电话)
优点:可靠,稳定,传输完整稳定,不限制数据大小
缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认
应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景

UDP(User Datagram Protocol)一种面向无连接的、不可靠的传输层通信协议(比如:发短信)
优点:速度快,可以多人同时聊天,耗费资源少,不许要建立链接
缺点:不稳定,不能保证每次数据都能接收到
应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景

客户端和服务端建立链接时:三次握手
客户端和服务端断开链接时:四次挥手
	四次挥手MSL为最大报文段生存时间,默认规定MSL为2分钟,但实际应用中常用的是30秒,1分钟和2分钟等

六、socket

socket的意义:网络通信过程中,信息拼接的工具(中文:套接字)

在开发中,一个端口只对应一个程序生效,在测试时,允许端口重复捆绑(开发时删掉)

在bind方法之前加上这句话,可以让一个端口重复使用

sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

实现TCP发消息

# 服务端
import socket
# 1 创建socket对象
sk = socket.socket()
# 2 绑定对应的ip和端口(注册网络,让其他主机能够找到)
sk.bind(('127.0.0.1', 8000))
# 3 开启监听
sk.listen()
# 4 建立三次握手
conn, addr = sk.accept()
print(conn, addr)
# 5 收发数据(recv里面的参数单位是字节,代表一次最多接收多少数据)
ret = conn.recv(1024)
# print(ret)
print(ret.decode("utf-8"))
# 6 四次挥手
conn.close()
# 7 退还端口
sk.close()


# 客户端
import socket
# 1 创建一个socket对象
sk = socket.socket()
# 2 与服务器进行连接
sk.connect(('127.0.0.1', 8000))
# 3 发送数据(二进制字节流)
sk.send("今天风和日丽".encode("utf-8"))
# 4 关闭连接
sk.close()

实现TCP循环发消息:

在收发数据的时候不使用encode编码是不能传输中文的

因为传输的数据类型是二进制字节流

要使用b修饰字符串,代表是二进制字节流

注:里面的字符串必须是ascii编码中的字符,不能是中文,否则报错

# 客户端
import socket
sk = socket.socket()
# 让端口重复绑定多个程序(仅仅在测试阶段使用)
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(('127.0.0.1', 8000))
sk.listen()
conn, addr = sk.accept()
while True:
    ret = conn.recv(1024)
    print(ret.decode())
    msg = input("服务端请输入:")
    conn.send(msg.encode())
conn.close()
sk.close()

# 服务端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8000))
while True:
    msg = input("客户端请输入:")
    sk.send(msg.encode())
    ret = sk.recv(1024)
    print(ret.decode())
sk.close()

实现UDP发送消息:

# 服务端
import socket
# 1 创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)
# 2 绑定地址端口
sk.bind(('127.0.0.1', 8000))
# 3 接收消息(udp作为服务端的时候,第一次一定是接收消息)
msg, cli_addr = sk.recvfrom(1024)
print(msg.decode())
print(cli_addr)
# 服务端给客户端发消息
msg = "你好,boy"
sk.sendto(msg.encode(), cli_addr)
# 4 关闭连接
sk.close()

# 客户端
import socket
# 1 创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)
# 2 发送数据
msg = "你好,girl"
sk.sendto(msg.encode(), ("127.0.0.1", 8000))
# 客户端接受服务端发过来的数据
msg, ser_addr = sk.recvfrom(1024)
print(msg.decode())
print(ser_addr)
# 3 关闭文件
sk.close()

实现UDP循环发送消息:

# 服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 8000))
while True:
    ret, cli_addr = sk.recvfrom(1024)
    print(ret.decode())
    msg = input("服务端请输入:")
    sk.sendto(msg.encode(), cli_addr)

sk.close()

# 客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
while True:
    msg = input("客户端输入:")
    sk.sendto(msg.encode(), ("127.0.0.1", 8000))
    ret, ser_addr = sk.recvfrom(1024)
    print(ret.decode())
sk.close()

黏包问题

tcp协议在发送数据时,会出现黏包现象

(1)数据黏包是因为在客户端/服务器端都会有一个数据缓冲区,缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。

(2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包。

黏包出现的两种情况

(1)黏包现象一:
	在发送端,由于两个数据短,发送的时间间隔短,所以在发送端形成黏包
	
(2)粘包现象二:
	在发送端,由于两个数据几乎同时被发送到对方的缓存中,所以在接收端形成了黏包

粘包对比:TCP和UDP

tcp协议
缺点:接收数据之间无边界,有可能粘合几条数据成一条数据,造成粘包
优点:不限制数据包的大小,稳定传输不丢包

udp协议:
优点:接收时侯数据之间有边界,传输速度快,不粘包
缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应相应完毕为止
而udp一旦发送失败,是不会询问对方是否有相应的,数据量过大就会容易丢包

解决黏包问题

解决黏包场景:
	应用场景在实时通讯时,需要阅读此次发送的消息是什么
不需要解决粘包场景:
	下载或者上传文件的时候,最后要把包都结合在一起,粘包无所谓
# 普通解决粘包方式第一种是通过time.slee()阻塞
# 普通解决粘包方式第二种是先把长度发送过去在进行接收
# 服务端
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(('127.0.0.1', 8000))
sk.listen()
conn, addr = sk.accept()
conn.send('00000100'.encode())
msg = "hello" * 20
conn.send(msg.encode())
conn.send(",word".encode())
conn.close()
sk.close()

# 客户端
import socket
import time
sk = socket.socket()
sk.connect(('127.0.0.1', 8000))

ret = sk.recv(8)
num = int(ret.decode("utf-8"))
ret1 = sk.recv(num)
# time.sleep(0.1)
ret2 = sk.recv(1024)
print(ret1)
print(ret2)
sk.close()

struct模块

pack:把任意长度的数字转化成居右4个字节的固定长度的字节流
unpack:把四个字节值恢复成原本的数字,返回元组
参数i:int的简写,要转化的当前数据时整型
注:pack的数值范围不是无限的,上限大概在21个亿左右,不要超过这个值
# 服务端
import socket
import struct
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(('127.0.0.1', 8000))
sk.listen()
conn, addr = sk.accept()
strvar = input("服务端请输入:")
msg = strvar.encode()
length = len(msg)

res = struct.pack('i', length)
conn.send(res)
conn.send(msg)

conn.send(",world".encode())

conn.close()
sk.close()

# 客户端
import socket
import time
import struct

sk = socket.socket()
sk.connect(('127.0.0.1', 8000))
n = sk.recv(4)
tup = struct.unpack('i', n)
n = tup[0]
print(n)
res1 = sk.recv(n)
print(res1.decode())
res2 = sk.recv(1024)
print(res2.decode())
sk.close()

七、socketserver模块

基于原有的socket模块进行封装后的模块socketserver

网络协议的最底层就是socket

socketserver的作用:实现TCP协议,server端的并发

socket参数的详解:

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
family参数:地址系列默认为AF_INET,AF_INET代表IPV4;AF_INET6代表IPV6
		   AF_UNIX,AF_CAN或AF_RDS.(AF_UNIX 域实际上是使用本地 socket [文件]来通信)同一机器
type参数:套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。 
         **SOCK_STREAM** 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。
         **SOCK_DGRAM** 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。
proto参数:协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。
fileno参数:如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。 
           与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。 这可能有助于使用socket.close()关闭一个独立的插座。

实例:

# 服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request
        while True:
            ret = conn.recv(1024)
            print(ret.decode())
            msg = input("服务端请输入:")
            conn.send(msg.encode())
server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), MyServer)
server.serve_forever()

# 客户端
import socket
sk = socket.socket()
sk.connect(("127.0.0.1", 8000))
while True:
    msg = input("客户端请输入:")
    sk.send(msg.encode())
    ret = sk.recv(1024)
    print(ret.decode())
sk.close()

八、hashlib模块和hmac模块

hashlib模块:是一堆加密算法的集合,哈希算法的加密方式不止一种

https://www.cmd5.com/ md5解密

应用场景:在需要校验功能时使用

​ 用户密码的 => 加密,解密

​ 相关校验的 => 加密,解密

哈希算法也叫摘要算法,相同的数据始终得到相同的输出结果,不同的数据得到不同的输出结果

(1)哈希将不可变的任意长度的数据,变成具有固定长度的唯一值

(2)字典的键和集合的值是通过哈希计算存储的,存储的数据是散列(无序)

1、加密

import hashlib
import random
hs = hashlib.md5()

# hashlib基本用法
hs.update("12346".encode("utf-8"))
ret = hs.hexdigest()
print(ret, len(ret))

# 加盐
hs = hashlib.md5("pddf".encode())
ret = hs.hexdigest()
print(ret, len(ret))

# 动态加盐
ret = str(random.randrange(1000,100000))
print(ret)
hs = hashlib.md5(ret.encode("utf-8"))
hs.update("123456".encode())
ret = hs.hexdigest()
print(ret)

# sha算法
hs = hashlib.sha1()
hs.update('123456'.encode())
ret = hs.hexdigest()
print(ret, len(ret))

# hmac加密
import hmac
key = b"asd"
msg = b"1232456"
hm = hmac.new(key, msg)
ret = hm.hexdigest()
print(ret, len(ret))

# hmac动态加盐
import os
key = os.urandom(32)
msg = b'123456'
hm = hmac.new(key, msg)
ret = hm.hexdigest()
print(ret, "<1>", len(ret))

2、文件的验证

import hashlib
hm = hashlib.md5()
hm.update('123'.encode())
ret = hm.hexdigest()
print(ret, len(ret))

# 文件的验证:小文件的验证
def check_md5(file):
    with open(file, mode="rb") as fp:
        hs = hashlib.md5()
        hs.update(fp.read())
    return hs.hexdigest()
ret1 = check_md5("ceshi1.txt")
ret2 = check_md5("ceshi2.txt")
print(ret1, ret2)

# 文件的验证:大文件的验证
hs = hashlib.md5()
hs.update("今天是星期一".encode())
ret = hs.hexdigest()
print(ret)

hs = hashlib.md5()
hs.update("今天是".encode())
hs.update("星期一".encode())
ret = hs.hexdigest()
print(ret)

# 验证方式一:通过文件截取的方式验证
def check_md5(file):
    hs = hashlib.md5()
    with open(file, mode = "rb") as fp:
        while True:
            content = fp.read(5)
            if content:
                hs.update(content)
            else:
                break
        return hs.hexdigest()
print(check_md5('ceshi1.txt'))
print(check_md5('ceshi2.txt'))

# 验证方式二:通过计算文件大小的方式验证
def check_md5(file):
    hs = hashlib.md5()
    num = os.path.getsize(file)
    with open(file, mode="rb") as fp:
        while num:
            content = fp.read(5)
            hs.update(content)
            num -= len(content)
        return hs.hexdigest()

4、服务器的合法性验证

# 服务端1(公司)
import socket
import hmac
import hashlib
def auth(sk,secret_key):
	# 处理权限验证的逻辑
	msg = sk.recv(32)
	hm = hmac.new(secret_key.encode(),msg)
	"""
	# 在不知道加密算法的时候,只知道密钥也没用;
	hs = hashlib.md5(secret_key.encode())
	hs.update(msg)
	res = hs.hexdigest()
	"""
	res = hm.hexdigest()
	print(res)
	sk.send(res.encode("utf-8"))

secret_key = "芝麻开门"
sk = socket.socket()
sk.connect(  ("127.0.0.1" , 9000) )

# 验证服务端
auth(sk,secret_key)

# 发送数据
sk.send("请求调用支付宝刷脸支付接口".encode("utf-8"))

# 服务端2(支付宝)
import socket
import hmac
import os

def auth(conn,secret_key):
	# 随机产生32位二进制字节流
	msg = os.urandom(32)
	conn.send(msg)
	hm = hmac.new(secret_key.encode(),msg)
	res_serve = hm.hexdigest()
	print(res_serve)
	res_client = conn.recv(1024).decode("utf-8")

	if res_client == res_serve:
		print("是合法的连接用户")
		return True
	else:
		print("是不合法的连接用户")
		return False
原文地址:https://www.cnblogs.com/wylshkjj/p/13046914.html