宁晋网站建设多少钱,阿里邮箱 网站开发,和女人做的电影网站,百度推广关键词怎么优化文章目录 wireshark 抓包工具的快速入门TCP/IP 协议详解TCP/IP 协议概述部分协议补充 OSI 参考模型及 TCP/IP 参考模型以太网帧格式ARP 数据报格式IP 段格式ICMP 协议TCP协议UDP 协议MTU和MSS补充 产品模式【C/S B/S】C/S 模式B/S 模式 DDOS简单介绍#xff08;SYN FloodingSYN FloodingSYN Flood 攻击防护手段 epoll 的使用多路复用技术相关用法即时聊天案例聊天室非阻塞编程VS多路复用技术来说用处不是很大了解补充 持续发送多个文件协议设计Python 发送整型数/浮点数多个文件传输的例子自行加for补充 wireshark 抓包工具的快速入门
wireshark 是捕获机器上的某一块网卡的网络包当你的机器上有多块网卡的时候你需要选择一个网卡。点击 Caputre-Interfaces… 出现下面对话框选择正确的网卡。然后点击Start按钮, 开始抓包。
WireShark 主要分为这几个界面
Display Filter(显示过滤器) 用于过滤Packet List Pane(封包列表) 显示捕获到的封包 有源地址和目标地址端口号。Packet Details Pane(封包详细信息), 显示封包中的字段Dissector Pane(16 进制数据)Miscellanous(地址栏杂项)
停止捕获之后我们可以通过左下角的这堆数据进而分析 Frame: 物理层的数据帧概况 Ethernet II: 数据链路层以太网帧头部信息 Internet Protocol Version 4: 互联网层 IP 包头部信息 Transmission Control Protocol: 传输层 T 的数据段头部信息大概率是是 TCP Hypertext Transfer Protocol: 应用层的信息大概率是 HTTP 协议
TCP/IP 协议详解
发送一句话的流程
TCP/IP 协议概述
tcp/ip 模型 4 层:
四层模型包含应用层http 超文本传输协议 ftp 文件传输协议 telnet 远程登录 ssh 安全外壳协议 smtp 简单邮件发送 pop3 收邮件传输层tcp 传输控制协议,udp 用户数据包协议网络层ip 网际互联协议 icmp 网络控制消息协议 igmp 网络组管理协议网络接口层arp 地址转换协议,rarp 反向地址转换协议mpls 多协议标签
部分协议补充
ARP地址转换协议用于获得同一物理网络中的硬件主机地址。是设备通过自己知道的 IP 地址来获得自己不知道的物理地址的协议。RARP反向地址转换协议RARP允许局域网的物理机器从网关服务器的 ARP 表或者缓存上请求其 IP 地址。网络管理员在局域网网关路由器里创建一个表以映射物理地址MAC和与其对应的 IP 地址。当设置一台新的机器时其 RARP 客户机程序需要向路由器上的 RARP 服务器请求相应的 IP 地址。假设在路由表中已经设置了一个记录RARP 服务器将会返回 IP 地址给机器此机器就会存储起来以便日后使用。 RARP 可以使用于以太网、光纤分布式数据接口及令牌环 LAN。IP网际互联协议负责在主机和网络之间寻址和路由数据包。ICMP网络控制消息协议用于发送报告有关数据包的传送错误的协议。Ping 命令所使用的协议IGMP网络组管理协议被 IP 主机用来向本地多路广播路由器报告主机组成员的协议。主机与本地路由器之间使用 Internet 组管理协议IGMPInternet Group Management Protocol来进行组播组成员信息的交互。TCP传输控制协议为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到响应的应用程序。UDP用户数据包协议提供了无连接通信且不对传送包进行可靠的保证。适合于一次传输少量数据。
OSI 参考模型及 TCP/IP 参考模型 TCP/IP 协议族的每一层的作用
网络接口层负责将二进制流转换为数据帧并进行数据帧的发送和接收。要注意的是数据帧是独立的网络信息传输单元。网络层负责将数据帧封装成 IP 数据报并运行必要的路由算法。传输层负责端对端之间的通信会话连接和建立。传输协议的选择根据数据传输方式而定。应用层负责应用程序的网络访问这里通过端口号来识别各个不同的进 程。
以太网帧格式 其中的源地址和目的地址是指网卡的硬件地址也叫 MAC 地址长度是 48bit是在网卡出厂时固化的。可在 shell 中使用 ifconfig 命令查看“HWaddr00:15:F2:14:9E:3F”部分就是硬件地址。协议字段有三种值分别对应 IP、ARP、RARP。帧尾是 CRC 校验码。
以太网帧中的数据长度规定最小 46 字节最大 1500 字节ARP 和 RARP 数据包的长度不够 46 字节要在后面补填充位。最大值 1500 称为以太网的最大传输单元MTU不同的网络类型有不同的 MTU如果一个数据包从以太网路由到拨号链路上数据包长度大于拨号链路的 MTU则需要对数据包进行分片。ifconfig 命令输出中也有“MTU:1500”。注意MTU 这个概念指数据帧中有效载荷的最大长度不包括帧头长度。
ARP 数据报格式
在网络通讯时源主机的应用程序知道目的主机的 IP 地址和端口号却不知道目的主机的硬件地址而数据包首先是被网卡接收到再去处理上层协议的如果接收到的数据包的硬件地址与本机不符则直接丢弃。因此在通讯前必须获得目的主机的硬件地址。ARP 协议就起到这个作用。源主机发出 ARP 请求询问“IP 地址是 192.168.0.1 的主机的硬件地址是多少”并将这个请求广播到本地网段以太网帧首部的硬件地址填 FF:FF:FF:FF:FF:FF 表示广播目的主机接收到广播的ARP 请求发现其中的 IP 地址与本机相符则发送一个 ARP 应答数据包给源主机将自己的硬件地址填写在应答包中。
每台主机都维护一个 ARP 缓存表可以用 arp -a 命令查看。缓存表中的表项有过期时间一般为 20 分钟如果 20 分钟内没有再次使用某个表项则该表项失效下次还要发 ARP 请求来获得目的主机的硬件地址。 硬件类型指链路层网络类型1 为以太网协议类型指要转换的地址类型 0x0800 为 IP 地址后面两个地址长度对于以太网地址和 IP 地址分别为 6 和 4 字节op 字段为 1 表示 ARP 请求op字段为 2 表示 ARP 应答
样例分析
Ethernet II, Src: Tp-LinkT_37:4f:8c (f4:83:cd:37:4f:8c), Dst: Broadcast (ff:ff:ff:ff:ff:ff)Destination: Broadcast (ff:ff:ff:ff:ff:ff)Source: Tp-LinkT_37:4f:8c (f4:83:cd:37:4f:8c)Type: ARP (0x0806)Address Resolution Protocol (request)Hardware type: Ethernet (1)Protocol type: IPv4 (0x0800)Hardware size: 6Protocol size: 4Opcode: request (1)Sender MAC address: Tp-LinkT_37:4f:8c (f4:83:cd:37:4f:8c)Sender IP address: 192.168.0.1Target MAC address: 00:00:00_00:00:00 (00:00:00:00:00:00)Target IP address: 192.168.0.108
IP 段格式
报文头5字节
版本号长度 4 bit。标识目前采用的 IP 协议的版本号。一般的值为 0100IPv40110IPv6首部长度长度 4 比特。这个字段的作用是为了描述 IP 包头的长度因为在 IP 包头中有变长的可选部分。该部分占 4 个 bit 位单位为32bit4 个字节即本区域值 IP 头部长度单位为 bit/(84)因此一个IP 包头的长度最长为“1111”即 15460 个字节。IP 包头最小长度为 20 字节。服务类型TOS长度 8 比特。8 位 按位被如下定义 PPP DTRC0 PPP定义包的优先级取值越大数据越重要 000 普通 (Routine) 001 优先的 (Priority) 010 立即的发送 (Immediate) 011 闪电式的 (Flash) 100 比闪电还闪电式的 (Flash Override) 101 CRI/TIC/ECP(找不到这个词的翻译) 110 网间控制 (Internetwork Control) 111 网络控制 (Network Control) D 时延: 0:普通 1:延迟尽量小 T 吞吐量: 0:普通 1:流量尽量大 R 可靠性: 0:普通 1:可靠性尽量大 M 传输成本: 0:普通 1:成本尽量小 0 最后一位被保留恒定为 0IP 包总长Total Length长度 16 比特。 以字节为单位计算的 IP 包的长度(包括头部和数据)所以 IP 包最大长度 65535 字节。标识符Identifier:长度 16 比特。该字段和 Flags 和 Fragment Offest 字段联合使用对较大的上层数据包进行分段fragment操作。路由器将一个包拆分后所有拆分开的小包被标记相同的值以便目的端设备能够区分哪个包属于被拆分开的包的一部分。标记Flags长度 3 比特。该字段第一位不使用。第二位是 DFDon’tFragment位DF 位设为 1 时表明路由器不能对该上层数据包分段。如果一个上层数据包无法在不分段的情况下进行转发则路由器会丢弃该上层数据包并返回一个错误信息。第三位是 MFMore Fragments位当路由器对一个上层数据包分段则路由器会在除了最后一个分段的 IP 包的包头中将 MF 位设为 1。片偏移Fragment Offset长度 13 比特。表示该 IP 包在该组分片包中位置接收端靠此来组装还原 IP 包。片偏移量13 位指出较长的分组在分片后某段在原分组的相对位置。也就是说相对原分组数据段的起点该片从何处开始。段偏移以 8 字节为偏移单位。这就是每个分片的长度一定是 8 字节64 位的整数倍生存时间TTL长度 8 比特。当 IP 包进行传送时先会对该字段赋予某个特定的值。当 IP 包经过每一个沿途的路由器的时候每个沿途的路由器会将 IP 包的 TTL 值减少 1。如果 TTL 减少为 0则该 IP 包会被丢弃。这个字段可以防止由于路由环路而导致 IP 包在网络中不停被转发。协议Protocol长度 8 比特。标识了上层所使用的协议。1 ICMP 2 IGMP 6 TCP 17 UDP 88 IGRP 89 OSPF校验Header Checksum长度 16 位。用来做 IP 头部的正确性检测但不包含数据部分。 因为每个路由器要改变 TTL 的值,所以路由器会为每个通过的数据包重新计算这个值。起源和目标地址Source and Destination Addresses这两个地段都是 32 比特。标识了这个 IP 包的起源和目标地址。要注意除非使用 NAT否则整个传输的过程中这两个地址不会改变。至此IP 包头基本的 20 字节已介绍完毕此后部分属于可选项不是必须的部分。可选项Options这是一个可变长的字段。该字段属于可选项主要用于测试由起源设备根据需要改写。暂时不介绍。
ICMP 协议
ICMP 是Internet 控制报文协议。它是TCP/IP 协议族的一个子协议用于在 IP 主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。
我们在网络中经常会使用到 ICMP 协议比如我们经常使用的用于检查网络通不通的 Ping 命令Linux 和 Windows 中均有这个“Ping”的过程实际上就是ICMP 协议工作的过程。还有其他的网络命令如跟踪路由的 Tracert 命令windows、traceroutelinux也是基于ICMP 协议的。 TCP协议 报文头5字节
Transmission Control Protocol, Src Port: 14660, Dst Port: 443, Seq: 1, Ack: 1, Len: 279Source Port: 14660Destination Port: 443[Stream index: 31][Conversation completeness: Complete, WITH_DATA (63)][TCP Segment Len: 279]Sequence Number: 1 (relative sequence number)Sequence Number (raw): 466040739[Next Sequence Number: 280 (relative sequence number)]Acknowledgment Number: 1 (relative ack number)Acknowledgment number (raw): 14604059820101 .... Header Length: 20 bytes (5)Flags: 0x018 (PSH, ACK)000. .... .... Reserved: Not set...0 .... .... Accurate ECN: Not set.... 0... .... Congestion Window Reduced: Not set.... .0.. .... ECN-Echo: Not set.... ..0. .... Urgent: Not set.... ...1 .... Acknowledgment: Set.... .... 1... Push: Set.... .... .0.. Reset: Not set.... .... ..0. Syn: Not set.... .... ...0 Fin: Not set[TCP Flags: ·······AP···]Window: 514[Calculated window size: 131584][Window size scaling factor: 256]Checksum: 0x865b [unverified][Checksum Status: Unverified]Urgent Pointer: 0[Timestamps][Time since first frame in this TCP stream: 0.038470000 seconds][Time since previous frame in this TCP stream: 0.001132000 seconds][SEQ/ACK analysis][iRTT: 0.037338000 seconds][Bytes in flight: 279][Bytes sent since last PSH flag: 279]TCP payload (279 bytes)
源端口、目的端口16 位长。标识出远端和本地的端口号。序号32 位长。标识发送的数据报的顺序。确认号32 位长。希望收到的下一个数据报的序列号。TCP 头长4 位长。表明 TCP 头中包含多少个 32 位字。就是有多少个 4 个字节4位未用。12位表示状态按顺序分别是 CWR: 拥塞窗口减发送方降低它的发送速率 ECE: ECN 回显发送方收到了一个更早的拥塞报告 URG紧急指针urgent pointer有效紧急指针指出在本报文段中的紧急数据的最后一个字节的序号 ACKACK 位置 1 表明确认号是合法的。如果 ACK 为 0那么数据报不包含确认信息确认字段被省略。 PSH表示是带有 PUSH 标志的数据。接收方因此请求数据报一到便可送往应用程序而不必等到缓冲区装满时才发送。当 PSH1 时则报文段会被尽快地交付给目的方不会对这样的报文段使用缓存策略 RST用于复位由于主机崩溃或其他原因而出现的错误的连接。还可以用于拒绝非法的数据报或拒绝连接请求。当 RST 为 1 时表明 TCP 连接中出现了严重的差错必须释放连接然后再重新建立连接。 SYN用于建立连接。当 SYN1 时表示发起一个连接请求。 FIN用于释放连接。当 FIN1 时表明此报文段的发送端的数据已发送完 成并要求释放连接。窗口大小16 位长。窗口大小字段表示在确认了字节之后还可以发送多少个字节。此字段用来进行流量控制。单位为字节数这个值是本机期望一次接收的字节数校验和16 位长。是为了确保高可靠性而设置的。它校验头部、数据和伪 TCP头部之和。可选项0 个或多个 32 位字。包括最大 TCP 载荷窗口比例、选择重复数据报等选项。
所以对于tcp来说他发一个包当中最多能有1500-20ip中的5字节报头-20tcp当中的5字节报头1460个数据位
UDP 协议
2字节报头
源、目标端口号字段占 16 比特。作用与 TCP 数据段中的端口号字段相同用来标识源端和目标端的应用进程。长度字段占 16 比特。标明 UDP 头部和 UDP 数据的总长度字节。数据报的长度是指包括报头和数据部分在内的总字节数。因为报头的长度是固定的所以该域主要被用来计算可变长度的数据部分又称为数据负载。数据报的最大长度根据操作环境的不同而各异。从理论上说包含报头在内的数据报的最大长度为 65535字节。不过一些实际应用往往会限制数据报的大小有时会降低到 8192 字节。校验和字段占 16 比特。用来对 UDP 头部和 UDP 数据进行校验。和 TCP 不同的是对 UDP 来说此字段是可选项而 TCP 数据段中的校验和字段是必须有的
相同的对于udp来说他发一个包当中最多能有1500mtu-20ip中的5字节报头-8udp当中的字节报头1472个数据位
MTU和MSS
MTU 是网络传输最大报文包MSS 是网络传输数据最大值。
mss 加包头数据就等于 mtu. 简单说拿 TCP 包做例子。 报文传输 1400 字节的数据的话那么 mss 就是 1400再加上 20 字节 IP 包头20 字节 tcp 包头那么 mtu 就是 14002020. 当然传输的时候其他的协议还要加些包头在前面总之mtu 就是总的最后发出去的报文大小。mss 就是你需要发出去的数据大小。MSS: MSS 就是 TCP 数据包每次能够传输的最大数据分段。为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS值这个值 TCP 协议在实现的时候往往用 MTU 值代替需要减去 IP 数据包包头的大小 20Bytes 和 TCP 数据段的包头 20Bytes所以往往 MSS 为 1460。通讯双方会根据双方提供的 MSS 值得最小值确定为这次连接的最大 MSS 值.
补充
TCP 和 UDP一台机器向另外一台机器发送了 3 个包对方可能收到几个包 TCP 3 3 个到任意多个 实际上就是超时重传导致的 UDP 0 到 3 个
产品模式【C/S B/S】
C/S 模式
传统的网络应用设计模式客户机(client)/服务器(server)模式。需要在通 讯两端各自部署客户机和服务器来完成数据通信。
B/S 模式
浏览器()/服务器(server)模式。只需在一端部署服务器而另外一端使用每台 PC 都默认配置的浏览器即可完成数据的传输。
DDOS简单介绍SYN Flooding
在探讨 SYN 攻击之前我们先看看 linux 内核对 SYN 是怎么处理的 1. Server 接收到 SYN 连接请求。 内部维护一个队列我们暂称之半连接队列半连接并不准确 发送 ack 及 syn 给 Client 端等待 Client 端的 ack 应答接收到则完成三次握手建立连接。 如果接收不到 ack 应答则根据延时重传规则继续发送 ack 及 syn 给客户端。
利用上述特点。我们构造网络包源地址随机构建意味着当 Server 接到SYN 包时应答 ack 和 syn 时不会得到回应。在这种情况下 Server 端内核就会维持一个很大的队列来管理这些半连接。 当半连接足够多的时候就会导致新来的正常连接请求得不到响应 也就是所谓的 DOS 攻击。
SYN Flood 攻击防护手段
tcp_max_syn_backlog 半连接队列长度tcp_synack_retries: synack 的重传次数tcp_syncookies : syn dookie
上面的三个路径在linux服务器下的/proc/sys/net/ipv4下
一般的防御措施就是就是减小 SYNACK 重传次数增加半连接队列长度启用 syn cookie。不过在高强度攻击面前调优 tcp_syn_retries 和tcp_max_syn_backlog 并不能解决根本问题更有效的防御手段是激活 tcp_syncookies在连接真正创建起来之前它并不会立刻给请求分配数据区存储连接状态而是通过构建一个带签名的序号来屏蔽伪造请求。
epoll 的使用多路复用技术
通常来说实现处理tcp请求为一个连接一个线程在高并发的场景这种多线程模型与Epoll相比就显得相形见绌了。epoll是linux2.6内核的一个新的系统调用epoll在设计之初就是为了替代select, poll线性复杂度的模型epoll的时间复杂度为O(1), 也就意味着epoll在高并发场景随着文件描述符的增长有良好的可扩展性。
相关用法
import select 导入 select 模块
epoll select.epoll() 创建一个 epoll 对象
epoll.register(文件句柄,事件类型) 注册要监控的文件句柄和事件事件类型:select.EPOLLIN 可读事件select.EPOLLOUT 可写事件select.EPOLLERR 错误事件select.EPOLLHUP 客户端断开事件
epoll.unregister(文件句柄) 销毁文件句柄
epoll.poll(timeout) 当文件句柄发生变化则会以列表的形式主动报告给用户进程,timeout为超时时间默认为-1即一直等待直到文件句柄发生变化如果指定为 1那么 epoll 每 1 秒汇报一次当前文件句柄的变化情况如果无变化则返回空
epoll.fileno() 返回 epoll 的控制文件描述符(Return the epoll control file descriptor)
epoll.modfiy(fineno,event) fineno 为文件描述符 event 为事件类型 作用是修改文件描述符所对应的事件
epoll.fromfd(fileno) 从 1 个指定的文件描述符创建 1 个 epoll 对象
epoll.close() 关闭 epoll 对象的控制文件描述符即时聊天案例
服务端
import socket
import sys
import selectdef tcp_server():# 创建一个tcp的socket对象server socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 绑定if len(sys.argv) 2:server.bind((sys.argv[1],2000))else:server.close()return# 监听server.listen(128)# accepts_client,s_arr server.accept()# 构建epoll对象s_epoll select.epoll()# 进行监听s_epoll.register(s_client.fileno(),select.EPOLLIN) # 监听s_client是否接收到消息并转化为可读s_epoll.register(sys.stdin.fileno(),select.EPOLLIN)# 监听标准缓冲区stdin是否接收到消息并转化为可读# while进行聊天while True:# 开始进行监听 -1代表永久时长events s_epoll.poll(-1)for fd,event in events:if fd s_client.fileno():data s_client.recv(1000)# TCP 对方断开时内核把 socket 对象对应的描述符标记为可读状态epoll 就会检测到这时候 recv 读时读到到的内容为空通过这个来判断进行断开if data:print(data.decode(utf8))else:print(对方断开连接)s_epoll.close()breakelif fd sys.stdin.fileno():data input()s_client.send(data.encode(utf8))s_client.close()server.close()if __name__ __main__:tcp_server()客户端
import socket
import sys
import selectdef tcp_client():# 创建一个tcp的socket对象client socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 绑定if len(sys.argv) 2:client.connect((sys.argv[1],2000))else:client.close()return# 构建epoll对象c_epoll select.epoll()# 进行监听c_epoll.register(client.fileno(),select.EPOLLIN) # 监听s_client是否接收到消息并转化为可读c_epoll.register(sys.stdin.fileno(),select.EPOLLIN)# 监听标准缓冲区stdin是否接收到消息并转化为可读# while进行聊天while True:# 开始进行监听 -1代表永久时长events c_epoll.poll(-1)for fd,event in events:if fd client.fileno():data client.recv(1000)# TCP 对方断开时内核把 socket 对象对应的描述符标记为可读状态epoll 就会检测到这时候 recv 读时读到到的内容为空通过这个来判断进行断开if data:print(data.decode(utf8))else:print(对方断开连接)c_epoll.close()breakelif fd sys.stdin.fileno():data input()client.send(data.encode(utf8))client.close()if __name__ __main__:tcp_client()聊天室
客户端
import select
import socket
import sysdef tcp_client():客户端:return:Noneclient socket.socket(socket.AF_INET,socket.SOCK_STREAM)if len(sys.argv) 2:client.connect((sys.argv[1],2000))else:client.connect((,2000))epoll select.epoll()epoll.register(client.fileno(),select.EPOLLIN)epoll.register(sys.stdin.fileno(),select.EPOLLIN)while True:events epoll.poll(-1)for fd, event in events:if fd client.fileno():data client.recv(100)if data:print(data.decode(utf8))else:print(对方断开了)returnelif fd sys.stdin.fileno():data input()client.send(data.encode(utf8))client.close()if __name__ __main__:tcp_client()服务端
import socket
import select
import sysdef tcp_server():聊天室的中转服务器:return:Noneserver socket.socket(socket.AF_INET,socket.SOCK_STREAM)if len(sys.argv) 2:server.bind((sys.argv[1],2000))else:server.bind((,2000))server.listen(128)epoll select.epoll()# 让epoll监控server对象如果传来消息就创建相对应的连接epoll.register(server.fileno(),select.EPOLLIN)total_client {}while True:events epoll.poll(-1)for fd,event in events:if fd server.fileno():client,client_arr server.accept()# client Client(fd,s_client,s_client_arr)# 值得关注的室这里total_client[client.fileno()]自己写错了成了total_client[fd]卡了很久total_client[client.fileno()] tuple(client,client_arr)print(str(client_arr) 加入群聊)epoll.register(client.fileno(),select.EPOLLIN)else:remove_client Nonedata total_client[fd][0].recv(1000)str_data str(total_client[fd][1]) : data.decode()if data:for other_fd in total_client:if other_fd ! fd:total_client[other_fd][0].send(str_data.encode())else:remove_client total_client[fd]if remove_client:print(remove_client[1]离开群聊)total_client.pop(remove_client[0].fileno())epoll.unregister(remove_client[0].fileno(), select.EPOLLIN)remove_client[0].close()server.close()非阻塞编程VS多路复用技术来说用处不是很大了解
from socket import *
import select
import systcp_server_socket socket(AF_INET, SOCK_STREAM)# 重用对应地址和端口
tcp_server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)# 本地IP地址和端口
address (192.168.5.7, 2000)tcp_server_socket.bind(address)
# 端口激活
tcp_server_socket.listen(100)# 非阻塞编程的的关键在于这一句话实际上就是通过这一句话让程序遇到阻塞的地方不进行阻塞而进行爆异常
tcp_server_socket.setblocking(False)client_socket None
temp_client None
while True:try:temp_client, clientAddr tcp_server_socket.accept()except Exception as e:# print(e)client_socket temp_clientif client_socket:client_socket.setblocking(False)try:text client_socket.recv(1024)#如果对方断开if not text:print(byebye)client_socket.close()temp_clientNonecontinueprint(text.decode(utf-8))except Exception as e:pass以及这里才用到的 重用对应地址和端口 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
补充
相同的 send也会阻塞在send发送的过多次的时候send也需要进行epoll或者非阻塞编程
持续发送多个文件协议设计
粘包两次发送的报文挨在一起就好比如我们百度网盘在下载多个文件的时候抓包的结果就是文件名1文件内容1文件名2文件内容2······的字节流
首先我们采用的方法就是在接受一个文件之前先接受一个整形的数然后拿这个整型数接着接后续的字节流再接整型数最后的效果给个例子就是 先读到5 然后读取文件名file1 然后读取100 然后读取100个文件内容1的字节流and so on
那么就出现一个问题我们如何使用python来发送一个整形/浮点数的数据呢
Python 发送整型数/浮点数
参考的详细链接
这边简单只要会使用pack和unpack就差不多
还有关于其struct模块定义的数据类型表常用
FormatC Typecchar?booliintIunsigned intffloatddoublesstring
给个使用的样例的话
import structfile_content_bytes 我是一个文件.encode(utf8)
print(file_content_bytes.decode())
print(file_content_bytes)
print(100**)file_head len(file_content_bytes)
print(file_head) # 18个 3*6
print(type(file_head))
print(100**)file_head_bytes struct.pack(I,file_head)
print(file_head_bytes)
print(type(file_head_bytes))
print(100**)file_head2 struct.unpack(I,file_head_bytes)
print(file_head2)
print(type(file_head2)) # 出来的是一个tuple类型所以实际上需要使用tuple接口
print(file_head2[0])
print(type(file_head2[0]))
print(100**)
值得关注的是我们传进去的字节流如果仔细观察你会发现他是小端传入但是计网中传入的数据似乎是大端这里打个问号笔者也不知道
多个文件传输的例子自行加for
服务端
from socket import *
import select
import sys
import time
import struct
tcp_server_socket socket(AF_INET, SOCK_STREAM)
# 重用对应地址和端口
tcp_server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 本地 IP 地址和端口
address (192.168.1.112, 2000)
tcp_server_socket.bind(address)
# 端口激活
tcp_server_socket.listen(100)
client_socket, clientAddr tcp_server_socket.accept()
#连上了
print(clientAddr)
#发文件名
file_name Readme
#先发报文头--文件名的长度
b_file_name file_name.encode(utf-8)
client_socket.send(struct.pack(I, len(b_file_name)))
client_socket.send(b_file_name)
#发文件内容
file open(file_name,rb)
text_bytes file.read()
client_socket.send(struct.pack(I, len(text_bytes)))
client_socket.send(text_bytes)
file.close()
time.sleep(5)
client_socket.close()
tcp_server_socket.close()客户端
from socket import *
import select
import sys
import time
import struct
tcp_client_socket socket(AF_INET, SOCK_STREAM)
# 本地 IP 地址和端口
address (192.168.1.112, 2000)
# 连接服务器
tcp_client_socket.connect(address)
time.sleep(1)
#接文件名
train_lentcp_client_socket.recv(4)
file_nametcp_client_socket.recv(struct.unpack(I,train_len)[0])
print(file_name)
fileopen(file_name.decode(utf-8),wb)
train_lentcp_client_socket.recv(4)
text_bytestcp_client_socket.recv(struct.unpack(I,train_len)[0])
file.write(text_bytes)
file.close()
tcp_client_socket.close()补充
接收方不知道该接收多大的数据才算接收完毕
stat 接口
print(os.stat(Readme).st_size)
print(os.stat(Readme).st_ino)
print(hex(os.stat(Readme).st_mode))
print(os.stat(Readme).st_uid)
print(os.stat(Readme).st_gid)
print(time.ctime(os.stat(Readme).st_mtime))