Go语言IP网络程序设计

  • 内容
  • 评论
  • 相关

IP 是 Internet 网络层的核心协议,它是一种不可靠的、无连接的通信协议。TCP、UDP 都是在 IP 的基础上实现的通信协议,所以 IP 属于一种底层协议,它可以直接对网络数据包 (Package) 进行处理。另外,通过 IP 用户还可以实现自己的网络服务协议。本节将详细讲解 IP 网络编程服务器、客户机的设计原理和设计过程。

IPAddr 地址结构体

在进行 IP 网络编程时,服务器或客户机的地址使用 IPAddr 地址结构体表示,IPAddr 结构体只有一个字段 IP,形式如下:

type IPAddr struct {
    IP IP
}

通过了解 IPAddr 地址结构可以发现,IP 网络编程属于一种底层网络程序设计,它可以直接对 IP 包进行处理,所以 IPAddr 地址中没有端口地址,这个和 TCPAddr 地址结构、UDPAddr 地址结构都不同,在应用时要特别注意。

函数 ResolveIPAddr() 可以把网络地址转换为 IPAddr 地址结构,该函数原型定义如下:

func ResolveIPAddr(net, addr string) (*IPAddr, error)

在调用 ResolveIPAddr() 函数时,参数 net 表示网络类型,可以是“ip”、“ip4”或“ip6”,参数 addr 是 IP 地址或域名,如果是 IPv6 地址则必须使用“[]”括起来。

函数 ResolveIPAddr() 调用成功后返回一个指向 IPAddr 结构体的指针,否则返回一个错误类型。

另外,IPAddr 地址对象还有两个方法:Network() 和 String()。Network() 方法用于返回 IPAddr 地址对象的网络协议名,比如“ip”;String() 方法可以将 IPAddr 地址转换成字符串形式。这两个方法原型定义如下:

func (a *IPAddr) Network() string
func (a *IPAddr) String() string

IPConn 对象

在进行 IP 网络编程时,客户机和服务器之间是通过 IPConn 对象实现连接的,IPConn 是 Conn 接口的实现。IPConn 对象绑定了服务器的网络协议和地址信息,IPConn 对象定义如下:

type IPConn struct {
    //空结构
}

由于 IPConn 是一个无连接的通信对象,所以 IPConn 对象提供了 ReadFromIP() 方法和 WriteToIP() 方法用于在客户机和服务器之间进行数据收发操作。ReadFromIP() 和 WriteToIP() 的原型定义如下:

func (c *IPConn) ReadFromIP(b []byte) (int, *IPAddr, error)
func (c *IPConn) WriteToIP(b []bytez addr *IPAddr) (int, error)

ReadFromIP() 方法调用成功后返回接收字节数和发送方地址,否则返回一个错误类型;WriteToIP() 方法调用成功后返回发送字节数,否则返回一个错误类型。

IP 服务器设计

由于工作在网络层,ip 服务器并不需要在一个指定的端口上和客户机进行通信连接,IP 服务器的工作过程如下:

1) IP 服务器使用指定的协议簇和协议,调用 ListenIP() 函数创建一个 IPConn 连接对象,并在该对象和客户机间建立不可靠连接。

2) 如果服务器和某个客户机建立了 IPConn 连接,就可以使用该对象的 ReadFromIP() 方法和 WriteToIP() 方法相互通信了。

3) 如果通信结束,服务器还可以调用 Close() 方法关闭 IPConn 连接。

函数 ListenIP() 原型定义如下:

func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error)

在调用函数 ListenIP() 时,参数 netProto 是“网络类型+协议名”或“网络类型+协议号”,中间用“:”隔开,比如“IP4:IP”或“IP4:4”。参数 laddr 是服务器本地地址,可以是任意活动的主机地址,或者是内部测试地址“127.0.0.1”。该函数调用成功,返回一个 IPConn 对象;调用失败,返回一个错误类型。

【示例 1】IP Server 端设计,服务器使用本地主机地址,调用 Hostname() 函数获取。服务器设计工作模式采用循环服务器,对每一个连接请求调用线程 handleClient 来处理。

// IP Server 端设计
package main

import(
    "fmt"
    "net"
    "os"
)
func main() {
    name, err := os.Hostname()
    checkError(err)
    ipAddr, err := net.ResolveIPAddr("ip4", name)
    checkError(err)
    fmt.Println(ipAddr)
    conn, err := net.ListenIP("ip4:ip", ipAddr)
    checkError(err)
    for {
        handleClient(conn)
    }
}
func handleClient(conn *net.IPConn) {
    var buf [512]byte
    n, addr, err := conn.ReadFromIP(buf[0:])
    if err != nil {
        return
    }
    fmt.Println("Receive from client", addr.String(), string(buf[0:n]))
    conn.WriteToIP([]byte("Welcome Client!"), addr)
}
func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

IP 客户机设计

在 ip 网络编程中,客户机工作过程如下:

1) IP 客户机在获取了服务器的网络地址之后,可以调用 DialIP() 函数向服务器发出连接请求,如果请求成功会返回 IPConn 对象。

2) 如果连接成功,客户机可以直接调用 IPConn 对象的 ReadFromIP() 方法或 WriteToIP() 方法,与服务器进行通信活动。

3) 通信完成后,客户机调用 Close() 方法关闭 IPConn 连接,断开通信链路。

函数 DialIP() 原型定义如下:

func DialIP (netProto string, laddr, raddr *IPAddr) (*IPConn, error)

在调用函数 DialIP() 时,参数 netProto 是“网络类型+协议名”或“网络类型+协议号”,中间用“:”隔开,比如“IP4:IP”或“IP4:4”。参数 laddr 是本地主机地址,可以设为 nil。参数 raddr 是对方主机地址,必须指定不能省略。函数调用成功后,返回 IPConn 对象;调用失败,返回一个错误类型。

方法 Close() 的原型定义如下:

func (c *IPConn) Close() error

该方法调用成功后,关闭 IPConn 连接;调用失败,返回一个错误类型。

【示例 2】IP Client 端设计,客户机通过内部测试地址“127.0.0.1”和服务器建立通信连接,服务器主机地址可以使用 Hostname() 函数获取。

// IP Client端设计
package main

import(
    "fmt"
    "net"
    "os"
)
func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
    }
    service := os.Args[1]
    lAddr, err := net.ResolveIPAddr("ip4", service)
    checkError(err)
    name, err := os.Hostname()
    checkError(err)
    rAddr, err := net.ResolveIPAddr("ip4", name)
    checkError(err)
    conn, err := net.DialIP("ip4:ip", lAddr, rAddr)
    checkError(err)
    _, err = conn.WriteToIP([]byte("Hello Server!"), rAddr)
    checkError(err)
    var buf [512]byte
    n, addr, err := conn.ReadFromIP(buf[0:])
    checkError(err)
    fmt.Println("Reply form server", addr.String(), string(buf[0:n]))
    conn.Close()
    os.Exit(0)
}
func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

编译并运行服务器端和客户端,测试过程如下:

启动服务器:go run .\main.go
客戶机连接:go run .\client.go 127.0.0.1
服务器响应:Receive from client 127.0.0.1 Hello Server!
客户机接收:Reply form server 192.168.1.104 Welcome Client!

通过测试结果可以看出,TCP、UDP 的服务器和客户机通信时必须使用端口号,而 IP 服务器和客户机之间通信不需要端口号。另外,如果在同一台计算机上,服务器、客户机要使用不同的地址进行测试,比如本例服务器地址是“192.168.1.104”,客户机使用内部测试地址“127.0.0.1”。

如果使用相同的地址,会发生自发自收的现象,原因是 IP 是底层通信,并没有像 TCP、UDP 那样使用端口号来区分不同的通信进程。

Ping 程序设计

不管是 UNIX 还是 Windows 系统中都有一个 Ping 命令,利用它可以检查网络是否连通,分析判断网络故障。Ping 会向目标主机发送测试数据包,看对方是否有响应并统计响应时间,以此测试网络。

Ping 命令的这些功能是使用 IP 层的 ICMP 实现的,在测试过程中,源主机向目标主机发送回显请求报文(ICMP_ECHO_REQUEST,type = 8, code = 0),目的主机返回回显响应报文(ICMP_ECHO_REPLY,type = 0, code = 0),相关的数据包格式如下图所示。

ICMP 回显请求和响应数据包格式
图:ICMP 回显请求和响应数据包格式

本文标题:Go语言IP网络程序设计

本文地址:https://www.hosteonscn.com/6800.html

评论

0条评论

发表评论

邮箱地址不会被公开。 必填项已用*标注