1 基础
1.1 作业题1
# 1. 简述 二层交换机 & 路由器 & 三层交换机 的作用。
"""
二层交换机:构建局域网并实现局域网内数据的转发。
路由器:实现跨局域网进行通信。
三层交换机:具备二层交换机和路由的功能。
"""
# 2. 简述常见词:IP、子网掩码、DHCP、公网IP、端口、域名的作用。
"""
IP,本质上是一个32位的二进制,通过 . 等分为了4组8位二进制。
子网掩码,用于指定IP的网络地址和主机地址。
DHCP,网络设备中的一个服务,用于给接入当前网络的电脑自动设置 IP、子网掩码、网关。(动态分配IP、子网掩码、网关)
公网IP,一般企业拉专线时会给固定的公网IP,只有具备公网IP才可以被互联网上的其他电脑访问。
端口,IP用于表示某台电脑,端口则表示此电脑上的具体程序。0-65535
域名,与IP构造对应关系,方便用户记忆。
"""
2 TCP和UDP
2.1 基础
# IP可复用
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 非阻塞
server.setblocking(False)
# TCP和UDP
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2.1.1 TCP实例
-
server
import os import json import socket import struct def recv_data(conn, chunk_size=1024): # 获取头部信息:数据长度 has_data_len = 0 bytes_data = b'' while has_data_len < 4: chunk = conn.recv(4 - has_data_len) has_data_len += len(chunk) bytes_data += chunk data_length = struct.unpack('i', bytes_data)[0] # 获取数据 data_list = [] has_read_data_size = 0 while has_read_data_size < data_length: size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size chunk = conn.recv(size) data_list.append(chunk) has_read_data_size += len(chunk) data = b"".join(data_list) return data def recv_file(conn, save_file_name, chunk_size=1024): save_file_path = os.path.join('files', save_file_name) # 获取头部信息:数据长度 has_read_size = 0 bytes_list = [] while has_read_size < 4: chunk = conn.recv(4 - has_read_size) bytes_list.append(chunk) has_read_size += len(chunk) header = b"".join(bytes_list) data_length = struct.unpack('i', header)[0] # 获取数据 file_object = open(save_file_path, mode='wb') has_read_data_size = 0 while has_read_data_size < data_length: size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size chunk = conn.recv(size) file_object.write(chunk) file_object.flush() has_read_data_size += len(chunk) file_object.close() def run(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IP可复用 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 8001)) sock.listen(5) while True: conn, addr = sock.accept() while True: # 获取消息类型 message_type = recv_data(conn).decode('utf-8') if message_type == 'close': # 四次挥手,空内容。 print("关闭连接") break # 文件:{'msg_type':'file', 'file_name':"xxxx.xx" } # 消息:{'msg_type':'msg'} message_type_info = json.loads(message_type) if message_type_info['msg_type'] == 'msg': data = recv_data(conn) print("接收到消息:", data.decode('utf-8')) else: file_name = message_type_info['file_name'] print("接收到文件,要保存到:", file_name) recv_file(conn, file_name) conn.close() sock.close() if __name__ == '__main__': run()
-
client
import os import json import socket import struct def send_data(conn, content): data = content.encode('utf-8') header = struct.pack('i', len(data)) conn.sendall(header) conn.sendall(data) def send_file(conn, file_path): file_size = os.stat(file_path).st_size header = struct.pack('i', file_size) conn.sendall(header) has_send_size = 0 file_object = open(file_path, mode='rb') while has_send_size < file_size: chunk = file_object.read(2048) conn.sendall(chunk) has_send_size += len(chunk) file_object.close() def run(): client = socket.socket() client.connect(('127.0.0.1', 8001)) while True: """ 请发送消息,格式为: - 消息:msg|你好呀 - 文件:file|xxxx.png """ content = input(">>>") # msg or file if content.upper() == 'Q': send_data(client, "close") break input_text_list = content.split('|') if len(input_text_list) != 2: print("格式错误,请重新输入") continue message_type, info = input_text_list # 发消息 if message_type == 'msg': # 发消息类型 send_data(client, json.dumps({"msg_type": "msg"})) # 发内容 send_data(client, info) # 发文件 else: file_name = info.rsplit(os.sep, maxsplit=1)[-1] # 发消息类型 send_data(client, json.dumps({"msg_type": "file", 'file_name': file_name})) # 发内容 send_file(client, info) client.close() if __name__ == '__main__': run()
2.1.2 UDP实例
-
server
import socket server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server.bind(('127.0.0.1', 8002)) while True: data, (host, port) = server.recvfrom(1024) # 阻塞 print(data, host, port) server.sendto("好的".encode('utf-8'), (host, port))
-
client
import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: text = input("请输入要发送的内容:") if text.upper() == 'Q': break client.sendto(text.encode('utf-8'), ('127.0.0.1', 8002)) data, (host, port) = client.recvfrom(1024) print(data.decode('utf-8')) client.close()
2.2 三次握手,四次挥手
三次握手情景:
1、客户端主机说:“我可以给你发送数据吗?”
2、服务器说:“可以的,不过我可能也会给你发送数据。”
3、客户端:“好,那我开始互相发送数据吧。”
四次挥手的情景大致是这样的:
1、客户端主机说:“我没有数据了,断开连接吧。 ”
2、服务器说:“好,但是我还有数据(不断给客户端发送数据,此时客户端已经不能给服务端发送数据了,但是必须要就收服务端发来的数据)。”
3、(当服务端给客户端发完数据后)服务端说:“我发完了,断开连接吧。”
4、客户端说:“好,断开连接吧。”
2.3 OSI七层模型
2.4 对比
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
3 粘包
当客户端向服务端放松数据时,服务端由于网络等原因,无法迅速的接受到数据,这时如果客户端连续的发送多条数据,服务端接收时无法区分客户端发来几条数据,而是直接接收全部(数据量如果小的话),这时客户端发送的几条消息就会被服务端当成一条数据接收。
处理:
客户端每次发送数据时,先把数据的长度按固定的字节数发送到服务端,服务端在接收时,先按固定的字节数把客户端要发送的数据的长度获取一下,之后再根据数据长度获取客户端发来的数据。
4 阻塞和非阻塞
server.setblocking(False) # 加上就变为了非阻塞
如果代码变成了非阻塞,程序运行时一旦遇到 `accept`、`recv`、`connect` 就会抛出 BlockingIOError 的异常。
5 IO多路复用
IO多路复用 + 非阻塞,可以实现让TCP的服务端同时处理多个客户端的请求
"""
优点:
1. 干点那其他的事。
2. 让服务端支持多个客户端同时来连接。
"""
# ################### socket服务端 ###################
import select
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False) # 加上就变为了非阻塞
server.bind(('127.0.0.1', 8001))
server.listen(5)
inputs = [server, ] # socket对象列表 -> [server, 第一个客户端连接conn ]
while True:
# 当 参数1 序列中的socket对象发生可读时(accetp和read),则获取发生变化的对象并添加到 r列表中。
# r = []
# r = [server,]
# r = [第一个客户端连接conn,]
# r = [server,]
# r = [第一个客户端连接conn,第二个客户端连接conn]
# r = [第二个客户端连接conn,]
r, w, e = select.select(inputs, [], [], 0.05)
for sock in r:
# server
if sock == server:
conn, addr = sock.accept() # 接收新连接。
print("有新连接")
# conn.sendall()
# conn.recv("xx")
inputs.append(conn)
else:
data = sock.recv(1024)
if data:
print("收到消息:", data)
else:
print("关闭连接")
inputs.remove(sock)
# 干点其他事 20s
# ################### socket客户端 ###################
import socket
client = socket.socket()
# 阻塞
client.connect(('127.0.0.1', 8001))
while True:
content = input(">>>")
if content.upper() == 'Q':
break
client.sendall(content.encode('utf-8'))
client.close()
标签:知识点,socket,python,data,server,file,必坑,conn,size
From: https://www.cnblogs.com/z-h-q/p/17651659.html