c++ socket send file_网络概念、开发构架、socket模块、粘包、tcp协议的文件传输、socketsever模块...

网络的概念

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地址,从而到达两台机器相互通信的目的。

a14ffdb253ffde62629f0eb11dacaa00.png

交换机内部有缓存机制,不必每次传输数据都要通过广播获取另一台机器的mac地址,只有两台机器通讯一次,交换机内部就会缓存两机器的mac地址,此后交换机可通过两机器的mac地址通讯。

局域网之间:网关,路由器(可以识别ip地址)

网段:192.168.13.0,最多有254台机器(还有一台ip是网关)

子网掩码(也是一个ip地址):用来判断两台机器是否在一个局域网内。通过子网掩码和机器的ip地址的二进制按位与,得出结果若相等则在一个局域网中。

0f1d8219f3f1befb0bbe0dd498a0cc54.png

子网掩码: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,确认机器上的具体应用程序的。自己的机器取获取其他机器的数据,端口是由自己机器随机产生的。

网络开发架构

  1. C/S架构(应用程序):server端(服务端),client端(客户端)。可以离线使用,功能更完善,安全性更高。

  2. 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,网卡,二层交换机),物理层

a7221ed581647c8f16525c46e4efd8d3.png

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协议中,常见原因有以下几种:

  1. tcp协议多条消息之间没有边界(是流式传输),并且有优化的算法,

  2. 发生端:多条消息很短,发送时间也很短。

  3. 接收端:多条消息没有被及时接受而被缓存在一起。

  4. tcp协议多条消息之间没有边界:

  5. 网络最大宽带限制(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()

验证客户端的合法性

一般情况下客户登录后就可以不用验证客户端的合法性。

81ac4ee9b39d98557ab0491dae99bb59.png

 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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值