17、Python网络编程:从基础套接字到Ping扫描应用

Python网络编程:从基础套接字到Ping扫描应用

1. 基础网络编程:使用套接字实现简单的客户端 - 服务器连接

在网络编程中,理解如何使用套接字(sockets)来实现客户端和服务器之间的通信是非常重要的。为了介绍Python提供的套接字API,我们将创建一个简单的网络服务器和客户端。

1.1 选择IP地址和端口

我们将使用本地回环IP地址 127.0.0.1 ,这个地址在几乎所有系统中都是相同的,并且发送到该地址的任何消息都不会到达外部网络,而是自动返回到本地主机。端口号范围在0到65,535之间,我们应避免使用低于1024的端口,因为这些端口通常分配给标准网络服务。在这个例子中,我们选择使用端口5555,因为它容易记忆。

可以将IP地址想象成邮局的街道地址,而端口则是邮局内的特定邮箱。

1.2 服务器端代码(server.py)

服务器的主要目标是:
1. 设置一个简单的监听套接字。
2. 等待连接请求。
3. 在端口5555上接受连接。
4. 成功连接后向客户端发送消息。

import socket
# Standard Library Socket Module
# Create Socket
myServerSocket = socket.socket()
# Get my local host address
localHost = socket.gethostname()
# Specify a local Port to accept connections on
localPort = 5555
# Bind myServerSocket to localHost and the specified Port
# Note the bind call requires one parameter, but that
# parameter is a tuple (notice the parenthesis usage)
myServerSocket.bind((localHost, localPort))
# Begin Listening for connections
myServerSocket.listen(1)
# Wait for a connection request
# Note this is a synchronous Call
# meaning the program will halt until
# a connection is received.
# Once a connection is received
# we will accept the connection and obtain the
# ipAddress of the connector
print('Python-Forensics .... Waiting for Connection Request')
conn, clientInfo = myServerSocket.accept()
# Print a message to indicate we have received a connection
print('Connection Received From:', clientInfo)
# Send a message to connector using the connection object 'conn'
# that was returned from the myServerSocket.accept() call
# Include the client IP Address and Port used in the response
conn.send('Connection Confirmed:' + 'IP:' + clientInfo[0] + 'Port:' + str(clientInfo[1]))
1.3 客户端代码(client.py)

客户端的主要目标是:
1. 设置一个客户端套接字。
2. 尝试连接到端口5555上的服务器。
3. 等待回复。
4. 打印从服务器收到的消息。

import socket
# Standard Library Socket Module
MAX_BUFFER = 1024
# Set the maximum size to receive
# Create a Socket
myClientSocket = socket.socket()
# Get my local host address
localHost = socket.gethostname()
# Specify a local Port to attempt a connection
localPort = 5555
# Attempt a connection to my localHost and localPort
myClientSocket.connect((localHost, localPort))
# Wait for a reply
# This is a synchronous call, meaning
# that the program will halt until a response is received
# or the program is terminated
msg = myClientSocket.recv(MAX_BUFFER)
print(msg)
# Close the Socket, this will terminate the connection
myClientSocket.close()
1.4 程序执行

我们可以创建两个终端窗口,先运行 server.py ,再运行 client.py 。客户端会从源端口(如59,714,由套接字服务选择)与服务器的目标端口5555进行通信。

下面是这个简单客户端 - 服务器通信的流程图:

graph LR
    A[启动服务器] --> B[等待连接请求]
    C[启动客户端] --> D[尝试连接服务器]
    D --> B
    B --> E[接受连接]
    E --> F[服务器发送确认消息]
    F --> G[客户端接收消息]
    G --> H[客户端关闭连接]
2. 网络调查基础:使用Ping扫描发现网络主机

在网络调查中,发现网络上的所有主机(端点)是关键要素之一。这可以通过向网络上的每个可能的IP地址发送Ping(使用Internet控制消息协议,即ICMP)来实现。

2.1 Ping扫描的原理

当我们向一个IP地址发送Ping请求时,如果该地址有响应,我们可以得到两个重要信息:一是该主机存在且可响应;二是响应返回所需的时间。但需要注意的是,许多现代防火墙会阻止ICMP消息,因为黑客可能会利用这些消息对网络进行侦察活动,而且现代操作系统默认也不会响应ICMP。不过,在网络内部,Ping扫描仍然是定位和检测端点的有效方法。

2.2 相关模块介绍

为了实现Ping扫描应用,我们将使用两个特殊模块:
- wxPython :用于构建Ping扫描应用的简单图形用户界面(GUI)。它提供了跨平台的GUI功能,支持Windows、Linux和Mac系统。可以访问 http://www.wxPython.org/ 项目页面获取更多信息并安装适合自己配置的版本。
- Ping.py :一个完全用Python编写的第三方模块,用于处理ICMP操作的细节。该模块可以从 http://www.g-loaded.eu/2009/10/30/Python-ping/ 下载。

以下是Ping.py模块的代码:

#!/usr/bin/env Python
"""
A pure Python ping implementation using raw socket.
Note that ICMP messages can only be sent from processes running as root.
Derived from ping.c distributed in Linux's netkit. That code is
copyright (c) 1989 by The Regents of the University of California.
That code is in turn derived from code written by Mike Muuss of the
US Army Ballistic Research Laboratory in December, 1983 and
placed in the public domain. They have my thanks.
Bugs are naturally mine. I'd be glad to hear about them. There are
certainly word - size dependencies here.
Copyright (c) Matthew Dixon Cowles, <http://www.visi.com/mdc/>.
Distributable under the terms of the GNU General Public License
version 2. Provided with no warranties of any sort.
Original Version from Matthew Dixon Cowles:
-> ftp://ftp.visi.com/users/mdc/ping.py
Rewrite by Jens Diemer:
-> http://www.Python-forum.de/post-69122.html#69122
Rewrite by George Notaras:
-> http://www.g-loaded.eu/2009/10/30/Python-ping/
Revision history

November 8, 2009
-------------
Improved compatibility with GNU/Linux systems.
Fixes by:
* George Notaras -- http://www.g-loaded.eu
Reported by:
* Chris Hallman -- http://cdhallman.blogspot.com
Changes in this release:
- Re-use time.time() instead of time.clock(). The 2007 implementation
worked only under Microsoft Windows. Failed on GNU/Linux.
time.clock() behaves differently under the two OSes[1].
[1] http://docs.Python.org/library/time.html#time.clock
May 30, 2007
----------
little rewrite by Jens Diemer:
- change socket asterisk import to a normal import
- replace time.time() with time.clock()
- delete "return None" (or change to "return" only)
- in checksum() rename "str" to "source_string"
November 22, 1997
-------------
Initial hack. Doesn't do much, but rather than try to guess
what features I (or others) will want in the future, I've only
put in what I need now.
December 16, 1997
--------------
For some reason, the checksum bytes are in the wrong order when
this is run under Solaris 2.X for SPARC but it works right under
Linux x86. Since I don't know just what's wrong, I'll swap the
bytes always and then do an htons().
December 4, 2000
-------------
Changed the struct.pack() calls to pack the checksum and ID as
unsigned. My thanks to Jerome Poincheval for the fix.
Last commit info:

$LastChangedDate: $
$Rev: $
$Author: $
"""
import os, sys, socket, struct, select, time
# From /usr/include/linux/icmp.h; your mileage may vary.
ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris.
def checksum(source_string):
    """
    I'm not too confident that this is right but testing seems
    to suggest that it gives the same answers as in_cksum in ping.c
    """
    sum = 0
    countTo = (len(source_string)/2)*2
    count = 0
    while count<countTo:
        thisVal = ord(source_string[count + 1])*256 + ord(source_string[count])
        sum = sum + thisVal
        sum = sum & 0xffffffff # Necessary?
        count = count + 2
    if countTo<len(source_string):
        sum = sum + ord(source_string[len(source_string) - 1])
        sum = sum & 0xffffffff # Necessary?
    sum = (sum >> 16) + (sum & 0xffff)
    sum = sum + (sum >> 16)
    answer = ~sum
    answer = answer & 0xffff
    # Swap bytes. Bugger me if I know why.
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer
def receive_one_ping(my_socket, ID, timeout):
    """
    receive the ping from the socket.
    """
    timeLeft = timeout
    while True:
        startedSelect = time.time()
        whatReady = select.select([my_socket], [], [], timeLeft)
        howLongInSelect = (time.time() - startedSelect)
        if whatReady[0] == []: # Timeout
            return
        timeReceived = time.time()
        recPacket, addr = my_socket.recvfrom(1024)
        icmpHeader = recPacket[20:28]
        type, code, checksum, packetID, sequence = struct.unpack(
            "bbHHh", icmpHeader
        )
        if packetID == ID:
            bytesInDouble = struct.calcsize("d")
            timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0]
            return timeReceived - timeSent
        timeLeft = timeLeft - howLongInSelect
        if timeLeft <= 0:
            return
def send_one_ping(my_socket, dest_addr, ID):
    """
    Send one ping to the given >dest_addr<.
    """
    dest_addr = socket.gethostbyname(dest_addr)
    # Header is type (8), code (8), checksum (16), id (16), sequence (16)
    my_checksum = 0
    # Make a dummy header with a 0 checksum.
    header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1)
    bytesInDouble = struct.calcsize("d")
    data = (192 - bytesInDouble) * "Q"
    data = struct.pack("d", time.time()) + data
    # Calculate the checksum on the data and the dummy header.
    my_checksum = checksum(header + data)
    # Now that we have the right checksum, we put that in. It's just easier
    # to make up a new header than to stuff it into the dummy.
    header = struct.pack(
        "bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1
    )
    packet = header + data
    my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1
def do_one(dest_addr, timeout):
    """
    Returns either the delay (in seconds) or none on timeout.
    """
    icmp = socket.getprotobyname("icmp")
    try:
        my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
    except socket.error, (errno, msg):
        if errno == 1:
            # Operation not permitted
            msg = msg + (
                " - Note that ICMP messages can only be sent from processes"
                " running as root."
            )
            raise socket.error(msg)
        raise # raise the original error
    my_ID = os.getpid() & 0xFFFF
    send_one_ping(my_socket, dest_addr, my_ID)
    delay = receive_one_ping(my_socket, my_ID, timeout)
    my_socket.close()
    return delay
def verbose_ping(dest_addr, timeout = 2, count = 4):
    """
    Send >count< ping to >dest_addr< with the given >timeout< and
    display the result.
    """
    for i in xrange(count):
        print "ping %s. . ." % dest_addr,
        try:
            delay = do_one(dest_addr, timeout)
        except socket.gaierror, e:
            print "failed. (socket error:'%s')" % e[1]
            break
        if delay == None:
            print "failed. (timeout within %ssec.)" % timeout
        else:
            delay = delay * 1000
            print "get ping in %0.4fms" % delay
    print
if __name__ == '__main__':
    verbose_ping("heise.de")
    verbose_ping("google.com")
    verbose_ping("a-test-url-taht-is-not-available.com")
    verbose_ping("192.168.1.1")

通过以上内容,我们了解了如何使用Python的套接字实现简单的客户端 - 服务器通信,以及如何利用Ping扫描来发现网络上的主机。在下半部分,我们将详细介绍如何使用wxPython构建Ping扫描应用的GUI界面。

3. 构建Ping扫描应用的GUI界面

为了让Ping扫描应用更加易用,我们将使用wxPython构建一个简单的图形用户界面(GUI)。这个界面将包含两个按钮(Scan和Exit)以及几个旋转控件,用于指定基本IP地址和本地主机范围。

3.1 代码实现(guiPing.py)
#
# Python Ping Sweep GUI Application
#
import wxversion
# Specify the proper version of wxPython
wxversion.select("2.8")
# Import the necessary modules
import wx
# Import the GUI module wx
import sys
# Import the standard library module sys
import ping
# Import the ICMP Ping Module
import socket
# Import the standard library module socket
from time import gmtime, strftime # import time functions

#
# Event Handler for the pingScan Button Press
# This is executed each time the Scan Button is pressed on the GUI
#
def pingScan(event):
    # Since the user specifies a range of Hosts to Scan, I need to verify
    # that the startHost value is <= endHost value before scanning
    # this would indicate a valid range
    # If not I need to communicate the error with the user
    if hostEnd.GetValue() < hostStart.GetValue():
        # This is an improper setting
        # Notify the user using a wx.MessageDialog Box
        dlg = wx.MessageDialog(mainWin, "Invalid Local Host Selection", "Confirm", wx.OK | wx.ICON_EXCLAMATION)
        result = dlg.ShowModal()
        dlg.Destroy()
        return
    # If we have a valid range update the Status Bar
    mainWin.StatusBar.SetStatusText('Executing Ping Sweep .... Please Wait')
    # Record the Start Time and Update the results window
    utcStart = gmtime()
    utc = strftime("%a, %d %b %Y %X +0000", utcStart)
    results.AppendText("\n\nPing Sweep Started: " + utc + "\n\n")
    # Similar to the example script at the beginning of the chapter
    # I need to build the base IP Address String
    # Extract data from the ip Range and host name user selections
    # Build a Python List of IP Addresses to Sweep
    baseIP = str(ipaRange.GetValue()) + '.' + str(ipbRange.GetValue()) + '.' + str(ipcRange.GetValue()) + '.'
    ipRange = []
    for i in range(hostStart.GetValue(), (hostEnd.GetValue() + 1)):
        ipRange.append(baseIP + str(i))
    # For each of the IP Addresses in the ipRange List, Attempt an PING
    for ipAddress in ipRange:
        try:
            # Report the IP Address to the Window Status Bar
            # Prior to the attempt
            mainWin.StatusBar.SetStatusText('Pinging IP: ' + ipAddress)
            # Perform the Ping
            delay = ping.do_one(ipAddress, timeout=2)
            # Display the IP Address in the Main Window
            results.AppendText(ipAddress + '\t')
            if delay != None:
                # If Successful (i.e. no timeout) display
                # the result and response time
                results.AppendText('Response Success')
                results.AppendText('Response Time: ' + str(delay) + ' Seconds')
                results.AppendText("\n")
            else:
                # If delay == None, then the request timed out
                # Report the Response Timeout
                results.AppendText('Response Timeout')
                results.AppendText("\n")
        except socket.error, e:
            # If any socket Errors occur Report the offending IP
            # along with any error information provided by the socket
            results.AppendText(ipAddress)
            results.AppendText('Response Failed:')
            results.AppendText(e.message)
            results.AppendText("\n")
    # Once all ipAddresses are processed
    # Record and display the ending time of the sweep
    utcEnd = gmtime()
    utc = strftime("%a, %d %b %Y %X +0000", utcEnd)
    results.AppendText("\nPing Sweep Ended: " + utc + "\n\n")
    # Clear the Status Bar
    mainWin.StatusBar.SetStatusText('')
    return
# End Scan Event Handler =========================

#
# Program Exit Event Handler
# This is executed when the user presses the exit button
# The program is terminated using the sys.exit() method
#
def programExit(event):
    sys.exit()
# End Program Exit Event Handler =================

#
# Setup the Application Windows =========================
#
# This section of code sets up the GUI environment
#
# Instantiate a wx.App() object
app = wx.App()
# define the main window including the size and title
mainWin = wx.Frame(None, title="Simple Ping (ICMP) Sweeper 1.0", size=(1000, 600))
# define the action panel, this is the area where the buttons and spinners
# are located
panelAction = wx.Panel(mainWin)
# define action buttons
# I'm creating two buttons, one for Scan and one for Exit
# Notice that each button contains the name of the function that will
# handle the button press event -- pingScan and ProgramExit respectively
scanButton = wx.Button(panelAction, label='Scan')
scanButton.Bind(wx.EVT_BUTTON, pingScan)
exitButton = wx.Button(panelAction, label='Exit')
exitButton.Bind(wx.EVT_BUTTON, programExit)
# define a Text Area where I can display results
results = wx.TextCtrl(panelAction, style=wx.TE_MULTILINE | wx.HSCROLL)
# Base Network for Class C IP Addresses have 3 components
# For class C addresses, the first 3 octets (24 bits) define the network
# e.g., 127.0.0
# the last octet (8 bits) defines the host i.e., 0 - 255
# Thus I setup 3 spin controls one for each of the 3 network octets
# I also set the default value to 127.0.0 for convenience
ipaRange = wx.SpinCtrl(panelAction, -1, '')
ipaRange.SetRange(0, 255)
ipaRange.SetValue(127)
ipbRange = wx.SpinCtrl(panelAction, -1, '')
ipbRange.SetRange(0, 255)
ipbRange.SetValue(0)
ipcRange = wx.SpinCtrl(panelAction, -1, '')
ipcRange.SetRange(0, 255)
ipcRange.SetValue(0)
# Also, I'm adding a label for the user
ipLabel = wx.StaticText(panelAction, label="IP Base: ")
# Next, I want to provide the user with the ability to set the host range
# they wish to scan. Range is 0 - 255
hostStart = wx.SpinCtrl(panelAction, -1, '')
hostStart.SetRange(0, 255)
hostStart.SetValue(1)
hostEnd = wx.SpinCtrl(panelAction, -1, '')
hostEnd.SetRange(0, 255)
hostEnd.SetValue(10)
HostStartLabel = wx.StaticText(panelAction, label="Host Start: ")
HostEndLabel = wx.StaticText(panelAction, label="Host End: ")
# Now I create BoxSizer to automatically align the different components
# neatly within the panel
# First, I create a horizontal Box
# I'm adding the buttons, ip Range and Host Spin Controls
actionBox = wx.BoxSizer()
actionBox.Add(scanButton, proportion=1, flag=wx.LEFT, border=5)
actionBox.Add(exitButton, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(ipLabel, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(ipaRange, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(ipbRange, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(ipcRange, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(HostStartLabel, proportion=0, flag=wx.LEFT | wx.CENTER, border=5)
actionBox.Add(hostStart, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(HostEndLabel, proportion=0, flag=wx.LEFT | wx.CENTER, border=5)
actionBox.Add(hostEnd, proportion=0, flag=wx.LEFT, border=5)
# Next I create a Vertical Box that I place the Horizontal Box Inside
# Along with the results text area
vertBox = wx.BoxSizer(wx.VERTICAL)
vertBox.Add(actionBox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
vertBox.Add(results, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, border=5)
# I'm adding a status bar to the main windows to display status messages
mainWin.CreateStatusBar()
# Finally, I use the SetSizer function to automatically size the windows
# based on the definitions above
panelAction.SetSizer(vertBox)
mainWin.Show()
app.MainLoop()
3.2 代码说明
  • 事件处理函数
  • pingScan(event) :当用户点击“Scan”按钮时执行。首先检查用户指定的主机范围是否有效,如果无效则弹出消息框通知用户;如果有效,则记录扫描开始时间,构建要扫描的IP地址列表,并对每个IP地址进行Ping扫描,记录扫描结果和响应时间,最后记录扫描结束时间。
  • programExit(event) :当用户点击“Exit”按钮时执行,使用 sys.exit() 方法终止程序。

  • GUI布局

  • 使用 wx.App() 创建应用程序对象。
  • 创建主窗口 mainWin ,并在其中添加面板 panelAction
  • 在面板中添加“Scan”和“Exit”按钮,并绑定相应的事件处理函数。
  • 使用 wx.SpinCtrl 创建旋转控件,用于用户指定IP地址的各个部分和主机范围。
  • 使用 wx.TextCtrl 创建文本区域 results ,用于显示扫描结果。
  • 使用 wx.BoxSizer 进行布局管理,使界面元素排列整齐。
  • 为主窗口添加状态栏,用于显示扫描状态信息。

以下是Ping扫描GUI应用的操作步骤:
1. 确保已经安装了wxPython和Ping.py模块。
2. 运行 guiPing.py 脚本,需要以管理员权限运行(因为发送ICMP消息需要root权限)。
3. 在界面中设置IP地址的基本部分和要扫描的主机范围。
4. 点击“Scan”按钮开始Ping扫描,扫描结果将显示在文本区域中。
5. 点击“Exit”按钮退出程序。

下面是Ping扫描GUI应用的流程图:

graph LR
    A[启动程序] --> B[显示GUI界面]
    B --> C{点击Scan按钮}
    C -- 是 --> D{主机范围是否有效}
    D -- 是 --> E[记录开始时间]
    E --> F[构建IP地址列表]
    F --> G[对每个IP进行Ping扫描]
    G --> H{是否有响应}
    H -- 是 --> I[记录响应时间和结果]
    H -- 否 --> J[记录超时结果]
    I --> K{是否扫描完所有IP}
    J --> K
    K -- 否 --> G
    K -- 是 --> L[记录结束时间]
    L --> M[显示扫描结果]
    D -- 否 --> N[弹出错误消息框]
    C -- 否 --> O{点击Exit按钮}
    O -- 是 --> P[退出程序]
    O -- 否 --> C

总结

通过本文,我们学习了如何使用Python的套接字API实现简单的客户端 - 服务器通信,以及如何利用Ping扫描发现网络上的主机。同时,我们还使用wxPython构建了一个简单的GUI界面,使Ping扫描应用更加易用。这些知识对于网络编程和网络调查都非常有帮助。在实际应用中,我们可以根据需要对代码进行扩展和优化,以满足不同的需求。例如,可以增加更多的错误处理机制,提高程序的稳定性;或者添加多线程功能,提高Ping扫描的效率。

深度学习作为人工智能的关键分支,依托多层神经网络架构对高维数据进行模式识别与函数逼近,广泛应用于连续变量预测任务。在Python编程环境中,得益于TensorFlow、PyTorch等框架的成熟生态,研究者能够高效构建面向回归分析的神经网络模型。本资源库聚焦于通过循环神经网络及其优化变体解决时序预测问题,特别针对传统RNN在长程依赖建模中的梯度异常现象,引入具有门控机制的长短期记忆网络(LSTM)以增强序列建模能力。 实践案例涵盖从数据预处理到模型评估的全流程:首先对原始时序数据进行标准化处理与滑动窗口分割,随后构建包含嵌入层、双向LSTM层及全连接层的网络结构。在模型训练阶段,采用自适应矩估计优化器配合早停策略,通过损失函数曲线监测过拟合现象。性能评估不仅关注均方根误差等量化指标,还通过预测值与真实值的轨迹可视化进行定性分析。 资源包内部分为三个核心模块:其一是经过清洗的金融时序数据集,包含标准化后的股价波动记录;其二是模块化编程实现的模型构建、训练与验证流程;其三是基于Matplotlib实现的动态结果展示系统。所有代码均遵循面向对象设计原则,提供完整的类型注解与异常处理机制。 该实践项目揭示了深度神经网络在非线性回归任务中的优势:通过多层非线性变换,模型能够捕获数据中的高阶相互作用,而Dropout层与正则化技术的运用则保障了泛化能力。值得注意的是,当处理高频时序数据时,需特别注意序列平稳性检验与季节性分解等预处理步骤,这对预测精度具有决定性影响。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值