- 套接字级编程
- Introduction
- 介绍
- The TCP/IP stack
- TCP/IP协议栈
- Internet addresses
- 互联网地址
- IP address type
- IP地址类型
- IP类型
- Services
- 服务
- TCP Sockets
- TCP套接字
- Controlling TCP connections
- 控制TCP连接
- UDP Datagrams
- UDP数据报
- Server listening on multiple sockets
- 服务器侦听多个套接字
- The types Conn, PacketConn and Listener
- Conn,PacketConn和Listener类型
- Raw sockets and the type IPConn
- 原始套接字和IPConn类型
- Conclusion
- 结论
套接字级编程
This chapter looks at the basic techniques for network programming. It deals with host and service addressing, and then considers TCP and UDP. It shows how to build both servers and clients using the TCP and UDP Go APIs. It also looks at raw sockets, in case you need to implement your own protocol above IP.
本章将着眼于网络编程的基础方法,将涉及到主机和服务寻址,也会考虑到TCP和UDP。同时也将展示如何使用GO的TCP和UDP相关的API来构建服务器和客户端。最后介绍了原生套接字,如果你需要基于IP协议实现你自己的协议的话。
Introduction
介绍
There are many kinds of networks in the world. These range from the very old such as serial links, through to wide area networks made from copper and fibre, to wireless networks of various kinds, both for computers and for telecommunications devices such as phones. These networks obviously differ at the physical link layer, but in many cases they also differed at higher layers of the OSI stack.
世上存在很多种网络。它们涵盖了从古老如串行链路,到为计算机或手机这样的通讯设备所搭建的铜缆和光纤的广域网,或各种各样的无线网络。在物理链路层它们区别明显,但很多时候,在更高层次的OSI模型它们也存在差异。
Over the years there has been a convergence to the "internet stack" of IP and TCP/UDP. For example, Bluetooth defines physical layers and protocol layers, but on top of that is an IP stack so that the same internet programming techniques can be employed on many Bluetooth devices. Similarly, developing 4G wireless phone technologies such as LTE (Long Term Evolution) will also use an IP stack.
多年的发展,使得IP和TCP/UDP协议基本上就等价于网络协议栈。例如, 蓝牙定义了物理层和协议层,但最重要的是IP协议栈,可以在许多蓝牙设备使相同的互联网编程技术。同样, 开发4G无线手机技术,如LTE(Long Term Evolution)也将使用IP协议栈。
While IP provides the networking layer 3 of the OSI stack, TCP and UDP deal with layer 4. These are not the final word, even in the interenet world: SCTP has come from the telecommunications to challenge both TCP and UDP, while to provide internet services in interplanetary space requires new, under development protocols such as DTN. Nevertheless, IP, TCP and UDP hold sway as principal networking technologies now and at least for a considerable time into the future. Go has full support for this style of programming
IP提供了第3层的OSI网络协议栈,TCP和UDP则提供了第4层。即使在因特网世界,这些都不是固定不变的:TCP和UDP将面临来自SCTP(STREAM CONTROL TRANSMISSION PROTOCOL 流控制传输协议)的挑战,同时在星际空间中提供互联网服务需要新的像正在开发的DTN协议。不过,IP, TCP和UDP至少在当前甚至未来相当长的时间内是主要的网络技术。Go语言提供了对这种编程的全面支持。
This chapter shows how to do TCP and UDP programming using Go, and how to use a raw socket for other protocols.
本章介绍如何使用GO编写TCP和UDP程序,以及如何使用其他协议的原始套接字。
The TCP/IP stack
TCP/IP协议栈
The OSI model was devised using a committee process wherein the standard was set up and then implemented. Some parts of the OSI standard are obscure, some parts cannot easily be implemented, some parts have not been implemented.
OSI模型标准的建立和实施是一个委员会(国际标准化组织ISO—译者注)设计的。OSI标准中的一些部分是模糊的,有些部件不能很容易地实现,一些地方还没有得到落实。
The TCP/IP protocol was devised through a long-running DARPA project. This worked by implementation followed by RFCs (Request For Comment). TCP/IP is the principal Unix networking protocol. TCP/IP = Transmission Control Protocol/Internet Protocol.
TCP/IP协议由长期运行的一个DARPA(美国国防先进研究项目局)项目设计。该工作其次由RFC (Request For Comment)实施。TCP/IP是Unix的首要网络协议。TCP/IP等于传输控制协议/互联网协议。
The TCP/IP stack is shorter than the OSI one:
TCP is a connection-oriented protocol, UDP (User Datagram Protocol) is a connectionless protocol.
TCP/IP协议栈是OSI模型的一部分:
TCP是一个面向连接的协议,UDP(User Datagram Protocol,用户数据报协议)是一种无连接的协议。
IP datagrams
IP数据包
The IP layer provides a connectionless and unreliable delivery system. It considers each datagram independently of the others. Any association between datagrams must be supplied by the higher layers.
IP层提供了无连接的不可靠的传输系统,任何数据包之间的关联必须依赖更高的层来提供。
The IP layer supplies a checksum that includes its own header. The header includes the source and destination addresses.
IP层包头支持数据校验,在包头包括源地址和目的地址。
The IP layer handles routing through an Internet. It is also responsible for breaking up large datagrams into smaller ones for transmission and reassembling them at the other end.
IP层通过路由连接到因特网,还负责将大数据包分解为更小的包,并传输到另一端后进行重组。
UDP
UDP is also connectionless and unreliable. What it adds to IP is a checksum for the contents of the datagram and port numbers. These are used to give a client/server model - see later.
UDP是无连接的,不可靠的。它包括IP数据报的内容和端口号的校验。在后面,我们会用它来构建一些客户端/服务器例子。
TCP
TCP supplies logic to give a reliable connection-oriented protocol above IP. It provides a virtual circuit that two processes can use to communicate. It also uses port numbers to identify services on a host.
TCP是构建于IP之上的面向链接的协议。它提供了一个虚电路使得两个应用进程可以通过它来通信。它通过端口号来识别主机上的服务。
Internet addresses
互联网地址
In order to use a service you must be able to find it. The Internet uses an address scheme for devices such as computers so that they can be located. This addressing scheme was originally devised when there were only a handful of connected computers, and very generously allowed upto 2^32 addresses, using a 32 bit unsigned integer. These are the so-called IPv4 addresses. In recent years, the number of connected (or at least directly addressable) devices has threatened to exceed this number, and so "any day now" we will switch to IPv6 addressing which will allow upto 2^128 addresses, using an unsigned 128 bit integer. The changeover is most likely to be forced by emerging countries, as the developed world has already taken nearly all of the pool of IPv4 addresses.
要想使用一项服务,你必须先能找到它。互联网使用地址定位例如计算机的设备。这种寻址方案最初被设计出来只允许极少数的计算机连接上,使用32位无符号整形,拥有高达2^32个地址。这就是所谓的IPv4地址。近年来,连接(至少可以直接寻址)的设备的数量可能超过这个数字,所以在不久的某一天我们将切换到利用128位无符号整数,拥有高2^128个地址的IPv6寻址。这种转换最有可能被已经耗尽了所有的IPv4地址的新兴国家发达地区。
IPv4 addresses
IPv4地址
The address is a 32 bit integer which gives the IP address. This addresses down to a network interface card on a single device. The address is usually written as four bytes in decimal with a dot '.' between them, as in "127.0.0.1" or "66.102.11.104".
IP地址是一个32位整数构成。每个设备的网络接口都有一个地址。该地址通常使用'.'符号分割的4字节的十进制数,例如:"127.0.0.1" 或 "66.102.11.104"。
The IP address of any device is generally composed of two parts: the address of the network in which the device resides, and the address of the device within that network. Once upon a time, the split between network address and internal address was simple and was based upon the bytes used in the IP address.
所有设备的IP地址,通常是由两部分组成:网段地址和网内地址。从前,网络地址和网内地址的分辨很简单,使用字节构建IP地址。
- In a class A network, the first byte identifies the network, while the last three identify the device. There are only 128 class A networks, owned by the very early players in the internet space such as IBM, the General Electric Company and MIT (http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml)
- Class B networks use the first two bytes to identify the network and the last two to identify devices within the subnet. This allows upto 2^16 (65,536) devices on a subnet
- Class C networks use the first three bytes to identify the network and the last one to identify devices within that network. This allows upto 2^8 (actually 254, not 256) devices
- 一个A类网络,前1个字节标识为网络地址,同时后3字节标识为主机地址。A类网络只有128个, 被很早的互联网成员例如IBM,通用电气公司(the General Electric Company)和MIT所拥有。(http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml)
- 一个B类网络使用前2个字节标识为网络地址,后2字节标识为子网的主机地址。这最多允许2^16 (65,536)个设备在同一个子网。
- 一个C类网络使用前3字节标识为网络地址,后1字节的主机地址。这最多允许2^8 (其实是254, 不是256)个设备。
This scheme doesn't work well if you want, say, 400 computers on a network. 254 is too small, while 65,536 is too large. In binary arithmetic terms, you want about 512. This can be achieved by using a 23 bit network address and 9 bits for the device addresses. Similarly, if you want upto 1024 devices, you use a 22 bit network address and a 10 bit device address.
但是,比如你需要400台计算机在同一个网络,该方案是不可行的。254太小,而65,536又太大。根据二进制计算,你大约需要512(2^9,译者注)。这样就可以通过使用一个23位的网络地址和9位的设备地址实现。同样,如果您需要高达1024台设备,使用一个22位网络地址和一个10位的设备地址。
Given an IP address of a device, and knowing how many bits N are used for the network address gives a relatively straightforward process for extracting the network address and the device address within that network. Form a "network mask" which is a 32-bit binary number with all ones in the first N places and all zeroes in the remaining ones. For example, if 16 bits are used for the network address, the mask is 11111111111111110000000000000000. It's a little inconvenient using binary, so decimal bytes are usually used. The netmask for 16 bit network addresses is 255.255.0.0, for 24 bit network addresses it is 255.255.255.0, while for 23 bit addresses it would be 255.255.254.0 and for 22 bit addresses it would be 255.255.252.0.
知道设备的IP地址和多少字节用于网络地址,那么可以比较直接的提取出这个网络中的网络地址和设备地址。例如:“网络掩码”是一个前面N位为1,其他所有位为0的32位二进制数。例如,如果使用16位的网络地址,掩码为11111111111111110000000000000000。使用二进制有一点不方便,所以通常使用十进制字节。16位网络地址的子网掩码是255.255.0.0,而对于23位地址,这将是255.255.254.0,和22位地址,这将是255.255.252.0。
Then to find the network of a device, bit-wise AND it's IP address with the network mask, while the device address within the subnet is found with bit-wise AND of the 1's complement of the mask with the IP address.
接着查找设备的网络,并将其IP地址与网络掩码进行按位与操作,而该设备在子网中的地址,可通过其IP地址同掩码与1的补码的按位与操作发现。
IPv6 addresses
IPv6地址
The internet has grown vastly beyond original expectations. The initially generous 32-bit addressing scheme is on the verge of running out. There are unpleasant workarounds such as NAT addressing, but eventually we will have to switch to a wider address space. IPv6 uses 128-bit addresses. Even bytes becomes cumbersome to express such addresses, so hexadecimal digits are used, grouped into 4 digits and separated by a colon ':'. A typical address might be 2002:c0e8:82e7:0:0:0:c0e8:82e7.
因特网的迅速发展大大超出了原来的预期。最初富余的32位地址解决方案已经接近用完。虽然有一些例如NAT地址输入这样不是很完美的解决方法,但最终我们将不得不切换到更广阔的地址空间。IPv6使用128位地址,即使表达同样的地址,字节数变得很麻烦,由':'分隔的4位16进制组成。一个典型的例子如:2002:c0e8:82e7:0:0:0:c0e8:82e7。
These addresses are not easy to remember! DNS will become even more important. There are tricks to reducing some addresses, such as eliding zeroes and repeated digits. For example, "localhost" is 0:0:0:0:0:0:0:1, which can be shortened to ::1
要记住这些地址并不容易!DNS将变得更加重要。有一些技巧用来介绍一些地址,如省略一些零和重复的数字。例如:"localhost"地址是:0:0:0:0:0:0:0:1,可以缩短到::1。
IP address type
IP地址类型
The type IP
IP类型
The package "net" defines many types, functions and methods of use in Go network programming. The type IP
is defined as an array of bytes
"net"包定义了许多类型, 函数,方法用于Go网络编程。IP
类型被定义为一个字节数组。
type IP []byte
There are several functions to manipulate a variable of type IP
, but you are likely to use only some of them in practice. For example, the function ParseIP(String)
will take a dotted IPv4 address or a colon IPv6 address, while the IP
method String
will return a string. Note that you may not get back what you started with: the string form of 0:0:0:0:0:0:0:1 is ::1.
有几个函数来处理一个IP
类型的变量, 但是在实践中你很可能只用到其中的一些。例如, ParseIP(String)
函数将获取逗号分隔的IPv4或者冒号分隔的IPv6地址, 而IP
方法的字符串
将返回一个字符串。请注意,您可能无法取回你期望的: 字符串 0:0:0:0:0:0:0:1是::1。
A program to illustrate this is
下面用一个程序来说明
/* IP
*/
package main
import (
"net"
"os"
"fmt"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0])
os.Exit(1)
}
name := os.Args[1]
addr := net.ParseIP(name)
if addr == nil {
fmt.Println("Invalid address")
} else {
fmt.Println("The address is ", addr.String())
}
os.Exit(0)
}
If this is compiled to the executable IP
then it can run for example as
如果编译它为可执行文件IP
,那么它可以运行如
IP 127.0.0.1
with response
得到结果
The address is 127.0.0.1
or as
或
IP 0:0:0:0:0:0:0:1
得到结果
The address is ::1
The type IPmask
IP掩码
In order to handle masking operations, there is the type
为了处理掩码操作,有下面类型:
type IPMask []byte
There is a function to create a mask from a 4-byte IPv4 address
下面这个函数用一个4字节的IPv4地址来创建一个掩码
func IPv4Mask(a, b, c, d byte) IPMask
Alternatively, there is a method of IP
which returns the default mask
另外, 这是一个IP
的方法返回默认的掩码
func (ip IP) DefaultMask() IPMask
Note that the string form of a mask is a hex number such as ffff0000 for a mask of 255.255.0.0.
需要注意的是一个掩码的字符串形式是一个十六进制数,如掩码255.255.0.0为ffff0000。
A mask can then be used by a method of an IP address to find the network for that IP address
一个掩码可以使用一个IP地址的方法,找到该IP地址的网络
func (ip IP) Mask(mask IPMask) IP
An example of the use of this is the following program:
下面的程序是一个使用了这个的例子:
/* Mask
*/
package main
import (
"fmt"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s dotted-ip-addr\n", os.Args[0])
os.Exit(1)
}
dotAddr := os.Args[1]
addr := net.ParseIP(dotAddr)
if addr == nil {
fmt.Println("Invalid address")
os.Exit(1)
}
mask := addr.DefaultMask()
network := addr.Mask(mask)
ones, bits := mask.Size()
fmt.Println("Address is ", addr.String(),
" Default mask length is ", bits,
"Leading ones count is ", ones,
"Mask is (hex) ", mask.String(),
" Network is ", network.String())
os.Exit(0)
}
If this is compiled to Mask
and run by
编译并运行Mask
Mask 127.0.0.1
it will return
将返回
Address is 127.0.0.1 Default mask length is 8 Network is 127.0.0.0
The type IPAddr
IPAddr类型
Many of the other functions and methods in the net package return a pointer to an IPAddr
. This is simply a structure containing an IP
.
在net包的许多函数和方法会返回一个指向IPAddr
的指针。这不过只是一个包含IP
类型的结构体。
type IPAddr {
IP IP
}
A primary use of this type is to perform DNS lookups on IP host names.
这种类型的主要用途是通过IP主机名执行DNS查找。
func ResolveIPAddr(net, addr string) (*IPAddr, os.Error)
where net
is one of "ip", "ip4" or "ip6". This is shown in the program
其中net
是"ip","ip4"或者"ip6"的其中一个. 下面的程序中将会展示。
/* ResolveIP
*/
package main
import (
"net"
"os"
"fmt"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s hostname\n", os.Args[0])
fmt.Println("Usage: ", os.Args[0], "hostname")
os.Exit(1)
}
name := os.Args[1]
addr, err := net.ResolveIPAddr("ip", name)
if err != nil {
fmt.Println("Resolution error", err.Error())
os.Exit(1)
}
fmt.Println("Resolved address is ", addr.String())
os.Exit(0)
}
Running ResolveIP www.google.com
returns
运行ResolveIP www.google.com
返回
Resolved address is 66.102.11.104
Host lookup
主机查询
The function ResolveIPAddr
will perform a DNS lookup on a hostname, and return a single IP address. However, hosts may have multiple IP addresses, usually from multiple network interface cards. They may also have multiple host names, acting as aliases.
ResolveIPAddr
函数将对某个主机名执行DNS查询,并返回一个简单的IP地址。然而,通常主机如果有多个网卡,则可以有多个IP地址。它们也可能有多个主机名,作为别名。
func LookupHost(name string) (cname string, addrs []string, err os.Error)
One of these addresses will be labelled as the "canonical" host name. If you wish to find the canonical name, use func LookupCNAME(name string) (cname string, err os.Error)
这些地址将会被归类为“canonical”主机名。如果你想找到的规范名称,使用func LookupCNAME(name string) (cname string, err os.Error)
This is shown in the following program
下面是一个演示程序
/* LookupHost
*/
package main
import (
"net"
"os"
"fmt"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s hostname\n", os.Args[0])
os.Exit(1)
}
name := os.Args[1]
addrs, err := net.LookupHost(name)
if err != nil {
fmt.Println("Error: ", err.Error())
os.Exit(2)
}
for _, s := range addrs {
fmt.Println(s)
}
os.Exit(0)
}
Note that this function returns strings, not IPAddress
values.
注意,这个函数返回字符串,而不是IPAddress
。
Services
服务
Services run on host machines. They are typically long lived and are designed to wait for requests and respond to them. There are many types of services, and there are many ways in which they can offer their services to clients. The internet world bases many of these services on two methods of communication, TCP and UDP, although there are other communication protocols such as SCTP waiting in the wings to take over. Many other types of service, such as peer-to-peer, remote procedure calls, communicating agents, and many others are built on top of TCP and UDP.
服务运行在主机。它们通常长期存活,同时被设计成等待的请求和响应请求。有许多类型的服务,有他们能够通过各种方法向客户提供服务。互联网的世界基于TCP和UDP这两种通信方法提供许多这些服务,虽然也有其他通信协议如SCTP伺机取代。许多其他类型的服务,例如点对点, 远过程调用, 通信代理, 和许多其他建立在TCP和UDP之上的服务之上。
Ports
端口
Services live on host machines. The IP address will locate the host. But on each computer may be many services, and a simple way is needed to distinguish between them. The method used by TCP, UDP, SCTP and others is to use a port number. This is an unsigned integer beween 1 and 65,535 and each service will associate itself with one or more of these port numbers.
服务存活于主机内。IP地址可以定位主机。但在每台计算机上可能会提供多种服务,需要一个简单的方法对它们加以区分。TCP,UDP,SCTP或者其他协议使用端口号来加以区分。这里使用一个1到65,535的无符号整数,每个服务将这些端口号中的一个或多个相关联。
There are many "standard" ports. Telnet usually uses port 23 with the TCP protocol. DNS uses port 53, either with TCP or with UDP. FTP uses ports 21 and 20, one for commands, the other for data transfer. HTTP usually uses port 80, but it often uses ports 8000, 8080 and 8088, all with TCP. The X Window System often takes ports 6000-6007, both on TCP and UDP.
有很多“标准”的端口。Telnet服务通常使用端口号23的TCP协议。DNS使用端口号53的TCP或UDP协议。FTP使用端口21和20的命令,进行数据传输。HTTP通常使用端口80,但经常使用,端口8000,8080和8088,协议为TCP。X Window系统往往需要端口6000-6007,TCP和UDP协议。
On a Unix system, the commonly used ports are listed in the file /etc/services
. Go has a function to interrogate this file
在Unix系统中, /etc/services
文件列出了常用的端口。Go语言有一个函数可以获取该文件。
func LookupPort(network, service string) (port int, err os.Error)
The network argument is a string such as "tcp" or "udp", while the service is a string such as "telnet" or "domain" (for DNS).
network是一个字符串例如"tcp"或"udp", service也是一个字符串,如"telnet"或"domain"(DNS)。
A program using this is
示例程序如下
/* LookupPort
*/
package main
import (
"net"
"os"
"fmt"
)
func main() {
if len(os.Args) != 3 {
fmt.Fprintf(os.Stderr,
"Usage: %s network-type service\n",
os.Args[0])
os.Exit(1)
}
networkType := os.Args[1]
service := os.Args[2]
port, err := net.LookupPort(networkType, service)
if err != nil {
fmt.Println("Error: ", err.Error())
os.Exit(2)
}
fmt.Println("Service port ", port)
os.Exit(0)
}
For example, running LookupPort tcp telnet
prints Service port: 23
举个例子, 运行LookupPort tcp telnet
打印 Service port: 23
The type TCPAddr
TCPAddr类型
The type TCPAddr
is a structure containing an IP
and a port
:
TCPAddr
类型包含一个IP
和一个port
的结构:
type TCPAddr struct {
IP IP
Port int
}
The function to create a TCPAddr
is ResolveTCPAddr
函数ResolveTCPAddr
用来创建一个TCPAddr
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
where net
is one of "tcp", "tcp4" or "tcp6" and the addr
is a string composed of a host name or IP address, followed by the port number after a ":", such as "www.google.com:80" or '127.0.0.1:22". if the address is an IPv6 address, which already has colons in it, then the host part must be enclosed in square brackets, such as "[::1]:23". Another special case is often used for servers, where the host address is zero, so that the TCP address is really just the port name, as in ":80" for an HTTP server.
net
是"tcp", "tcp4"或"tcp6"其中之一,addr
是一个字符串,由主机名或IP地址,以及":"后跟随着端口号组成,例如: "www.google.com:80" 或 '127.0.0.1:22"。如果地址是一个IPv6地址,由于已经有冒号,主机部分,必须放在方括号内, 例如:"[::1]:23". 另一种特殊情况是经常用于服务器, 主机地址为0, 因此,TCP地址实际上就是端口名称, 例如:":80" 用来表示HTTP服务器。
TCP Sockets
TCP套接字
When you know how to reach a service via its network and port IDs, what then? If you are a client you need an API that will allow you to connect to a service and then to send messages to that service and read replies back from the service.
当你知道如何通过网络和端口ID查找一个服务时,然后呢?如果你是一个客户端,你需要一个API,让您连接到服务,然后将消息发送到该服务,并从服务读取回复。
If you are a server, you need to be able to bind to a port and listen at it. When a message comes in you need to be able to read it and write back to the client.
如果你是一个服务器,你需要能够绑定到一个端口,并监听它。当有消息到来,你需要能够读取它并回复客户端。
The net.TCPConn
is the Go type which allows full duplex communication between the client and the server. Two major methods of interest are
net.TCPConn
是允许在客户端和服务器之间的全双工通信的Go类型。两种主要方法是
func (c *TCPConn) Write(b []byte) (n int, err os.Error)
func (c *TCPConn) Read(b []byte) (n int, err os.Error)
A TCPConn
is used by both a client and a server to read and write messages.
TCPConn
被客户端和服务器用来读写消息。/p>
TCP client
TCP客户端
Once a client has established a TCP address for a service, it "dials" the service. If succesful, the dial returns a TCPConn
for communication. The client and the server exchange messages on this. Typically a client writes a request to the server using the TCPConn
, and reads a response from the TCPConn
. This continues until either (or both) sides close the connection. A TCP connection is established by the client using the function
一旦客户端已经建立TCP服务, 就可以和对方设备"通话"了. 如果成功,该调用返回一个用于通信的TCPConn
。客户端和服务器通过它交换消息。通常情况下,客户端使用TCPConn
写入请求到服务器, 并从TCPConn
的读取响应。持续如此,直到任一(或两者)的两侧关闭连接。客户端使用该函数建立一个TCP连接。
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
where laddr
is the local address which is usually set to nil
and raddr
is the remote address of the service, and the net
string is one of "tcp4", "tcp6" or "tcp" depending on whether you want a TCPv4 connection, a TCPv6 connection or don't care.
其中laddr
是本地地址,通常设置为nil
和raddr
是一个服务的远程地址, net
是一个字符串,根据您是否希望是一个TCPv4连接,TCPv6连接来设置为"tcp4", "tcp6"或"tcp"中的一个,当然你也可以不关心链接形式。
A simple example can be provided by a client to a web (HTTP) server. We will deal in substantially more detail with HTTP clients and servers in a later chapter, but for now we will keep it simple.
一个简单的例子,展示个客户端连接到一个网页(HTTP)服务器。在后面的章节,我们将处理大量的HTTP客户端和服务器细节,现在我们先从简单的看看。
One of the possible messages that a client can send is the "HEAD" message. This queries a server for information about the server and a document on that server. The server returns information, but does not return the document itself. The request sent to query an HTTP server could be
客户端可能发送的消息之一就是“HEAD”消息。这用来查询服务器的信息和文档信息。 服务器返回的信息,不返回文档本身。发送到服务器的请求可能是
"HEAD / HTTP/1.0\r\n\r\n"
which asks for information about the root document and the server. A typical response might be
这是在请求服务器的根文件信息。 一个典型的响应可能是
HTTP/1.0 200 OK
ETag: "-9985996"
Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT
Content-Length: 18074
Connection: close
Date: Sat, 28 Aug 2010 00:43:48 GMT
Server: lighttpd/1.4.23
We first give the program (GetHeadInfo.go) to establish the connection for a TCP address, send the request string, read and print the response. Once compiled it can be invoked by e.g.
我们首先通过(GetHeadInfo.go)程序来建立TCP连接,发送请求字符串,读取并打印响应。编译后就可以调用,例如:
GetHeadInfo www.google.com:80
The program is
程序
/* GetHeadInfo
*/
package main
import (
"net"
"os"
"fmt"
"io/ioutil"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
os.Exit(1)
}
service := os.Args[1]
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
checkError(err)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
checkError(err)
_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
checkError(err)
//result, err := readFully(conn)
result, err := ioutil.ReadAll(conn)
checkError(err)
fmt.Println(string(result))
os.Exit(0)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
The first point to note is the almost excessive amount of error checking that is going on. This is normal for networking programs: the opportunities for failure are substantially greater than for standalone programs. Hardware may fail on the client, the server, or on any of the routers and switches in the middle; communication may be blocked by a firewall; timeouts may occur due to network load; the server may crash while the client is talking to it. The following checks are performed:
第一个要注意的点是近乎多余的错误检查。因为正常情况下,网络程序失败的机会大大超过单机的程序。在客户端,服务器端或任何路由和中间交换上,硬件可能失败;通信可能会被防火墙阻塞;因网络负载可能会出现超时;当客户端联系服务器,服务器可能会崩溃,下列检查是必须的:
- There may be syntax errors in the address specified
- The attempt to connect to the remote service may fail. For example, the service requested might not be running, or there may be no such host connected to the network
- Although a connection has been established, writes to the service might fail if the connection has died suddenly, or the network times out
- Similarly, the reads might fail
- 指定的地址中可能存在语法错误
- 尝试连接到远程服务可能会失败。例如, 所请求的服务可能没有运行, 或者有可能是主机没有连接到网络
- 虽然连接已经建立,如果连接突然丢失也可能导致写失败,或网络超时
- 同样,读操作也可能会失败
Reading from the server requires a comment. In this case, we read essentially a single response from the server. This will be terminated by end-of-file on the connection. However, it may consist of several TCP packets, so we need to keep reading till the end of file. The io/ioutil
function ReadAll
will look after these issues and return the complete response. (Thanks to Roger Peppe on the golang-nuts mailing list.).
值得一提的是,如何从服务端读取数据。在这种情况下,读本质上是一个单一的来自服务器的响应,这将终止文件结束的连接。但是,它可能包括多个TCP数据包,所以我们需要不断地读,直到文件的末尾。在io/ioutil
下的ReadAll
函数考虑这些问题,并返回完整响应。(感谢Roger Peppe在golang-nuts上的邮件列表。)。
There are some language issues involved. First, most of the functions return a dual value, with possible error as second value. If no error occurs, then this will be nil
. In C, the same behaviour is gained by special values such as NULL
, or -1, or zero being returned - if that is possible. In Java, the same error checking is managed by throwing and catching exceptions, which can make the code look very messy.
有一些涉及语言的问题,首先, 大多数函数返回两个值, 第二个值是可能出现的错误。如果没有错误发生, 那么它的值为nil
。在C中, 如果需要的话,同样的行为通过定义特殊值例如NULL
, 或 -1, 或0来返回。在Java中, 同样的错误检查通过抛出和捕获异常来管理,它会使代码看起来很凌乱。
In earlier versions of this program, I returned the result in the array buf
, which is of type [512]byte
. Attempts to coerce this to a string failed - only byte arrays of type []byte
can be coerced. This is a bit of a nuisance.
在这个程序的早期版本, 我在返回结果中返回buf
数组, 它的类型是[512]byte
。我试图强迫类型为一个字符串但失败了- 只有字节数组类型[]byte
可以强制转换。这确实有点困扰。
A Daytime server
一个时间(Daytime)服务器
About the simplest service that we can build is the daytime service. This is a standard Internet service, defined by RFC 867, with a default port of 13, on both TCP and UDP. Unfortunately, with the (justified) increase in paranoia over security, hardly any sites run a daytime server any more. Never mind, we can build our own. (For those interested, if you install inetd
on your system, you usually get a daytime server thrown in.)
最简单的服务,我们可以建立是时间(Daytime)服务。这是一个标准的互联网服务, 由RFC 867定义, 默认的端口13, 协议是TCP和UDP。很遗憾, 对安全的偏执,几乎没有任何站点运行着时间(Daytime)服务器。不过没关系,我们可以建立我们自己的。 (对于那些有兴趣, 你可以在你的系统安装inetd
, 你通常可以得到一个时间(Daytime)服务器。)
A server registers itself on a port, and listens on that port. Then it blocks on an "accept" operation, waiting for clients to connect. When a client connects, the accept call returns, with a connection object. The daytime service is very simple and just writes the current time to the client, closes the connection, and resumes waiting for the next client.
在一个服务器上注册并监听一个端口。然后它阻塞在一个"accept"操作,并等待客户端连接。当一个客户端连接, accept调用返回一个连接(connection)对象。时间(Daytime)服务非常简单,只是将当前时间写入到客户端, 关闭该连接,并继续等待下一个客户端。
The relevant calls are
有关调用
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
func (l *TCPListener) Accept() (c Conn, err os.Error)
The argument net
can be set to one of the strings "tcp", "tcp4" or "tcp6". The IP address should be set to zero if you want to listen on all network interfaces, or to the IP address of a single network interface if you only want to listen on that interface. If the port is set to zero, then the O/S will choose a port for you. Otherwise you can choose your own. Note that on a Unix system, you cannot listen on a port below 1024 unless you are the system supervisor, root, and ports below 128 are standardised by the IETF. The example program chooses port 1200 for no particular reason. The TCP address is given as ":1200" - all interfaces, port 1200.
net
参数可以设置为字符串"tcp", "tcp4"或者"tcp6"中的一个。如果你想监听所有网络接口,IP地址应设置为0,或如果你只是想监听一个简单网络接口,IP地址可以设置为该网络的地址。如果端口设置为0,O/S会为你选择一个端口。否则,你可以选择你自己的。需要注意的是,在Unix系统上,除非你是监控系统,否则不能监听低于1024的端口,小于128的端口是由IETF标准化。该示例程序选择端口1200没有特别的原因。TCP地址如下":1200" - 所有网络接口, 端口1200。
The program is
程序
/* DaytimeServer
*/
package main
import (
"fmt"
"net"
"os"
"time"
)
func main() {
service := ":1200"
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
daytime := time.Now().String()
conn.Write([]byte(daytime)) // don't care about return value
conn.Close() // we're finished with this client
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
If you run this server, it will just wait there, not doing much. When a client connects to it, it will respond by sending the daytime string to it and then return to waiting for the next client.
如果你运行该服务器, 它会在那里等待, 没有做任何事。当一个客户端连接到该服务器, 它会响应发送时间(Daytime)字符串,然后继续等待下一个客户端。
Note the changed error handling in the server as compared to a client. The server should run forever, so that if any error occurs with a client, the server just ignores that client and carries on. A client could otherwise try to mess up the connection with the server, and bring it down!
相比客户端服务器更要注意对错误的处理。服务器应该永远运行,所以,如果出现任何错误与客户端,服务器只是忽略客户端继续运行。否则,客户端可以尝试搞砸了与服务器的连接,并导致服务器宕机。
We haven't built a client. That is easy, just changing the previous client to omit the initial write. Alternatively, just open up a telnet
connection to that host:
我们还没有建立一个客户端。这很简单,只是改变以前的客户端省略的初始写入。另外, 只需打开一个telnet
连接到该主机:
telnet localhost 1200
This will produce output such as
输出如下:
$telnet localhost 1200
Trying ::1...
Connected to localhost.
Escape character is '^]'.
Sun Aug 29 17:25:19 EST 2010Connection closed by foreign host.
where "Sun Aug 29 17:25:19 EST 2010" is the output from the server.
服务器输出:"Sun Aug 29 17:25:19 EST 2010"。
Multi-threaded server
多线程服务器
"echo" is another simple IETF service. This just reads what the client types, and sends it back:
"echo"是另一种简单的IETF服务。只是读取客户端数据,并将其发送回去:
/* SimpleEchoServer
*/
package main
import (
"net"
"os"
"fmt"
)
func main() {
service := ":1201"
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
handleClient(conn)
conn.Close() // we're finished
}
}
func handleClient(conn net.Conn) {
var buf [512]byte
for {
n, err := conn.Read(buf[0:])
if err != nil {
return
}
fmt.Println(string(buf[0:]))
_, err2 := conn.Write(buf[0:n])
if err2 != nil {
return
}
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
While it works, there is a significant issue with this server: it is single-threaded. While a client has a connection open to it, no other cllient can connect. Other clients are blocked, and will probably time out. Fortunately this is easly fixed by making the client handler a go-routine. We have also moved the connection close into the handler, as it now belongs there
工作时,此服务器有一个明显的问题: 它是单线程的。当有一个客户端连接到它,就没有其他的客户端可以连接上。其他客户端将被阻塞,可能会超时。幸好客户端很容易使用go-routine扩展。我们仅仅需要把连接关闭移到处理程序结束后,示例代码如下:
/* ThreadedEchoServer
*/
package main
import (
"net"
"os"
"fmt"
)
func main() {
service := ":1201"
tcpAddr, err := net.ResolveTCPAddr("ip4", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
// run as a goroutine
go handleClient(conn)
}
}
func handleClient(conn net.Conn) {
// close connection on exit
defer conn.Close()
var buf [512]byte
for {
// read upto 512 bytes
n, err := conn.Read(buf[0:])
if err != nil {
return
}
// write the n bytes read
_, err2 := conn.Write(buf[0:n])
if err2 != nil {
return
}
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
Controlling TCP connections
控制TCP连接
Timeout
超时
The server may wish to timeout a client if it does not respond quickly enough i.e. does not write a request to the server in time. This should be a long period (several minutes), because the user may be taking their time. Conversely, the client may want to timeout the server (after a much shorter time). Both do this by
服务端会断开那些超时的客户端,如果他们响应不够快,比如没有及时往服务端写一个请求。这应该是长时间(几分钟)的,因为用户可能花费了时间。相反, 客户端可能希望超时服务器(一个更短的时间后)。通过下面的来实现这两种:
func (c *TCPConn) SetTimeout(nsec int64) os.Error
before any reads or writes on the socket.
套接字读写前。
Staying alive
存活状态
A client may wish to stay connected to a server even if it has nothing to send. It can use
即使没有任何通信,一个客户端可能希望保持连接到服务器的状态。可以使用
func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
There are several other connection control methods, documented in the "net" package.
还有几个其他的连接控制方法, 可以查看"net"包。
UDP Datagrams
UDP数据报
In a connectionless protocol each message contains information about its origin and destination. There is no "session" established using a long-lived socket. UDP clients and servers make use of datagrams, which are individual messages containing source and destination information. There is no state maintained by these messages, unless the client or server does so. The messages are not guaranteed to arrive, or may arrive out of order.
在一个无连接的协议中,每个消息都包含了关于它的来源和目的地的信息。没有"session"建立在使用长寿命的套接字。UDP客户端和服务器使用的数据包,单独包含来源和目的地的信息。除非客户端或服务器这样做,否则消息的状态不会保持。这些消息不能保证一定到达,也可能保证按顺序到达。
The most common situation for a client is to send a message and hope that a reply arrives. The most common situation for a server would be to receive a message and then send one or more replies back to that client. In a peer-to-peer situation, though, the server may just forward messages to other peers.
客户端最常见的情况发送消息,并希望响应正常到达。服务器最常见的情况为将收到一条消息,然后发送一个或多个回复给客户端。而在点对点的情况下, 服务器可能仅仅是把消息转发到其他点。
The major difference between TCP and UDP handling for Go is how to deal with packets arriving from possibly multiple clients, without the cushion of a TCP session to manage things. The major calls needed are
Go下处理TCP和UDP之间的主要区别是如何处理多个客户端可能同时有数据包到达,没有一个管理TCP会话的缓冲。主要需要调用的是
func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)
func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error)
The client for a UDP time service doesn't need to make many changes, just changing …TCP…
calls to …UDP…
calls:
UDP时间服务的客户端并不需要做很多的变化,仅仅改变…TCP…
调用为…UDP…
调用:
/* UDPDaytimeClient
*/
package main
import (
"net"
"os"
"fmt"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
os.Exit(1)
}
service := os.Args[1]
udpAddr, err := net.ResolveUDPAddr("up4", service)
checkError(err)
conn, err := net.DialUDP("udp", nil, udpAddr)
checkError(err)
_, err = conn.Write([]byte("anything"))
checkError(err)
var buf [512]byte
n, err := conn.Read(buf[0:])
checkError(err)
fmt.Println(string(buf[0:n]))
os.Exit(0)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
os.Exit(1)
}
}
while the server has to make a few more:
服务器也有很少的改动:
/* UDPDaytimeServer
*/
package main
import (
"fmt"
"net"
"os"
"time"
)
func main() {
service := ":1200"
udpAddr, err := net.ResolveUDPAddr("up4", service)
checkError(err)
conn, err := net.ListenUDP("udp", udpAddr)
checkError(err)
for {
handleClient(conn)
}
}
func handleClient(conn *net.UDPConn) {
var buf [512]byte
_, addr, err := conn.ReadFromUDP(buf[0:])
if err != nil {
return
}
daytime := time.Now().String()
conn.WriteToUDP([]byte(daytime), addr)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
os.Exit(1)
}
}
Server listening on multiple sockets
服务器侦听多个套接字
A server may be attempting to listen to multiple clients not just on one port, but on many. In this case it has to use some sort of polling mechanism between the ports.
一个服务器可能不止在一个端口监听多个客户端,或是更多端口,在这种情况下,它在端口之间使用某种轮询机制。
In C, the select() call lets the kernel do this work. The call takes a number of file descriptors. The process is suspended. When I/O is ready on one of these, a wakeup is done, and the process can continue. This is cheaper than busy polling. In Go, accomplish the same by using a different goroutine for each port. A thread will become runnable when the lower-level select() discovers that I/O is ready for this thread.
在C中, 调用的内核select()可以完成这项工作。 调用需要一个文件描述符的数字。该进程被暂停。当I/O准备好其中一个,一个唤醒被完成,并且该过程可以继续。This is cheaper than busy polling. 在G中, 完成相同的功能,通过为每个端口使用一个不同的goroutine。低级别的select()时发现,I/O已经准备好该线程,一个线程将运行。
The types Conn, PacketConn and Listener
Conn,PacketConn和Listener类型
So far we have differentiated between the API for TCP and the API for UDP, using for example DialTCP
and DialUDP
returning a TCPConn
and UDPConn
respectively. The type Conn
is an interface and both TCPConn
and UDPConn
implement this interface. To a large extent you can deal with this interface rather than the two types.
迄今为止我们已经区分TCP和UDP API的不同,使用例子DialTCP
和DialUDP
分别返回一个TCPConn
和 UDPConn
。Conn
类型是一个接口,TCPConn
和UDPConn
实现了该接口。在很大程度上,你可以通过该接口处理而不是用这两种类型。
Instead of separate dial functions for TCP and UDP, you can use a single function
你可以使用一个简单的函数,而不是单独使用TCP和UDP的dial函数。
func Dial(net, laddr, raddr string) (c Conn, err os.Error)
The net
can be any of "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only) and "ip6" (IPv6-only). It will return an appropriate implementation of the Conn
interface. Note that this function takes a string rather than address as raddr
argument, so that programs using this can avoid working out the address type first.
net
可以是"tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only)和"ip6" (IPv6-only)任何一种。它将返回一个实现了Conn
接口的类型。注意此函数接受一个字符串而不是raddr
地址参数,因此,使用此程序可避免的地址类型。
Using this function makes minor changes to programs. For example, the earlier program to get HEAD information from a Web page can be re-written as
使用该函数需要对程序轻微的调整。例如, 前面的程序从一个Web页面获取HEAD信息可以被重新写为
/* IPGetHeadInfo
*/
package main
import (
"bytes"
"fmt"
"io"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
os.Exit(1)
}
service := os.Args[1]
conn, err := net.Dial("tcp", service)
checkError(err)
_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
checkError(err)
result, err := readFully(conn)
checkError(err)
fmt.Println(string(result))
os.Exit(0)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
func readFully(conn net.Conn) ([]byte, error) {
defer conn.Close()
result := bytes.NewBuffer(nil)
var buf [512]byte
for {
n, err := conn.Read(buf[0:])
result.Write(buf[0:n])
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
}
return result.Bytes(), nil
}
Writing a server can be similarly simplified using the function
使用该函数同样可以简化一个服务器的编写
func Listen(net, laddr string) (l Listener, err os.Error)
which returns an object implementing the Listener
interface. This interface has a method
返回一个实现Listener
接口的对象. 该接口有一个方法
func (l Listener) Accept() (c Conn, err os.Error)
which will allow a server to be built. Using this, the multi-threaded Echo server given earlier becomes
这将允许构建一个服务器。使用它, 将使前面给出的多线程Echo服务器改变
/* ThreadedIPEchoServer
*/
package main
import (
"fmt"
"net"
"os"
)
func main() {
service := ":1200"
listener, err := net.Listen("tcp", service)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go handleClient(conn)
}
}
func handleClient(conn net.Conn) {
defer conn.Close()
var buf [512]byte
for {
n, err := conn.Read(buf[0:])
if err != nil {
return
}
_, err2 := conn.Write(buf[0:n])
if err2 != nil {
return
}
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
If you want to write a UDP server, then there is an interface PacketConn
and a method to return an implementation of this:
如果你想写一个UDP服务器, 这里有一个PacketConn
的接口,和一个实现了该接口的方法:
func ListenPacket(net, laddr string) (c PacketConn, err os.Error)
This interface has primary methods ReadFrom
and WriteTo
to handle packet reads and writes.
这个接口的主要方法ReadFrom
和WriteTo
用来处理数据包的读取和写入。
The Go net
package recommends using these interface types rather than the concrete ones. But by using them, you lose specific methods such as SetKeepAlive
or TCPConn
and SetReadBuffer
of UDPConn
, unless you do a type cast. It is your choice.
Go的net
包建议使用接口类型而不是具体的实现类型。但是,通过使用它们,你失去了具体的方法,比如SetKeepAlive
或TCPConn
和UDPConn
的SetReadBuffer
,除非你做一个类型转换。如何选择在于你。
Raw sockets and the type IPConn
原始套接字和IPConn类型
This section covers advanced material which most programmers are unlikely to need. it deals with raw sockets, which allow the programmer to build their own IP protocols, or use protocols other than TCP or UDP
本节涵盖了大多数程序员可能需要的高级资料。它涉及raw sockets, ,允许程序员建立自己的IP协议,或使用TCP或UDP协议。
TCP and UDP are not the only protocols built above the IP layer. The site http://www.iana.org/assignments/protocol-numbers lists about 140 of them (this list is often available on Unix systems in the file /etc/protocols
). TCP and UDP are only numbers 6 and 17 respectively on this list.
TCP和UDP并不是建立在IP层之上唯一的协议。该网站:http://www.iana.org/assignments/protocol-numbers 列表上大约有140关于它们(该列表往往在Unix系统的/etc/protocols
文件上。)。TCP和UDP在这个名单上分别为6和17。
Go allows you to build so-called raw sockets, to enable you to communicate using one of these other protocols, or even to build your own. But it gives minimal support: it will connect hosts, and write and read packets between the hosts. In the next chapter we will look at designing and implementing your own protocols above TCP; this section considers the same type of problem, but at the IP layer.
Go允许你建立所谓的原始套接字,使您可以使用这些其它协议通信,或甚至建立你自己的。但它提供了最低限度的支持: 它会连接主机, 写入和读取和主机之间的数据包。在接下来的章节中,我们将着眼于设计和实现自己的基于TCP之上的协议; 这部分认为同样的问题存在于IP层。
To keep things simple, we shall use almost the simplest possible example: how to send a ping message to a host. Ping uses the "echo" command from the ICMP protocol. This is a byte-oriented protocol, in which the client sends a stream of bytes to another host, and the host replies. the format is:
为了简单起见,我们将使用几乎最简单的例子: 如何发送一个ping消息给主机。Ping使用"echo"命令的ICMP协议。这是一个面向字节协议, 客户端发送一个字节流到另一个主机, 并等待主机的答复。格式如下:
- The first byte is 8, standing for the echo message
- The second byte is zero
- The third and fourth bytes are a checksum on the entire message
- The fifth and sixth bytes are an arbitrary indentifier
- The seventh and eight bytes are an arbitrary sequence number
- The rest of the packet is user data
- 首字节是8, 表示echo消息
- 第二个字节是0
- 第三和第四字节是整个消息的校验和
- 第五和第六字节是一个任意标识
- 第七和第八字节是一个任意的序列号
- 该数据包的其余部分是用户数据
The following program will prepare an IP connection, send a ping request to a host and get a reply. You may need to have root access in order to run it successfully.
下面的程序将准备一个IP连接,发送一个ping请求到主机,并得到答复。您可能需要root权限才能运行成功。
/* Ping
*/
package main
import (
"bytes"
"fmt"
"io"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "host")
os.Exit(1)
}
addr, err := net.ResolveIPAddr("ip", os.Args[1])
if err != nil {
fmt.Println("Resolution error", err.Error())
os.Exit(1)
}
conn, err := net.DialIP("ip4:icmp", addr, addr)
checkError(err)
var msg [512]byte
msg[0] = 8 // echo
msg[1] = 0 // code 0
msg[2] = 0 // checksum, fix later
msg[3] = 0 // checksum, fix later
msg[4] = 0 // identifier[0]
msg[5] = 13 //identifier[1]
msg[6] = 0 // sequence[0]
msg[7] = 37 // sequence[1]
len := 8
check := checkSum(msg[0:len])
msg[2] = byte(check >> 8)
msg[3] = byte(check & 255)
_, err = conn.Write(msg[0:len])
checkError(err)
_, err = conn.Read(msg[0:])
checkError(err)
fmt.Println("Got response")
if msg[5] == 13 {
fmt.Println("identifier matches")
}
if msg[7] == 37 {
fmt.Println("Sequence matches")
}
os.Exit(0)
}
func checkSum(msg []byte) uint16 {
sum := 0
// assume even for now
for n := 1; n < len(msg)-1; n += 2 {
sum += int(msg[n])*256 + int(msg[n+1])
}
sum = (sum >> 16) + (sum & 0xffff)
sum += (sum >> 16)
var answer uint16 = uint16(^sum)
return answer
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
func readFully(conn net.Conn) ([]byte, error) {
defer conn.Close()
result := bytes.NewBuffer(nil)
var buf [512]byte
for {
n, err := conn.Read(buf[0:])
result.Write(buf[0:n])
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
}
return result.Bytes(), nil
}
Conclusion
结论
This chapter has considered programming at the IP, TCP and UDP levels. This is often necessary if you wish to implement your own protocol, or build a client or server for an existing protocol.
本章着重IP, TCP和UDP级别的编程。如果你想实现自己的协议,或用现有的协议建立一个客户端或服务器,这些内容往往很重要。
Copyright Jan Newmarch, jan@newmarch.name
版权所有 Jan Newmarch, jan@newmarch.name
If you like this book, please contribute using Flattr
or donate using PayPal