tcpping c语言

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 C语言 正文

tcpping(也常被称为 tcping)是一个网络诊断工具,它的功能类似于 ping,但使用的是 TCP 协议而不是 ICMP 协议,这对于那些防火墙可能阻止 ICMP 流量但允许 TCP 流量的网络环境非常有用。

tcpping c语言
(图片来源网络,侵删)

tcpping 的基本工作原理是:

  1. 尝试与目标主机的指定 TCP 端口建立一个 TCP 连接。
  2. 如果连接成功,说明目标端口是开放的,并记录连接建立所花费的时间(RTT - Round-Trip Time),然后立即关闭连接。
  3. 如果连接失败(目标主机无响应、端口被过滤或拒绝),则记录失败信息。
  4. 重复以上步骤,就像 ping 一样发送多个“探测包”。

核心技术点

要实现一个 tcpping,我们需要掌握以下几个 C 语言和网络编程的关键点:

  • socket(): 创建一个套接字,这是进行网络通信的端点。
  • struct sockaddr_in: 用于存储 IPv4 地址信息的结构体,包括 IP 地址和端口号。
  • inet_addr()inet_pton(): 将点分十进制的 IP 地址字符串(如 "8.8.8.8")转换为网络字节序的二进制格式。
  • htons(): 将端口号从主机字节序转换为网络字节序,网络协议栈要求端口号必须使用网络字节序。
  • connect(): 尝试与指定的 IP 地址和端口建立 TCP 连接,这是 tcpping 的核心函数。
  • gettimeofday(): 获取高精度的时间戳,用于精确计算连接建立所需的时间。
  • close(): 关闭套接字,释放资源。
  • #include <sys/socket.h>, #include <netinet/in.h>, `#include <arpa/inet.h>, #include <unistd.h>, #include <sys/time.h>: 这些是实现网络编程所必需的头文件。

完整 C 语言代码示例

下面是一个功能完整的 tcpping 实现,它支持命令行参数,并会持续发送 TCP 探测包,直到被用户中断(如 Ctrl+C)。

// tcpping.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
// 全局变量,用于控制主循环
volatile sig_atomic_t keep_running = 1;
// 信号处理函数,当用户按下 Ctrl+C 时,设置 keep_running 为 0
void handle_sigint(int sig) {
    (void)sig; // 避免未使用参数的警告
    keep_running = 0;
}
// 打印使用说明
void print_usage(const char *prog_name) {
    fprintf(stderr, "Usage: %s <host> <port>\n", prog_name);
    fprintf(stderr, "Example: %s google.com 80\n", prog_name);
}
int main(int argc, char *argv[]) {
    if (argc != 3) {
        print_usage(argv[0]);
        return 1;
    }
    const char *hostname = argv[1];
    const char *port_str = argv[2];
    int port = atoi(port_str);
    if (port <= 0 || port > 65535) {
        fprintf(stderr, "Error: Invalid port number %s\n", port_str);
        return 1;
    }
    // 注册信号处理函数
    struct sigaction sa;
    sa.sa_handler = handle_sigint;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }
    printf("TCPing %s on port %d...\n", hostname, port);
    printf("Press Ctrl+C to stop.\n");
    while (keep_running) {
        int sock = -1;
        struct sockaddr_in server_addr;
        struct timeval start_time, end_time;
        long long elapsed_us;
        // 1. 创建套接字
        sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0) {
            perror("socket");
            break; // 创建失败,退出循环
        }
        // 2. 设置服务器地址信息
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port);
        // 将主机名转换为IP地址
        if (inet_pton(AF_INET, hostname, &server_addr.sin_addr) <= 0) {
            // 如果不是有效的 IPv4 地址,尝试通过 DNS 解析
            struct hostent *host = gethostbyname(hostname);
            if (host == NULL) {
                fprintf(stderr, "Error: Unknown host %s\n", hostname);
                close(sock);
                break;
            }
            memcpy(&server_addr.sin_addr, host->h_addr, host->h_length);
        }
        // 3. 记录开始时间
        gettimeofday(&start_time, NULL);
        // 4. 尝试连接
        int connect_result = connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
        // 5. 记录结束时间
        gettimeofday(&end_time, NULL);
        // 6. 计算耗时
        elapsed_us = (end_time.tv_sec - start_time.tv_sec) * 1000000LL + (end_time.tv_usec - start_time.tv_usec);
        // 7. 输出结果
        if (connect_result == 0) {
            // 连接成功
            printf("Connected to %s:%d in %lld ms\n", hostname, port, elapsed_us / 1000);
        } else {
            // 连接失败
            printf("Failed to connect to %s:%d. Error: %s (%lld ms)\n", hostname, port, strerror(errno), elapsed_us / 1000);
        }
        // 8. 关闭套接字
        close(sock);
        // 9. 等待 1 秒,避免过于频繁的探测
        sleep(1);
    }
    printf("\nTCPing finished.\n");
    return 0;
}

代码详解

  1. 信号处理 (handle_sigint, sigaction):

    tcpping c语言
    (图片来源网络,侵删)
    • volatile sig_atomic_t keep_running = 1; 定义了一个原子变量,用于控制 while 循环。
    • handle_sigint 函数在接收到 SIGINT 信号(通常是 Ctrl+C)时被调用,它将 keep_running 设为 0,从而优雅地终止主循环。
    • 这使得程序可以被用户正常中断,而不是被强制杀死。
  2. 命令行参数解析 (main 函数开头):

    • 检查 argc 是否为 3,如果不是,打印用法并退出。
    • 使用 atoi() 将端口号字符串转换为整数,并进行简单的有效性检查。
  3. 主循环 (while (keep_running)):

    • 循环会一直执行,直到 keep_running 变为 0。
  4. 创建套接字 (socket):

    • AF_INET 指定使用 IPv4 协议。
    • SOCK_STREAM 指定使用 TCP 协议。
    • 第三个参数通常为 0,表示让系统自动选择合适的协议。
  5. 设置服务器地址 (struct sockaddr_in):

    • memset 将结构体清零,这是一个好习惯。
    • server_addr.sin_family = AF_INET; 设置地址族。
    • htons(port) 将端口号转换为网络字节序。
    • inet_pton() 是一个现代的、更安全的函数,用于将 IP 地址字符串(如 "192.168.1.1")转换为二进制格式,如果传入的是域名(如 "google.com"),inet_pton 会失败,此时我们回退到 gethostbyname 进行 DNS 解析。
  6. 记录时间 (gettimeofday):

    • connect() 调用之前之后分别获取时间戳。
    • gettimeofday 返回秒和微秒,我们手动计算总微秒数,然后除以 1000 得到毫秒,这是更直观的单位。
  7. 连接与结果处理 (connect):

    • connect() 是阻塞调用,它会一直等待直到连接建立或失败。
    • 如果返回值为 0,表示连接成功。
    • 如果返回值为 -1,表示连接失败,errno 会被设置成具体的错误码(如 ECONNREFUSED 连接被拒绝,ETIMEDOUT 连接超时),我们可以用 strerror(errno) 打印出可读的错误信息。
  8. 清理 (close, sleep):

    • close(sock) 关闭套接字,释放系统资源。
    • sleep(1) 让程序暂停 1 秒,避免对目标服务器造成过大压力,也模拟了 ping 的间隔。

如何编译和运行

  1. 保存代码: 将上面的代码保存为 tcpping.c

  2. 编译: 使用 GCC 编译器进行编译。

    • 在 Linux 或 macOS 终端中运行:
      gcc tcpping.c -o tcpping
    • 在 Windows (使用 MinGW 或 WSL) 中,命令类似。
  3. 运行:

    • 探测一个 IP 地址和端口 (探测百度网站的 80 端口):
      ./tcpping 180.101.49.12 80
    • 探测一个域名和端口 (探测 Google 的 443 端口):
      ./tcpping google.com 443
    • Ctrl+C 停止

预期输出示例:

TCPing google.com on port 443...
Press Ctrl+C to stop.
Failed to connect to google.com:443. Error: Connection timed out (0 ms)
Failed to connect to google.com:443. Error: Connection timed out (0 ms)
Connected to google.com:443 in 42 ms
Connected to google.com:443 in 38 ms
^C
TCPing finished.

(注意:第一次连接可能会失败,因为 TCP 三次握手需要时间,或者中间路由器有延迟。)


扩展功能

这个基础版本已经实现了核心功能,但还可以进一步扩展:

  • 超时设置: 使用 setsockopt()SO_RCVTIMEO / SO_SNDTIMEO 来为 connect() 调用设置超时,避免无限等待。
  • 命令行选项: 使用 getopt 库来支持更多命令行选项,
    • -c <count>: 发送指定次数后退出,而不是无限循环。
    • -i <interval>: 自定义探测间隔时间(秒)。
    • -t <timeout>: 自定义连接超时时间(毫秒)。
  • ICMP 回显: 对于无法连接的情况,可以尝试发送一个 ICMP Echo Request (真正的 ping) 作为补充信息。
  • IPv6 支持: 修改代码以支持 AF_INET6,使其能够探测 IPv6 地址。
-- 展开阅读全文 --
头像
梦织自助建站真的好用吗?
« 上一篇 04-15
支付宝回调错误如何排查与解决?
下一篇 » 04-15

相关文章

取消
微信二维码
支付宝二维码

目录[+]