用Go语言重写Linux系统命令 -- nc简化版

用Go语言重写Linux系统命令 – nc简化版

1. 引言

netcat,简称 nc,被誉为网络工具中的“瑞士军刀”,是网络调试与分析的利器。它的功能十分强大,然而平时我们经常使用的就是他的连通性测试功能,但是nc是被设计用来测试TCPUDP的,对于HTTP支持不是太好,于是,我决定用 Go 语言重新实现一个简化版的 nc,专注于联通性测试功能,并扩展支持测试HTTPPing的连通性测试。通过这个项目,你不仅能深入学习网络编程,还能体验用 Go 语言开发系统工具的乐趣!

小目标:让我们的工具小而美,轻松搞定网络连通性调试!

2. 需求分析

在开始编写代码之前,我们需要明确工具的功能和设计目标:

  • 核心功能

    1. TCP 测试:检查指定 IP 和端口是否可建立连接,支持数据发送与接收。
    2. UDP 测试:发送 UDP 数据包并接收响应。
    3. HTTP 测试:测试指定 URL 是否可访问,支持自定义请求方法(如 GET/POST)。
    4. Ping 测试:检查目标 IP 是否可达,并测量响应时间。
  • 用户体验设计

    • 使用命令行参数指定测试类型和选项(如目标 IP、端口、超时)。
    • 错误提示清晰直观,方便排查问题。
  • 性能目标:快速响应,保持简洁。

3. 项目初始化

3.1 安装开发环境

确保已安装 Go 编译器(推荐版本 ≥ 1.20)。执行以下命令检查 Go 是否可用:

go version

3.2 初始化项目

创建项目目录并初始化 Go 模块:

mkdir gonc
cd gonc
go mod init gonc

项目结构:

gonc/
├── main.go        # 入口文件
├── tcp.go         # TCP 测试模块
├── udp.go         # UDP 测试模块
├── http.go        # HTTP 测试模块
├── ping.go        # Ping 测试模块
└── go.mod         # 依赖管理文件

4. 模块实现

4.1 实现 TCP 测试功能

tcp.go 中编写代码,建立 TCP 连接,发送和接收数据:

package myncimport ("fmt""log""net""time"
)// TestTCP 测试TCP端口是否连通,并支持发送和接收数据
func FireTCP(ip string, port, timeout int, data string) error {address := fmt.Sprintf("%s:%d", ip, port)// 设置连接超时时间conn, err := net.DialTimeout("tcp", address, time.Duration(timeout)*time.Second)if err != nil {log.Printf("Failed to connect to %s: %v\n", address, err)return err}defer conn.Close()fmt.Printf("Successfully connected to %s\n", address)// 发送数据if data != "" {fmt.Printf("Sending data: %s\n", data)_, err = conn.Write([]byte(data))if err != nil {log.Printf("Failed to send data: %v\n", err)} else {fmt.Println("Data sent successfully")}}// 接收响应数据buffer := make([]byte, 1024)conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second))// 读取响应数据// 如果超时则提示用户, 服务器没有发送数据n, err := conn.Read(buffer)if err != nil {if netErr, ok := err.(net.Error); ok && netErr.Timeout() {fmt.Println("No response from server")} else {log.Printf("Failed to read response: %v\n", err)}} else {fmt.Printf("Received: %s\n", string(buffer[:n]))}return err
}

4.2 实现 UDP 测试功能

udp.go 中实现 UDP 数据包的发送与接收:

package myncimport ("fmt""log""net""time"
)// TestUDP 测试UDP端口是否连通,并支持发送和接收数据
func FireUDP(ip string, port, timeout int, data string) error {address := fmt.Sprintf("%s:%d", ip, port)conn, err := net.Dial("udp", address)if err != nil {log.Printf("Failed to connect to %s: %v\n", address, err)return err}defer conn.Close()fmt.Printf("Successfully connected to %s\n", address)// 设置写超时conn.SetWriteDeadline(time.Now().Add(time.Duration(timeout) * time.Second))_, err = conn.Write([]byte(data))if err != nil {log.Printf("Failed to send data: %v\n", err)return err}fmt.Println("Data sent successfully")// 设置读超时conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second))buffer := make([]byte, 1024)n, err := conn.Read(buffer)if err != nil {log.Printf("No response received: %v\n", err)} else {fmt.Printf("Received: %s\n", string(buffer[:n]))}return err
}

4.3 实现 HTTP 测试功能

http.go 中构建 HTTP 请求:

package myncimport ("fmt""io""log""net/http""time"
)// TestHTTP 测试HTTP/HTTPS连接
func FireHTTP(url, method string, headers map[string]string, timeout int) error {client := &http.Client{Timeout: time.Duration(timeout) * time.Second,}// 创建HTTP请求req, err := http.NewRequest(method, url, nil)if err != nil {log.Printf("Failed to create HTTP request: %v\n", err)return err}// 设置请求头for key, value := range headers {req.Header.Set(key, value)}// 发送HTTP请求resp, err := client.Do(req)if err != nil {log.Printf("HTTP request failed: %v\n", err)return err}defer resp.Body.Close()// 读取响应内容body, err := io.ReadAll(resp.Body)if err != nil {log.Printf("Failed to read response body: %v\n", err)return err}fmt.Printf("HTTP Status: %s\n", resp.Status)fmt.Printf("Response Body:\n%s\n", string(body))return err
}

4.4 实现 Ping 测试功能

ping.go 中:


import ("fmt""log""net""os""time"
)const icmpEchoRequest = 8// Ping 测试目标IP是否可达
func Ping(ip string, timeout int) error {conn, err := net.Dial("ip4:icmp", ip)if err != nil {log.Printf("Failed to connect to %s: %v\n", ip, err)return err}defer conn.Close()// 构造ICMP请求包id := os.Getpid() & 0xffffseq := 1msg := []byte{icmpEchoRequest, 0, 0, 0, // ICMP Type, Code, and Checksum placeholderbyte(id >> 8), byte(id & 0xff), // Identifierbyte(seq >> 8), byte(seq & 0xff), // Sequence number}checksum := calculateChecksum(msg)msg[2] = byte(checksum >> 8)msg[3] = byte(checksum & 0xff)// 设置写超时conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))start := time.Now()_, err = conn.Write(msg)if err != nil {log.Printf("Failed to send ICMP request: %v\n", err)return err}// 接收ICMP响应buffer := make([]byte, 1024)_, err = conn.Read(buffer)if err != nil {log.Printf("Failed to receive ICMP response: %v\n", err)return err}elapsed := time.Since(start)fmt.Printf("Ping %s successful! Time=%v\n", ip, elapsed)return err
}// calculateChecksum 计算ICMP校验和
func calculateChecksum(data []byte) uint16 {var sum intfor i := 0; i < len(data)-1; i += 2 {sum += int(data[i])<<8 | int(data[i+1])}if len(data)%2 == 1 {sum += int(data[len(data)-1]) << 8}for (sum >> 16) > 0 {sum = (sum >> 16) + (sum & 0xffff)}return uint16(^sum)
}

4.5 命令行解析与功能整合

main.go 中,将上述模块整合,并解析命令行参数:

package mainimport ("flag""fmt""net/url""os""strconv""strings""mync"
)func main() {// 定义命令行参数var method, data stringvar httpHeaders httpHeaderMapvar timeout intflag.StringVar(&method, "method", "GET", "HTTP method to use (default: GET)")flag.StringVar(&data, "data", "", "Data to send (for TCP or UDP)")flag.Var(&httpHeaders, "header", "Custom HTTP header (key=value pairs, can use multiple times)")flag.IntVar(&timeout, "timeout", 5, "Timeout in seconds (default: 5)")// 自定义 Usageflag.Usage = func() {fmt.Fprintf(os.Stderr, "Usage: %s <protocol>://<target> [options]\n\n", os.Args[0])fmt.Fprintf(os.Stderr, "options:\n")fmt.Fprintf(os.Stderr, "  -method <method>     HTTP method to use (default: GET)\n")fmt.Fprintf(os.Stderr, "  -data <data>         Data to send (for TCP or UDP)\n")fmt.Fprintf(os.Stderr, "  -header <header>   Custom HTTP header (key=value pairs, can use multiple times)\n")fmt.Fprintf(os.Stderr, "  -timeout <seconds>   Timeout in seconds (default: 5)\n")fmt.Fprintf(os.Stderr, "Examples:\n")fmt.Fprintf(os.Stderr, "  Test TCP connection:\n")fmt.Fprintf(os.Stderr, "    %s tcp://192.168.1.1:80 -data \"Hello, TCP!\" -timeout 10\n", os.Args[0])fmt.Fprintf(os.Stderr, "  Test UDP connection:\n")fmt.Fprintf(os.Stderr, "    %s udp://192.168.1.1:53 -data \"Hello, UDP!\" -timeout 5\n", os.Args[0])fmt.Fprintf(os.Stderr, "  Test HTTP connection:\n")fmt.Fprintf(os.Stderr, "    %s http://example.com -method GET -timeout 5\n", os.Args[0])fmt.Fprintf(os.Stderr, "  Ping a target:\n")fmt.Fprintf(os.Stderr, "    %s ping://192.168.1.1 -timeout 3\n", os.Args[0])}// 解析 flag 参数flag.Parse()// 必须至少有一个非 flag 参数args := flag.Args()if len(args) < 1 {fmt.Println("Error: Missing protocol and target. Example usage: tcp://192.168.1.1:80")flag.Usage()os.Exit(1)}// 解析第一个参数为协议和目标targetURL := args[0]parsedURL, err := url.Parse(targetURL)if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" {fmt.Printf("Error: Invalid protocol or target format: %s\n", targetURL)flag.Usage()os.Exit(1)}protocol := parsedURL.Schemetarget := parsedURL.Host// 根据协议执行对应的功能switch protocol {case "tcp":ip, port, err := parseTarget(target)if err != nil {fmt.Println("Error:", err)os.Exit(1)}if err := mync.FireTCP(ip, port, timeout, data); err != nil {fmt.Println("TCP Test Error:", err)}case "udp":ip, port, err := parseTarget(target)if err != nil {fmt.Println("Error:", err)os.Exit(1)}if err := mync.FireUDP(ip, port, timeout, data); err != nil {fmt.Println("UDP Test Error:", err)}case "http", "https":headerMap := httpHeaders.ToMap()if err := mync.FireHTTP(targetURL, method, headerMap, timeout); err != nil {fmt.Println("HTTP Test Error:", err)}case "ping":if err := mync.Ping(target, timeout); err != nil {fmt.Println("Ping Test Error:", err)}default:fmt.Printf("Error: Unsupported protocol: %s\n", protocol)flag.Usage()os.Exit(1)}
}// parseTarget 解析 <ip>:<port> 格式
func parseTarget(target string) (string, int, error) {parts := strings.Split(target, ":")if len(parts) != 2 {return "", 0, fmt.Errorf("invalid target format: %s, expected format <ip>:<port>", target)}ip := parts[0]port, err := strconv.Atoi(parts[1])if err != nil {return "", 0, fmt.Errorf("invalid port number: %s", parts[1])}return ip, port, nil
}// httpHeaderMap 用于保存用户指定的多个 -header 参数
type httpHeaderMap []stringfunc (h *httpHeaderMap) String() string {return strings.Join(*h, ", ")
}func (h *httpHeaderMap) Set(value string) error {*h = append(*h, value)return nil
}// ToMap 将 httpHeaderMap 转换为 map
func (h *httpHeaderMap) ToMap() map[string]string {headerMap := make(map[string]string)for _, header := range *h {parts := strings.SplitN(header, "=", 2)if len(parts) == 2 {headerMap[parts[0]] = parts[1]}}return headerMap
}

5 测试

#!/bin/bashset -eip=192.168.100.10
port=3333timeout=2# 测试tcp端口
echo ">>> Testing tcp port $port..."
./gonc tcp://$ip:$port -data "Hello" -timeout $timeout# 测试udp端口
echo ">>> Testing udp port $port..."
./gonc udp://$ip:$port -data "Hello" -timeout $timeout# 测试http服务
echo ">>> Testing http service..."
./gonc http://example.com -method GET -timeout $timeout# 测试ping
echo ">>> Testing ping..."
sudo ./gonc ping://$ip -timeout $timeout

由于测试ping时使用了原始套接字,所以需要root权限

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

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

相关文章

【JAVA高级篇教学】第一篇:Springboot对接通义千问大模型

博主今天打算讲解下Java如何对接阿里云的通义千问大模型&#xff0c;可以自己玩玩ai问答之类的&#xff01; 目录 一、发展历程 二、API-KEY的获取与配置 三、引用SDK 四、文本模型 1.代码 2.返回数据 3.官方代码案例 五、通义千问VL 1.计量计费 六、查看API-KEY调用额…

LeetCode 每日一题 2024/12/2-2024/12/8

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录 12/2 52. N 皇后 II12/3 3274. 检查棋盘方格颜色是否相同12/4 2056. 棋盘上有效移动组合的数目12/5 3001. 捕获黑皇后需要的最少移动次数12/6 999. 可以被一步捕获的棋子数…

快捷构建AI大模型,源码自取可直接运行

Node.js 和 WebSocket 实现一个基于kimi&#xff08;Moonshot 月之暗大模型&#xff09;的AI工具 前端&#xff1a;前端界面比较容易&#xff0c;只需要简单的额css js即可&#xff0c;本文使用vue作为作为demo。 后端&#xff1a;我java很垃圾&#xff0c;写不出好的代码&am…

C# 关于实现保存数据以及数据溯源推送

前言 实现了一个数据接收、存储和推送的功能 首先定义我们数据存储的格式&#xff08;可根据自己的需求定义格式&#xff09;&#xff1a; 数据切割符号&#xff1a;**$是区分数据与其他数据的划分 数据内容切割号&#xff1a;|**是区分时间戳与内容数据的划分 以下是我存储的…

R语言 | 峰峦图 / 山脊图

目的&#xff1a;为展示不同数据分布的差异。 1. ggplot2 实现 # 准备数据 datmtcars[, c("mpg", "cyl")] colnames(dat)c("value", "type") head(dat) # value type #Mazda RX4 21.0 6 #Mazda RX4 Wag …

Redis性能优化18招

Redis性能优化的18招 目录 前言选择合适的数据结构避免使用过大的key和value[使用Redis Pipeline](#使用Redis Pipeline)控制连接数量合理使用过期策略使用Redis集群充分利用内存优化使用Lua脚本监控与调优避免热点key使用压缩使用Geo位置功能控制数据的持久化尽量减少事务使…

学习笔记063——通过使用 aspose-words 将 Word 转 PDF 时,遇到的字体改变以及乱码问题

文章目录 1、问题描述&#xff1a;2、解决方法&#xff1a; 1、问题描述&#xff1a; Java项目中&#xff0c;有个需要将word转pdf的需求。本人通过使用aspose-words来转换的。在Windows中&#xff0c;转换是完全正常的。但是当部署到服务器时&#xff0c;会出现转换生成的pdf…

(6)JS-Clipper2之ClipperOffset

1. 描述 ClipperOffset类封装了对打开路径和关闭路径进行偏移(膨胀/收缩)的过程。 这个类取代了现在已弃用的OffsetPaths函数&#xff0c;该函数不太灵活。可以使用不同的偏移量(增量)多次调用Execute方法&#xff0c;而不必重新分配路径。现在可以在一次操作中对开放和封闭路…

SpringCloudAlibaba教程之注册中心Nacos

目录 概念 架构 设计原则 架构分层 用户层 业务层 内核层 插件 单机部署 1.下载安装包 2.安装nacos 3.启动nacos 快速开始 1.添加Maven依赖 2.添加配置 3.启动 集群部署 搭建步骤 1.搭建数据库&#xff0c;初始化数据库表结构 2.配置nacos 3.启动nacos集群…

PostgreSQL 安装部署系列:使用YUM 方式在Centos 7.9 安装指定 PostgreSQL -15版本数据库

一、前言 千里之行始于足下&#xff0c;想学习一门数据库&#xff0c;首先要从安装部署开始&#xff0c;先拥有一套属于自己的学习测试库。为了更好的学习该数据库&#xff0c;可以选择一个在企业界使用率比较普及的操作系统&#xff0c;选择稳定版本的操作系统&#xff1b;如果…

【算法】数组中,求K个最大值

已知&#xff1a;数组 [8, 9, 15, 20, 3, 5, 7, 2, 6]&#xff0c;求第8个最大值是哪个值&#xff1f; function quickSort(arr, targetIndex, start) {if (arr.length < 1) return arr[0];let left [];let right [];const mid Math.floor(arr.length / 2);const midNum…

李飞飞的生成式3D场景,对数字孪生的未来影响几何?

大家好&#xff0c;我是日拱一卒的攻城师不浪&#xff0c;致力于技术与艺术的融合。这是2024年输出的第47/100篇文章。 前言 这两天&#xff0c;AI界的教母李飞飞团队重磅发布了空间智能生成式AI大模型。 仅通过一张图片就能够生成一个可操作和交互的3D空间场景。 空间智能的…

【Redis集群】使用docker compose创建docker集群,并暴露外部接口

使用 Docker Compose 创建一个 Redis 集群并暴露外部接口需要配置 docker-compose.yml 文件。以下是一个基本的步骤&#xff0c;包括 Redis 集群的创建和外部接口的暴露。 1、创建 docker-compose.yml 首先&#xff0c;您需要创建一个 docker-compose.yml 文件&#xff0c;配…

Redis面试专题-持久化

目录 前言 持久化相关知识 1.三种持久化机制 2.RDB持久化 3.深入剖析一下RDB持久化过程 4.AOF持久化 5.RDB和AOF对比​编辑 面试题 1.redis持久化机制有哪些&#xff1f; 2.那仔细讲讲你对他们的理解 3.你刚刚说AOF的文件很大&#xff0c;那AOF文件会越来越大&#xf…

数据结构之初始二叉树(1)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 目录 树型结构 树的概念 与树的有关概念 树的表示形式 树的应用 二叉树 概念 两种特殊的…

LeetCode139. 单词拆分(2024冬季每日一题 29)

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1&#xff1a; 输入: s “leetcode”, wordDic…

酷柚易汛生产管理系统PHP+Uniapp

生产管理系统&#xff0c;帮助企业数字化转型&#xff0c;打造智能工厂&#xff0c;专业为生产企业量身开发的一套完整的生产管理系统。主要包含以下模块&#xff1a;购货模块、生产模块、仓库模块、资料模块&#xff0c;可配合酷柚易汛进销存无缝衔接使用。 产品理念: 共享功…

从零开始学TiDB(2)深入了解TiDB Server模块

TiDB Server 架构 TiDB Server 的主要功能&#xff1a; 一条SQL的执行流程&#xff1a; 1.将整个SQL语句解析成一个个的token&#xff0c;生成一个树形结构。 2.编译模块 1.首先需要做一个合法性验证&#xff0c;比如表存不存在等。 2.做逻辑优化&#xff1a;依据关系型代数等…

贪心算法专题(四)

目录 1. 单调递增的数字 1.1 算法原理 1.2 算法代码 2. 坏了的计算器 2.1 算法原理 2.2 算法代码 3. 合并区间 3.1 算法原理 3.2 算法代码 4. 无重叠区间 4.1 算法原理 4.2 算法代码 5. 用最少数量的箭引爆气球 5.1 算法原理 ​5.2 算法代码 1. 单调递增的数字…

241207-通过Docker部署Wiki.JS并设置ElasticSearch进行中文搜索

A. 最终效果 B. 配置文件 version: "3" services:wiki:image: ghcr.io/requarks/wiki:2container_name: wikijsports:- "3000:3000"volumes:- /home/lgk/Projects/WikiJS/config:/configenvironment:- DB_TYPEpostgres- DB_HOSTdatabase- DB_PORT5432- DB…