网络的概念
ip地址
ipv4协议,所有ip地址必须由四位点分十进制组成(192.168.12.12)表示范围:0.0.0.0-255.255.255.255,一位十进制数是由8位二进制表示的。ip地址是有限的,是变化的,分为公网地址和内网地址。
公网ip地址:需要购买,别人可以访问。
内网ip地址:别人无法访问。保留字段:192.168.0.0-192.168.255.255、172.16.0.0-172.31.255.255、10.0.0.0-10.255.255.255,之间的全是内网地址。
特殊的ip地址:127.0.0.1 本地回环地址,总是自己找自己,过网卡而不过交换机。做测试的时候用。
Windows查看ip地址,cmd:ipconfig
,mac:ifconfig
mac地址
出厂时设置,唯一的标识,每一台机器网卡的mac地址永远不会改变。
局域网
连到同一个交换机上的多台机器形成的一个局域网络。机器间的信息传递都是由交换机代做传递的。交换机只识别mac地址。
arp协议:地址解析协议,在以局域网内,一台机器通过交换机给另一台机器发生数据,一开始给交换机传递自己的mac地址和另一台机器的ip地址,交换机不识别ip地址,便通过广播将此数据传递给所有的机器,但只有id地址是与广播传的ip是相同的机器才会接受此数据,另一台机器接受数据后会给交换机发生mac地址,从而到达两台机器相互通信的目的。
交换机内部有缓存机制,不必每次传输数据都要通过广播获取另一台机器的mac地址,只有两台机器通讯一次,交换机内部就会缓存两机器的mac地址,此后交换机可通过两机器的mac地址通讯。
局域网之间:网关,路由器(可以识别ip地址)
网段:192.168.13.0,最多有254台机器(还有一台ip是网关)
子网掩码(也是一个ip地址):用来判断两台机器是否在一个局域网内。通过子网掩码和机器的ip地址的二进制按位与,得出结果若相等则在一个局域网中。
子网掩码:255.0.0.0,只有第一位网段相同,就在同一局域网;255.255.0.0,只要前两位网段相同,就在同一局域网,依次类推。
ipv6:范围:0:0:0:0:0:0-FFFFFF:FFFFFF:.....
冒分十六进制表示法 格式为X:X:X:X:X:X:X:X,其中每个X表示地址中的16b,以十六进制表示。
端口
0-65535,确认机器上的具体应用程序的。自己的机器取获取其他机器的数据,端口是由自己机器随机产生的。
网络开发架构
C/S架构(应用程序):server端(服务端),client端(客户端)。可以离线使用,功能更完善,安全性更高。
B/S架构(网址):server端(服务端),browser (浏览器)。只需安装一个浏览器即可使用。B/S架构是C/S架构的一种特殊的情况。
统一PC端用户入口:B/S架构。
socket模块
1#serve端文件夹
2import socket
3
4sk = socket.socket()
5sk.bind(('127.0.0.1',9000)) # 申请操作系统的资源,绑定ip、端口
6sk.listen() #监听,socket服务开启
7
8#sk: 9conn,address = sk.accept() #和客户端握手/建立连接10#conn(存储的是一个客户端和一个server端的连接信息)在sk的基础上增加了客户端的raddr=('127.0.0.1', 2043),2043:端口11# address:('127.0.0.1', 2040) 2040:端口121314conn.send(b'hello') #我与对方通信15mas = conn.recv(1024) #对方与我通信,1024:最多能够接受的字节1617print((mas))18conn.close() #4次挥手,结束与对方的通信192021sk.close() #关闭服务器。归还申请的操作系统的资源2223#client端文件夹24import socket2526sk = socket.socket()27sk.connect(('127.0.0.1',9000))2829mas = sk.recv(1024) #最多1024个字节30print(mas)31sk.send(b'byebye') 32sk.close()
7层协议
应用层,表示层,会话层,传输层(端口),网络层(ip),数据链路层(mac),物理层(电信号)
osi5层协议
应用层(应用层,表示层,会话层合并,为python代码),传输层(port, udp, tcp, 四层路由器,四层交换机),网络层(ipv4, ipv6, 路由器,三层交换器),数据链路层(mac, arp,网卡,二层交换机),物理层
tcp协议:和多个客户端的简单通信,有三次握手;需要先建立连接然后才能通信。优点:可靠,实时性高。
1#server端
2import socket
3sk = socket.socket()
4sk.bind(('127.0.0.1',9000))
5sk.listen()
6
7while 1:
8 conn,address = sk.accept()
9 msg = conn.recv(10240)
10 if msg:
11 print(msg.decode('utf-8'))
12 send_msg = input('>>>')
13 if send_msg:
14 conn.send(send_msg.encode('utf-8'))
15 else:conn.close()
16 else:conn.close()
17sk.close()
18
19
20#client端
21import socket
22sk = socket.socket()
23sk.connect(('127.0.0.1',9000))
24
25while 1:
26 send_msg = input('>>>')
27 if send_msg:
28 sk.send(send_msg.encode('utf-8'))
29 mas = sk.recv(10240)
30 if mas :
31 print(mas.decode('utf-8'))
32 else:
33 break
34 else:sk.send(b' ');break
35
36sk.close()
udp协议:简单通信,server不能先发送,不需要建立连接即可通信,
1#sever端
2import socket
3sk = socket.socket(type=2)
4#也可以是sk = socket.socket(type=SOCK_DGRAM),SOCK_DGRAM是udp协议,默认参数为type = SOCK_STREAM,tcp协议
5sk.bind(('127.0.0.1',9000))
6
7
8while 1:
9 msg,addr = sk.recvfrom(10240)# #recvfrom 可以获取接受的消息与对方的id与端口
10 print(msg.decode())
11 send_msg = input('>>>')
12 if send_msg:
13 sk.sendto(send_msg.encode('utf-8'),addr)
14 else:sk.sendto(b' ',addr)
15#sk.close() while无法退出,因此此此代码可以不写
16
17#client端
18sk = socket.socket(type=2)
19server = ('127.0.0.1',9000)
20
21while 1:
22 send_msg = input('>>>')
23 if send_msg:
24 sk.sendto(send_msg.encode('utf-8'),server)
25 msg = sk.recv(10200)
26 if msg:
27 print(msg.decode('utf-8'))
28 else:break
29 else:break
30
31sk.close()
粘包现象
只出现在tcp协议中,常见原因有以下几种:
tcp协议多条消息之间没有边界(是流式传输),并且有优化的算法,
发生端:多条消息很短,发送时间也很短。
接收端:多条消息没有被及时接受而被缓存在一起。
tcp协议多条消息之间没有边界:
网络最大宽带限制(udp):一般是1500,可以修改,但在传送数据时取最小的。tcp无网络最大宽带限制,tcp数据之间没有边界,会将数据分割成特定大小的数据,因此没有边界。
解决tcp粘包现象
1sever端
2import socket
3sk = socket.socket()
4sk.bind(('127.0.0.1',9000))
5sk.listen()
6
7conn,address = sk.accept()
8msg = input('>>>').encode('utf-8')
9num = str(len(msg)).zfill(4).encode('utf-8')#zfill默认以0从左至右填充
10conn.send(num)
11conn.send(msg)
12conn.close()
13sk.close()
14
15client端:
16import socket
17sk = socket.socket()
18sk.connect(('127.0.0.1',9000))
19length = int(sk.recv(4).decode('utf-8'))
20msg = sk.recv(length).decode('utf-8')
21print(msg)
22sk.close()
struct模块优化以上代码
1import struct
2num1 =-2**31
3num2 = 123
4num3 = 12
5print(len(struct.pack('i',num1)))#4 #i,可以将-2**31~2**31范围内的数字(共2**32个数)pack为4字节。
6print(len(struct.pack('i',num2)))#4
7
8ret = struct.pack('i',num3) #q,d,n:8,e:2,i,f,l:4,b:1
9print(struct.unpack('i',ret)) #(12,)
10
11
12server端:
13import struct
14import socket
15sk = socket.socket()
16sk.bind(('127.0.0.1',9000))
17sk.listen()
18
19conn,address = sk.accept()
20msg = input('>>>').encode('utf-8')
21num = struct.pack('i',len(msg))
22conn.send(num)
23conn.send(msg)
24conn.close()
25sk.close()
26
27client端:
28import socket
29import struct
30sk = socket.socket()
31sk.connect(('127.0.0.1',9000))
32length = struct.unpack('i',sk.recv(4))[0]
33msg = sk.recv(length).decode('utf-8')
34print(msg)
35sk.close()
阻塞
input()
:等待用户输入enter键后结束阻塞;accept()
:有客户端来建立连接之后结束阻塞,recv(),recvfrom()
,收到对方发过来的消息之后结束阻塞。connect()
:知道server端结束了对一个client服务,开始和当前client端建立连接时结束阻塞。
收发端打印不同的颜色和名字
1server端
2import socket
3sk = socket.socket(type=socket.SOCK_DGRAM)
4sk.bind(('127.0.0.1',9000))
5
6friend_lst = {'小白':32,'小黑':33}
7guest_lst = {}
8
9while 1:
10 msg,address = sk.recvfrom(1024)
11 name,msg = msg.decode('utf-8').split('|',1)
12 print(f'\033[{friend_lst.get(name,30)}m{name}:{msg}\033[0m')
13 send_msg = input('>>>')
14 sk.sendto(send_msg.encode('utf-8'),address)
15
16
17sk.close()
18
19
20client端
21import socket
22sk = socket.socket(type=socket.SOCK_DGRAM)
23addr = ('127.0.0.1', 9000)
24
25while 1:
26 name = input('>>>')
27 while 1:
28 send_msg = input('>>>')
29 if send_msg.upper()=='Q':break
30 send_msg = '%s|%s'%(name,send_msg)
31 sk.sendto(send_msg.encode('utf-8'),addr)
32 msg = sk.recv(1024)
33 print(msg.decode('utf-8'))
34 break
35sk.close()
基于tcp协议的文件传输
1server端
2import socket
3import json
4sk = socket.socket()
5sk.bind(('127.0.0.1',9000))
6sk.listen()
7conn,addr = sk.accept()
8msg = conn.recv(1024).decode('utf-8')
9# print(json.loads(msg))
10msg = json.loads(msg)
11print(msg['filesize'])
12with open(msg['filename'],'wb') as f:
13 content = conn.recv(msg['filesize'])
14 print(len(content))
15 f.write(content)
16conn.close()
17sk.close()
18
19
20client端
21import socket
22import os
23import json
24sk = socket.socket()
25sk.connect(('127.0.0.1',9000))
26abs_path = r'D:\WorkSpace\python3.7\12.txt'
27filename = os.path.basename(abs_path)
28filesize = os.path.getsize(abs_path)
29dic = {'filename':filename,'filesize':filesize}
30str_dic=json.dumps(dic).encode('utf-8')
31sk.send(str_dic)
32with open(abs_path,mode='rb') as f:
33 content = f.read()
34 sk.send(content)
35sk.close()
上方代码存在较多问题,如client端的两次发生可能会出现粘包现象;不能上传大文件等。recv(n),最多可以收到n个字节,而不是一定可以收到n个字节。
进行优化
1server端
2import socket
3import json
4import struct
5
6sk = socket.socket()
7sk.bind(('127.0.0.1',9000))
8sk.listen()
9conn,addr = sk.accept()
10
11msg_len = conn.recv(4)
12dic_len = struct.unpack('i',msg_len)[0]
13msg = conn.recv(dic_len).decode('utf-8')
14msg = json.loads(msg)
15print(msg['filesize'])
16with open(msg['filename'],'wb') as f:
17 while msg['filesize'] >0:
18 content = conn.recv(1024)
19 f.write(content)
20 msg['filesize']-=len(content)
21 #不能是减去1024,由于tcp协议会分割数据,并且网络发送速度第网络接收速度慢,会导致文件丢失
22conn.close()
23sk.close()
24
25client端
26import socket
27import os
28import json
29import struct
30sk = socket.socket()
31sk.connect(('127.0.0.1',9000))
32abs_path = r'D:\学习\深度学习\01.mp4'
33filename = os.path.basename(abs_path)
34filesize = os.path.getsize(abs_path)
35dic = {'filename':filename,'filesize':filesize}
36str_dic=json.dumps(dic).encode('utf-8')
37msg_len = struct.pack('i',len(str_dic))
38sk.send(msg_len) #四个字节,表示字典的长度
39sk.send(str_dic)
40with open(abs_path,mode='rb') as f:
41 while filesize>0:
42 content = f.read(1024)
43 filesize-=len(content) #或filesize-=1024
44 sk.send(content)
45sk.close()
验证客户端的合法性
一般情况下客户登录后就可以不用验证客户端的合法性。
1import os
2os.urandom(32) #随机生成32位字节。
3
4可以hashlib算法加密。
5密钥可以是‘盐’
6server端:
7import socket
8import hashlib
9import os
10secret_key = b'hahaha'
11sk = socket.socket()
12sk.bind(('127.0.0.1',9000))
13sk.listen()
14conn,addr = sk.accept()
15rand = os.urandom(32)
16conn.send(rand)
17sha = hashlib.sha1(secret_key)
18sha.update(rand)
19res = sha.hexdigest()
20res_client = conn.recv(1024).decode('utf-8')
21if res == res_client:
22 print('是合法的客户端')
23else:conn.close()
24client端:
25import socket
26import hashlib
27
28secret_key = b'hahaha'
29sk = socket.socket()
30sk.connect(('127.0.0.1',9000))
31rand = sk.recv(32)
32sha = hashlib.sha1(secret_key)
33sha.update(rand)
34res = sha.hexdigest()
35sk.send(res.encode('utf-8'))
用hmac模块优化以上代码
1hmac模块:
2import os
3import hmac
4h = hmac.new(b'hahaha',os.urandom(32))
5ret = h.digest()
6
7server端
8import socket
9import hmac
10import os
11secret_key = b'hahaha'
12sk = socket.socket()
13sk.bind(('127.0.0.1',9000))
14sk.listen()
15conn,addr = sk.accept()
16rand = os.urandom(32)
17conn.send(rand)
18h = hmac.new(secret_key,rand)
19res = h.digest()
20res_client = conn.recv(1024)
21if res == res_client:
22 print('是合法的客户端')
23else:conn.close()
24
25client端:
26import socket
27import hmac
28secret_key = b'hahaha'
29sk = socket.socket()
30sk.connect(('127.0.0.1',9000))
31rand = sk.recv(32)
32h = hmac.new(secret_key,rand)
33res = h.digest()
34sk.send(res)
socketserver模块
socketserver模块是基于sock模块完成的并发的server端,用于处理并发的客户端请求(tcp协议)。
基本代码:
1import socketserver
2import time
3
4class Myserver(socketserver.BaseRequestHandler): #类名随便起
5 def handle(self) -> None:
6 #客户端连接后,程序从下方开始执行
7 conn = self.request
8 while 1:
9 content = conn.recv(1024).decode('utf-8')
10 conn.send(content.upper().encode('utf-8'))
11 time.sleep(0.5)
12
13#获取一个server对象
14server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),Myserver)
15server.serve_forever()
16
17client端:
18import socket
19sk = socket.socket()
20sk.connect(('127.0.0.1',9000))
21while 1:
22 sk.send(b'hello')
23 content = sk.recv(1024).decode('utf-8')
1打印进度条
2import time
3for i in range(0,101,2):
4 time.sleep(0.1)
5 char_num = i//2
6 if i == 100:
7 per_str = '\r%s%% : %s\n' % (i, '*' * char_num) #\r将光标移到行首,但是不换行。覆盖第一次打印的结果
8 else:
9 per_str = f"\r{i}% : {'*'*char_num}"
10 print(per_str,end='', flush=True)
11
12进阶
13import sys
14import time
15def processBar(num, total):
16 rate = num / total
17 rate_num = int(rate * 100)
18 if rate_num == 100:
19 r = '\r%s>%d%%\n' % ('=' * rate_num, rate_num,)
20 else:
21 r = '\r%s>%d%%' % ('=' * rate_num, rate_num,)
22 sys.stdout.write(r)
23processBar(12,1024)
24time.sleep(0.5)
25processBar(24,1024)