使用socket+Python实现ping

import os
import socket
import struct
import select
import time

# 计算校验和,用于确保数据的完整性
def checksum(source_string):
    sum = 0
    count = 0
    max_count = len(source_string)
    
    # 处理成对的字节
    while count < max_count - 1:
        val = source_string[count + 1] * 256 + source_string[count]
        sum = sum + val
        sum = sum & 0xffffffff  # 保持sum为32位
        count = count + 2
    
    # 处理最后一个字节(如果长度为奇数)
    if max_count % 2:
        sum = sum + source_string[-1]
        sum = sum & 0xffffffff
    
    # 折叠高16位和低16位
    sum = (sum >> 16) + (sum & 0xffff)
    sum = sum + (sum >> 16)
    # 取反得到最终的校验和
    answer = ~sum
    answer = answer & 0xffff
    # 最终调整顺序(大端或小端)
    answer = answer >> 8 | (answer << 8 & 0xff00)
    
    return answer

# 创建 ICMP echo 请求包
def create_packet(id):
    # 头部类型为8(ICMP echo请求),代码为0,校验和为0,id为传入的id,序列号为1
    header = struct.pack('bbHHh', 8, 0, 0, id, 1)
    data = 256 * b'Q'  # 数据部分
    my_checksum = checksum(header + data)  # 计算校验和
    # 重新打包头部,包含正确的校验和
    header = struct.pack('bbHHh', 8, 0, socket.htons(my_checksum), id, 1)
    return header + data

# 执行 ping 操作
def ping(dest_addr, timeout=1, count=4):
    icmp = socket.getprotobyname("icmp")  # 获取 ICMP 协议的编号
    # 创建原始套接字
    my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
    
    my_ID = os.getpid() & 0xFFFF  # 生成一个唯一的ID
    
    sent_count = 0  # 发送的ping包数量
    received_count = 0  # 收到的ping包数量
    
    while sent_count < count:
        sent_count += 1
        packet = create_packet(my_ID)
        
        my_socket.sendto(packet, (dest_addr, 1))  # 发送 ICMP echo 请求
        
        while True:
            started_select = time.time()
            # 监听套接字是否有数据可读
            what_ready = select.select([my_socket], [], [], timeout)
            how_long_in_select = (time.time() - started_select)
            if what_ready[0] == []:  # 超时
                print("请求超时。")
                break
            
            time_received = time.time()
            received_packet, addr = my_socket.recvfrom(1024)  # 接收数据包
            icmp_header = received_packet[20:28]  # 提取 ICMP 头部
            type, code, checksum, packet_ID, sequence = struct.unpack(
                "bbHHh", icmp_header
            )
            
            if packet_ID == my_ID:  # 确认是发自我们的请求
                bytes_In_double = struct.calcsize("d")
                time_sent = struct.unpack("d", received_packet[28:28 + bytes_In_double])[0]
                print("来自 {} 的回复: 字节={} 时间={:.2f}ms".format(
                    addr[0], len(received_packet), (time_received - time_sent) * 1000)
                )
                received_count += 1
                break
            
            time_left = timeout - how_long_in_select
            if time_left <= 0:
                print("请求超时。")
                break
    
    my_socket.close()
    return received_count

# 使用示例
if __name__ == '__main__':
    dest = input("输入要 ping 的主机: ")
    print("正在用 Python ping {}:".format(dest))
    ping(dest)

# 处理成对的字节
    while count < max_count-1:
        val = source_string[count + 1]*256 + source_string[count]
        sum = sum + val
        sum = sum & 0xffffffff
        count = count + 2

    1.以成对的字节进行遍历,将源字符串(source_string)中的成对字节合并成16位的整数,并将这些整数累加到 sum 变量中。源字符串是要发送的数据包的内容。

    2.val = source_string[count + 1]*256 + source_string[count] 将源字符串中的当前字节(source_string[count])和下一个字节(source_string[count + 1])结合起来形成一个16位的整数。由于在大多数计算机中,整数是以小端序存储的,所以 count + 1 位置的字节是高字节,需要乘以256(即左移8位)以放在结果整数的高位,然后加上 count 位置的字节作为低位。

    3.sum = sum + val 这里将刚刚计算出的16位整数 val 加到累加器 sum 上。这个累加器最终会包含所有16位整数的和。

    4.print(source_string)的结果为source_string:  b'\x08\x00\x00\x00\xe4H\x01\x00QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ'

由于字符串长度为264,循环将运行132次(假设字符串有偶数个字节)。第一次循环会处理索引为0和1的字节(\x08 和 \x00),第二次循环处理索引为2和3的字节(\x00 和 \x00),以此类推,直到处理完所有的字节对。(一个字节是8位)

# 最终调整顺序

answer = answer >> 8 | (answer << 8 & 0xff00)

    answer >> 8:这将 answer 的所有位向右移动8位。这个操作会将原始 answer 的高8位移动到低8位的位置。
    answer << 8:这将 answer 的所有位向左移动8位。这个操作会将原始 answer 的低8位移动到高8位的位置。
    answer << 8 & 0xff00:& 0xff00 是一个掩码操作,它将确保左移操作后只保留高8位的值,低8位将被清零。0xff00 是一个16位的整数,其中高8位是1,低8位是0。
    answer >> 8 | (answer << 8 & 0xff00):| 是按位或操作,它将上述两个操作的结果组合起来。右移的结果会在新值的低8位,左移并掩码后的结果会在新值的高8位。 

以 4660 为例操作:
    右移8位 (answer >> 8):将 00010010 01100100 右移8位变为 00000000 00010010,在十进制中这是 18。
    左移8位并应用掩码 (answer << 8 & 0xff00):首先,将 00010010 01100100 左移8位变为 01100100 00000000,这个操作后在十进制中为 25856。然后应用掩码 0xff00(在二进制中为 11111111 00000000),结果仍为 01100100 00000000(25856)。
    合并两个结果:使用按位或操作将 00000000 00010010(18)和 01100100 00000000(25856)合并,得到 01100100 00010010,这在十进制中为 25618。

header = struct.pack('bbHHh', 8, 0, 0, id, 1) 

    struct.pack() 是 Python struct 模块中的一个函数,它的作用是将给定的值打包成特定格式的二进制数据。在网络编程和二进制文件处理中经常使用这个函数,因为它能够根据指定的格式将Python数据类型转换为字节字符串,这些字节字符串可以被发送到网络或写入文件。

socket.htons(my_checksum) 

    socket.htons() 函数在 Python 中用于将一个16位的正短整数从主机字节序转换为网络字节序。在网络编程中经常需要这样做,因为不同的计算机系统有不同的整数存储方式,即字节序问题。 

my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)

    调用 socket.socket(socket.AF_INET, socket.SOCK_RAW) 的作用是创建一个原始套接字(raw socket),用于低级网络通信,其中可以发送和接收特定网络协议的数据包。 
    socket.AF_INET: 这个参数指定地址族为 IPv4。它是用来确定套接字使用的网络层协议,AF_INET 表示使用 IPv4 地址。
    socket.SOCK_RAW: 这个参数指定套接字类型为“原始套接字”。原始套接字允许你访问底层协议,如 ICMP、TCP、UDP 等。使用原始套接字,你可以构造自己的数据包头部,进行更为灵活的网络操作。
    icmp: 这是 socket.getprotobyname("icmp") 返回的 ICMP 协议编号。它告诉套接字使用 ICMP 协议。ICMP 通常用于发送控制消息,如 ping 请求和响应。

my_ID = os.getpid() & 0xFFFF 

    os.getpid() 函数用于获取当前进程的进程 ID(PID)。在 Python 中,这个函数返回一个整数,代表当前运行的进程的唯一标识符。 
    在网络编程,特别是在构建 ICMP Echo 请求(如 ping 命令)时,通常需要一个标识符来区分不同的 Echo 请求。使用当前进程的 PID 作为标识符是一个常见的做法,因为它能够为每个不同的进程提供一个唯一的标识。然而,由于 PID 的大小可能超出了 ICMP Echo 请求头中标识符字段所能容纳的范围(通常为 16 位),所以通过与 0xFFFF 进行按位与运算,可以确保得到的标识符适合在该字段中使用,即将 PID 限制在 0 到 65535 (即 0xFFFF)的范围内。这样做既保留了一定程度的唯一性,又符合协议的要求。(这个my_ID就是传入create_packet()函数中header = struct.pack('bbHHh', 8, 0, 0, id, 1) 所用的id参数。)

my_socket.sendto(packet, (dest_addr, 1)) 

   调用 my_socket.sendto(packet, (dest_addr, 1)) 的作用是通过之前创建的原始套接字 my_socket 发送一个数据包 packet 到指定的目标地址 dest_addr 上的端口号 1。

what_ready = select.select([my_socket], [], [], timeout)

if what_ready[0] == []:  # Timeout

                print("请求超时")

                break

(以上代码可以省去)

    调用 select.select([my_socket], [], [], timeout) 的作用是使用 select 模块来监视套接字的状态,检查套接字 my_socket 是否有数据可读,是否可以无阻塞地进行读操作。这是一种多路复用输入/输出的方式,用于在多个通信渠道上等待事件发生,从而避免程序在单个通信渠道上阻塞。

    在 select.select() 调用中,参数有以下含义:
    第一个参数 [my_socket] 是一个套接字列表,select 将监视这个列表中的套接字以查看它们是否变得可读(即是否有数据到达套接字,可以进行读操作)。
    第二个参数 [] 是一个空列表,用于指定需要检查是否可写的套接字列表。在这个调用中,我们不关心套接字是否可写,所以传入一个空列表。
    第三个参数 [] 同样是一个空列表,用于指定需要检查是否有错误的套接字列表。同样,在这个调用中,我们不关心套接字是否有错误,所以传入一个空列表。
    第四个参数 timeout 是一个超时值,指定 select 等待的最长时间(以秒为单位)。如果指定了超时时间,即使没有套接字变成可读,select 也会在超时后返回。如果 timeout 是 None,select 将会无限期地等待直到至少有一个套接字变得可读。

    例如,当你发送了一个 ICMP Echo 请求后,你可能会使用 select.select() 来等待一个响应,而不会阻塞程序的运行。如果 select 在超时时间内检测到 my_socket 有数据可读,它会返回一个包含 my_socket 的列表,这意味着你可以从套接字读取数据而不会阻塞。如果在超时时间内没有数据可读,select 将返回一个空列表,并且你可以决定是否重新发送请求、继续等待或进行其他操作。

 

received_packet, addr = my_socket.recvfrom(1024)

    调用 received_packet, addr = my_socket.recvfrom(1024) 的作用是接收通过网络传输到达指定的原始套接字 my_socket 的数据,并且读取最多 1024 字节的数据。

    在这个函数调用中:
    received_packet 是接收到的数据内容。这个数据包含了发送方发送的原始数据,可能包括IP头部、ICMP头部以及随后的数据部分。
    addr 是一个包含发送方地址信息的元组,通常形式为 (IP地址, 端口号)。在接收到的是 ICMP 消息的情况下,端口号通常是不相关的,因为 ICMP 是网络层的协议,不使用传输层的端口号。
my_socket 是之前创建的原始套接字,用于在网络上发送和接收低级别的协议数据包。
    1024 是指定的缓冲区大小,以字节为单位。它告诉 recvfrom 方法在单次调用中最多可以接收多少字节的数据。

    因此,当调用 my_socket.recvfrom(1024) 时,它会阻塞当前线程,直到有数据到达套接字或者套接字关闭。一旦接收到数据,它会停止阻塞,并将数据和发送方的地址赋值给 received_packet 和 addr 变量。这个操作通常用于网络通信中的数据接收,例如,在实现ping程序时,用来接收ICMP Echo响应。

icmp_header = received_packet[20:28]

    提取接收到的数据包中的 ICMP 协议头部分,ICMP 协议头通常包含类型、代码和校验和等信息,长度为 8 字节。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/593834.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nginx的前世今生(二)

书接上回&#xff1a; 上回书说到&#xff0c;nginx的前世今生&#xff0c;这回我们继续说 3.缓冲秘籍&#xff0c;洪流控水 Nginx的缓冲区是其处理数据传输和提高性能的关键设计之一&#xff0c;主要用于暂存和管理进出的数据流&#xff0c;以应对不同组件间速度不匹配的问题…

PG WAL日志理解

类似于oracle的redo log&#xff0c;用于数据库恢复&#xff0c;当一条SQL语句执行&#xff0c;PG会把对应的块放到缓冲区执行&#xff0c;&#xff0c;会写进WAL缓冲区会进行写操作&#xff0c;commit后&#xff0c;WAL writer进程进行写操作&#xff0c;把日志缓冲区WAL buff…

SpringData JPA - ORM 框架下,打造高效数据访问层

目录 一、SpringData JPA 概述 1.1、什么是 JPA 1.2、什么是 ORM 1.3、什么是 Hibernate 1.4、JPA 和 Hibernate 的关系 1.5、JPA 的优势 二、SpringData JPA 实战开发 2.1、依赖 2.2、配置文件 2.3、启动类 2.4、创建实体 2.5、基于 JpaRepository 的 CRUD 三、…

超市购物|基于SprinBoot+vue的超市购物系统(源码+数据库+文档)

目录 基于SprinBootvue的企业人事管理系统 一、前言 二、系统设计 三、系统功能设计 1商品管理 2公告管理 3公告类型管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设…

C语言--带环链表问题

继续学习 一、判断链表是否带环 141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;用快慢指针&#xff0c;快指针走两步&#xff0c;慢指针走一步&#xff0c;当慢指针走一半快指针进到环里 当慢指针进环&#xff0c;快指针已经在环中转了一会儿了 | |…

“字节跳动-文远知行杯”——广东工业大学第十四届程序设计竞赛

补题补题&#xff1a; A、hzy 和zsl 的生存挑战 题解&#xff1a;由于我们去考虑最优解&#xff0c;那么其中两人就应该是这样走法&#xff0c;一人选择相同数&#xff0c;另一人选择相反数&#xff0c;这样必会通关 #include <iostream> using namespace std;int main(…

Web服务器手动配置

目录 配置环境 http配置 配置步骤 1、首先安装Nginx&#xff08;已经安装的跳过这步&#xff09; 2、查看一下下Nginx的配置文件结构&#xff0c;了解如何配置&#xff0c;以及配置的各个条目有什么作用&#xff08;为接下来的配置打基础&#xff09; 3、创建你的网页 4、…

【银角大王——Django课程——靓号搜索实现/单独一篇】

搜索框功能实现 靓号搜索在Django框架中搜索功能实现——底层就是模糊查询添加一个搜索框&#xff0c;使用bootstrap框架将GO&#xff01;修改成一个放大镜靓号列表函数代码解释效果演示 靓号搜索 在Django框架中搜索功能实现——底层就是模糊查询 数字条件用法字符串条件用法…

学习如何使用PyQt5实现notebook功能

百度搜索“pyqt5中notebook控件”&#xff0c;AI自动生成相应例子的代码。在 PyQt5 中&#xff0c;QTabWidget 类被用作 Notebook 控件。以下是一个简单的示例&#xff0c;展示如何创建一个带有两个标签的 Notebook 控件&#xff0c;并在每个标签中放置一些文本。 import sys f…

电子信息工程专业就业前景怎么样

电子信息工程专业的就业前景十分广阔&#xff0c;主要得益于现代社会对信息技术的依赖不断加深以及科技的快速发展&#xff0c;以下是上大学网&#xff08;www.sdaxue.com&#xff09;对该专业就业前景的具体分析&#xff0c;供大家参考&#xff01; 行业需求广泛&#xff1a;随…

VBA字典与数组第十四讲:单列数组与单行数组间的运算

《VBA数组与字典方案》教程&#xff08;10144533&#xff09;是我推出的第三套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;字典是VBA的精华&#xff0c;我要求学员必学。7.1.3.9教程和手册掌握后&#xff0c;可以解决大多数工作中遇到的实际问题。…

动态规划 ------ 背包问题

文章目录 1. 01 背包问题1.二维解决2. 一维优化 2. 完全背包问题1.暴力3 for.2. 二维优化3. 一维优化 3. 多重背包问题Ⅰ.1. 二维解决2. 一维优化 4. 多重背包问题Ⅱ5. 混合背包问题6. 二维费用背包问题7. 分组背包问题 背包问题是动态规划中非常典型的一些题&#xff0c;本篇文…

ctfshow——SSRF

文章目录 web 351web 352web 353web 354web 355web 356web357web 358web 359web 360 SSRF(Server-Side Request Forgery&#xff1a;服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下&#xff0c;SSRF攻击的目标是从外网无法访问的内部系统…

同向双指针(滑动窗口)算法

209. 长度最小的子数组 这里的更新结果就题来定 class Solution {public int minSubArrayLen(int target, int[] nums) {int sum 0;int len 0;int f 0;for(int left 0, right 0; right < nums.length;){//求和sum nums[right];while(sum > target){//lenint t ri…

分布式锁之-redis

什么是分布式锁&#xff1f; 即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题&#xff0c;而分布式锁&#xff0c;就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是&#xff0c;分布式系统中竞争共享资源的最小粒度从线程升级成了…

JVM垃圾回收器G1大总结-详解

一、介绍: 1.停顿时间模型?? 作为CMS收集器的替代者和继承人&#xff0c;G1是基于“停顿时间模型”诞生的的垃圾收集器&#xff0c;停顿时间模型的意思是能够支持指定在一个长度为M毫秒的时间片段内&#xff0c;消耗在垃圾收集上的时间大概率不超过N毫秒这样的目标. 2.G1摒…

进程与线程(进程)

进程&#xff1a; 概念&#xff1a;进程是进程实体的运行过程&#xff0c;是系统进行资源分配和调度的一个独立单位 PID:当进程被创建时&#xff0c;操作系统会为该进程分配一个唯一的、不重复的“身份证号” 组成&#xff1a; PCB&#xff08;进程控制块&#xff09;&#…

使用AIGC生成软件类图表

文章目录 如何使用 AI 生成软件类图表什么是 MermaidMermaid 的图片如何保存&#xff1f;mermaid.liveDraw.io Mermaid可以画什么图&#xff1f;流程图时序图 / 序列图类图状态图甘特图实体关系图 / ER图 如何使用 AI 生成软件类图表 ChatGPT 大语言模型不能直接生成各类图表。…

W801学习笔记二十:宋词学习应用

前三章完成了唐诗的应用&#xff0c;本章将实现宋词的学习应用。 宋词与唐诗的区别不大&#xff0c;马上开始。 1、我们需要参考前面唐诗的方式&#xff0c;把宋词文本下载下来&#xff0c;并进行格式整理。 W801学习笔记十七&#xff1a;古诗学习应用——上 2、在菜单中添加…

电脑上的视频在电视上播放

视频右键->播放到设备->客厅电视 海信电视测试成功
最新文章