使用socket+Python实现ping

news2024/11/16 16:44:09
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.coloradmin.cn/o/1643882.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

结构体介绍(2)

结构体介绍&#xff08;2&#xff09; 前言一、结构体的内存对齐之深入理解为什么存在内存对齐&#xff1f;修改默认对齐数 二、结构体传参2.1&#xff1a;该怎么传参呢&#xff1f; 三、结构体实现位段3.1什么是位段位段的内存分配位段的跨平台问题 总结 前言 根据之前讲了结…

金融行业AI大模型百项应用案例综述【大模型系列】

逐浪金融大模型的玩家&#xff0c;除了BAT、华为等高科技巨头&#xff0c;试图以技术优势充当产业链的“卖铲人”&#xff0c;更多的还是金融和类金融企业&#xff0c;包括银行、保险、互金、券商等&#xff0c;既不想被喧宾夺主&#xff0c;又不想肥水外流&#xff0c;都在押注…

基于Springboot的校园竞赛管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园竞赛管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

VS2019下使用MFC完成科技项目管理系统

背景&#xff1a; &#xff08;一&#xff09;实验目的 通过该实验&#xff0c;使学生掌握windows程序设计的基本方法。了解科技项目组织管理的主要内容和管理方面的基本常识&#xff0c;熟练应用数据库知识&#xff0c;通过处理过程对计算机软件系统工作原理的进一步理解&am…

新型直膨式光伏光热热泵/动力热管复合循环系统

太阳能光伏光热热泵&#xff08;即PVT热泵&#xff09;技术是建筑领域内实现碳中和的有效技术手段&#xff0c;该技术具有优越的热电冷联产能力。然而&#xff0c;现有的PVT热泵在良好的室外工况下能耗较高。为了解决这一问题&#xff0c;本文提出了一种新型的DX-PVT热泵/动力热…

解决网络ping不通问题

网络ping不通可能有多种原因&#xff0c;以下是一些常见的解决方法&#xff1a; 1. 检查IP地址和域名&#xff1a;确保你使用的是正确的IP地址或者域名来ping目标设备。如果IP地址或者域名错误&#xff0c;ping请求将无法到达目标设备。 2. 检查网络连接&#xff1a;首先确保…

【电源专题】拿人体的循环系统与板级电源做个比较

一般人可能会觉得电源大概是电子设备里面比较容易搞定的门类。因为,只要线路没有接错,指示灯(如果有)能亮,电源都能工作。从这个方面说,好像是很容易。但是通过多年的经验和经历的坑,发现电源其实是一个很麻烦的东西,稍微有一点不完美就会有大问题出现。 如果将人体也当…

cloudreve手动添加文件

cloudreve导入本地已有的文件&#xff0c;不需要再次上传 需要更新版本到3.1及更高 在【管理面板】-----【文件】导入 如上图&#xff1a; 选择目标用户、原始路径、目的路径&#xff0c;创建导入任务即可&#xff01;

[C++基础学习-06]----C++指针详解

前言 指针是一个存储变量地址的变量&#xff0c;可以用来访问内存中的数据。在C中&#xff0c;指针是一种非常有用的数据类型&#xff0c;可以帮助我们在程序中对内存进行操作和管理。 正文 01-指针简介 指针的基本概念如下&#xff1a; 声明指针&#xff1a;使用“*”符…

AI+客服行业落地应用

一、客服行业变迁 1.传统客服时代 &#xff08;1&#xff09;客服工作重复性高&#xff0c;技术含量低 &#xff08;2&#xff09;呼出效率低&#xff0c;客服水平参差不齐 &#xff08;3&#xff09;管理难度高&#xff0c;情绪不稳定 &#xff08;4&#xff09;服务质量…

【系统架构师】-选择题(十三)

1、在某企业的营销管理系统设计阶段&#xff0c;属性"员工"在考勤管理子系统中被称为"员工"&#xff0c;而在档案管理子系统中被称为"职工"&#xff0c;这类冲突称为&#xff08; 命名冲突&#xff09;。 同一个实体在同系统中存在不同的命名&am…

2024“天一永安杯“宁波第七届网络安全大赛极安云科战队部分WP

“天一永安杯”2024 宁波第七届网络安全大赛暨第九届大学生网络技术与信息安全大赛 大赛竞赛形式 一、线上初赛 参赛人员&#xff1a;各单位自行选拔3人&#xff08;设队长1名&#xff09;组成团队&#xff0c;不足3人不允许参赛。 竞赛时间&#xff1a;8&#xff1a;30-12&…

15 C语言常用函数

C语言常用函数 本次笔记参考哔站尚硅谷宋红康老师的C语言教程。 C语言是一种广泛应用的编程语言&#xff0c;它提供了一系列的标准库函数&#xff0c;使得程序员能够更高效地编写程序。函数是C语言编程中的基础&#xff0c;通过它们&#xff0c;程序员可以构建出功能丰富的应用…

牛客NC382 切割木头【中等 二分超找 Java/Go/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/707d98cee255448c838c76918a702be0 核心 二分查找Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可…

泰克示波器电流探头如何抓浪涌电流波形?

泰克示波器是一种常见的电子测量仪器&#xff0c;广泛应用于电子工程、通信工程、医疗设备等领域。它的主要功能是实时显示电信号的波形&#xff0c;从而帮助工程师和技术人员分析和调试电路。而在一些特定的应用场景中&#xff0c;例如电源、电机、电器设备等&#xff0c;我们…

神经网络之防止过拟合

今天我们来看一下神经网络中防止模型过拟合的方法 在机器学习和深度学习中&#xff0c;过拟合是指模型在训练数据上表现得非常好&#xff0c;但在新的、未见过的数据上表现不佳的现象。这是因为模型过于复杂&#xff0c;以至于它学习了训练数据中的噪声和细节&#xff0c;而不…

基于EBAZ4205矿板的图像处理:12图像二值化(阈值可调)

基于EBAZ4205矿板的图像处理&#xff1a;12图像二值化(阈值可调) 先看效果 板卡拿回寝室了&#xff0c;明天晚上再补充实际运行效果 我的项目是可以通过按键调整二值化的阈值的&#xff0c;key1为阈值加1&#xff0c;key2为阈值减1&#xff0c;key3为阈值加10&#xff0c;ke…

vue初始化项目

打开终端输入vue create project-name 选择Manually select features回车&#xff0c;继续选择如下&#xff1a; 如果要使用pina就可以不选vuex&#xff0c;回车&#xff0c;选择如下&#xff1a; 按下图选即可

SX1301 1MHz、2A升压电流模式PWM转换器芯片IC

一般说明 S1301是直流-直流之间的a电流。安装Wμ电路内置0.25Ω功率MOSFET&#xff0c;使此稳压器高功率效率。内部补偿网络还可最大限度地减少多达6个外部元件数量。误差放大器的同相输入端接0.6V精密基准电压&#xff0c;内部具有软启动功能&#xff0c;可以减小浪涌电…

Hikyuu-SYS-趋势双均线交易策略实现

本篇中&#xff0c;我们将通过技术分析流派中经典的“趋势双均线策略”&#xff0c;向大家展现如何 Hikyuu 来测试自己的想法&#xff0c;并最终将它转化为策略&#xff01; 准备工作 下面的代码在 Jupyter Lab 中执行&#xff0c;和直接使用 .py 文件执行的区别主要在于 matp…