网络编程小总结/socket套接字/TCP粘包问题
网络编程:使计算机之间基于网络实现数据交互
交换机:只要是接入了交换机的计算机 彼此之间都是互联的
局域网:互联网其实都是由N多个局域网连接而成
基于以太网协议通信的特点
- 通信基本靠吼
- 广播
- 单播
根据IP地址获取mac地址
OSI协议:(补充)
路由器:连接局域网
域名解析:www.baidu.com >>> 14.215.177.39:80
应用层
- HTTP协议:超文本传输协议
- FTP协议
UDP协议
- 数据报协议
- 无需建立双向通道 数据传输是不安全的
- 将内存中的数据直接发送出去 不会做保留
- 例如早期的qq
TCP协议类似于打电话
UDP协议类似于发短信
今日内容
socket套接字
解决端口占用问题
from socket import SOL_SOCKET,SO_REUSEADDR
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
服务端
import socket # 买手机 server = socket.socket() # 不传参数时,默认用的就是TCP协议 # 插电话卡 bind((host,port)) 绑定ip和port server.bind(('127.0.0.1',8080)) # 开机 半连接池 server.listen(5) # 接听电话 等着别人给你打电话 conn, addr = server.accept() # 阻塞,等待 # 听别人说话 接收1024个字节数据 data = conn.recv(1024) # 阻塞,等待 # 给别人回话 conn.send(b'hello baby~') # 挂电话 conn.close() # 关机 sercer.close()
客户端
import socket client = socket.socket() # 拿电话 client.connect(('127.0.0.1',8080)) # 拨号,写的是对方的ip和port client.send(b'hello world!') # 对别人说话 data = client.recv(1024) # 听别人说话,接收1024个字节数据 client.close() # 挂电话
总结:
127.0.0.1是本机回还地址,只能自己识别自己,不能访问其他
send和recv对应:
- 不要出现两边都是相同的情况
- recv是跟内存要数据,至于数据的来源,我们无需考虑
关于半连接池
半连接池的概念
图解:假如上述一共7个对象,首位已在被执行,有5个等待位对应5个对象,还有1个对象在等待进入等待位,server.listen(5)中的5就是5个等待位
通信循环下的服务端
import socket server = socket.socket() # 生成一个对象 server.bind(('127.0.0.1',8080)) # 绑定ip和port server.listen(5) # 半连接池,最大等待数目 while True: # 使服务端24小时不停的提供服务 conn, addr = server.accept() # 等到别人来 conn就是类似于双向通道 print(addr) # ('127.0.0.1',51323) 客户端的地址 while True: try: data = conn.recv(1024) print(data) # b""是二进制数据,针对mac与linux客户端异常退出之后,服务端不会报错,只会一直接收b"" if len(data) == 0: break conn.send(data.upper()) except ConnectionResetError as e: printi(e) break conn.close()
通信循环下的客户端
import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0: continue client.send(msg) data = client.recv(1024) print(data)
TCP粘包问题
TCP特点
- 会将数据量比较小的一次性发给对方
如何将数据打包成固定的数据包(运用struct模块)?
import struct res = 'ssssssssssssssssssssssss' # 当原始数据特别大时,i模式打包不了,需要更换其他模式 res1 = struct.pack('i',len(res)) print(len(res1)) # 打包后的数据长度为4 res2 = struct.unpack('i',res1)[0] print(res2) # 解包之后的真实数据长度
如果遇到数据量特别大时,又该如何解决?
import struct # 当原始数据特别大的时候 i模式打包不了 需要更换模式? # 如果遇到数据量特别大的情况 该如何解决? d = { 'name':'jason', 'file_size':34554354353453545245345324565465465654, 'info':'为大家的骄傲' } import json json_d = json.dumps(d) print(len(json_d)) res1 = struct.pack('i',len(json_d)) print(len(res1)) res2 = struct.unpack('i',res1)[0] print('解包之后的',res2)
关于subprocess的应用
import subprocess cmd = input('cmd>>>:') obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) print(obj.stdout.read().decode('gbk')) # 正确命令返回的结果 print(obj.stderr.read().decode('gbk')) # 错误的命令返回的结果 # subprocess获取到的数据 拿完就没有了 不能重复的拿 print(obj.stdout.read().decode('gbk')) # 正确命令返回的结果 print(obj.stderr.read().decode('gbk')) # 错误的命令返回的结果
解决粘包问题的服务端
import socket import subprocess import struct import json server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() while True: try: cmd = conn.recv(1024) if len(cmd) == 0:break cmd = cmd.decode('uft-8') obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) res = obj.stdout.read() + obj.stderr.read() json_d = json.dumps(d) # 先制作一个字典的报头 header = struct.pack('i',len(json_d)) # 发送字典报头 conn.send(header) # 发送字典 conn.send(json_d.encode('utf-8')) # 再发真实数据 conn.send(res) except ConnectionResetError: break conn.close()
解决粘包问题的客户端
import socket import struct import json client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0: continue client.send(msg) # 先接收字典报头 header_dict = client.recv(4) # 解析报头 获取字典的长度 dict_size = struct.unpack('i',header_dict)[0] # 解包时一定要加上索引0 # 接收字典数据 dict_bytes = client.recv(dict_size) dict_json = json.loads(dict_bytes.decode('utf-8')) # 从字典中获取真实数据 recv_size = 0 real_data = b'' while recv_size < dict_json.get('file_size'): data = client.recv(1024) real_data += data recv_size += len(data) print(real_data.decode('gbk'))
解决粘包问题
服务端
- 1.先制作一个发送给客户端的字典
- 2.制作字典的报头
- 3.发送字典的报头
- 4.发送字典
- 5.再发真实数据
客户端
- 1.先接受字典的报头
- 2.解析拿到字典的数据长度
- 3.接受字典
- 4.从字典中获取真实数据的长度
- 5.接受真实数据
案例实现:写一个上传电影的功能
- 1.循环打印某一个文件夹下面的所有的文件
- 2.用户选取想要上传的文件
- 3.将用户选择的文件上传到服务端
- 4.服务端保存该文件