Server listening on multiple sockets
一个服务器可能需要在不同端口监听不同的客户端,在这种情况下,必须在多个端口之间使用某种形式的轮询机制。
在C语言中,select()调用后会让内核来做这个工作。这个函数接受若干文件描述符。这个过程是被暂停的。当其中的一个I/O准备好后,完成唤醒,并且续继这个过程。这比轮询更轻便。在Go语言中, 完成相同的处理是为每个端口使用不同的goroutine. 当低层的select()发现I/O对于这个线程是准备的时候,这个线程变成可运行。
The types Conn, PacketConn and Listener
到目前为止,我们知道TCP和UDP API的不同, 比如DialTCP, DialUDP返回一个TCPConn和UDPConn. Conn是一个接口,TCPConn和UDPConn都实现了这个接口,在很大的程度上,你可以直接处理这个接口,而不是具体的这两个类型。
你可以使用以下这个函数,代替上面的两个拨号函数
func Dial(net, laddr, raddr string) (c Conn, err os.Error)
net参数可以是"tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6". 注意这个函数接受的是一个字符串,而不是一个TCPAddr或者UDPAddr. 所以可以不需要向之前的函数使用ResolveTCP("tcp4", "localhost:1200")处理函数的类型.
以下是实际的例子
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 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
}
服务器可以使用相似的函数
func Listen(net, laddr string) (l Listener, err os.Error)
它返回的对像实现了Listener接口。这个接口有一个方法
func (l Listener) Accept() (c Conn, err os.Error)
以下是之前的多线程Echo服务器
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
}
}
}
如果你想写一个UDP服务器,则需要使用到PacketConn接口
func ListenPacket(net, laddr string) (c PacketConn, err os.Error)
这个有两个主要的方法ReadForm和WriteTo用来读写数据包
Go net package推荐使用这些接口类型,而不是具体的类型。但如果使用它们的话,你会丢失一些指定的方法,比如SetKeepAlive或者TCPConn和UDPConn的SetReadBuffer。
Raw sockets and the type IPConn
这一节中我们讲一上高级知识,大部分的程序员都不需要使用到它。它就是处理原生的数据包。它允许程序员建立自己的IP协议或者其它的协议,而不是TCP or UDP.
除了TCP和UDP这外,也有其它的协议是建立在IP之上的。在http://www.iana.org/assignments/protocol-numbers列出了140种这样的协议(也可以查看/etc/protocols). TCP和UDP在上面的编号为6和17。
Go允许你建立称为原始的套接字(raw sockets), 使你可以使用列表中的一个协议与另一个协议进行通信,甚至可以建立自己的协议。但原始套接字只有最小的功能:它将连接到主机,然后读写数据包。在下一章中我们将学习到如何在TCP之上设计和实现自己的协议。这一节只考虑相同类型的问题,但它是在IP层。
为了保持简单性,我们将使用最简单的例子来讲解:如何发送一个ping message给主机。Ping是在ICMP协议中使用"echo"命令,即客户端发送一个字节流给另一个主机,主机在返回。格式如下:
- 第1个字节是8, 表示它是一条echo message
- 第2个字节是0 * 第3和第4个字节是整个消息的校验和
- 第5和第6个字节是一个任意的标识符
- 第7和第8个字切是一个任意的序列号
- 剩下的数据包是用户数据
以下这个程序将准备一个IP连接,发送一个ping请求,并且从主机获得返回。
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "host")
os.Exit(1)
}
laddr := net.IPAddr{IP: net.ParseIP("0.0.0.0")}
addr, err := net.ResolveIPAddr("ip", os.Args[1]) //不需要有端口
if err != nil {
fmt.Println("Resolution error", err.Error())
os.Exit(1)
}
//DialIP 的第一个参数是ip之后跟冒号和协议名称
conn, err := net.DialIP("ip4:icmp", &laddr, addr)
checkError(err)
var msg [512]byte
msg[0] = 8
msg[1] = 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)
fmt.Printf("\n正在ping %s 具有 0 字节的数据\n", addr.String())
recv := make([]byte, 1024)
sended_packets := 0
for i := 4; i > 0; i-- {
if _, err := conn.Write(msg[0:len]); err != nil {
fmt.Println(err.Error())
return
}
sended_packets++
t_start := time.Now()
conn.SetReadDeadline(time.Now().Add(time.Second * 5))
_, err := conn.Read(recv)
if err != nil {
fmt.Println("请求超时")
continue
}
t_end := time.Now()
dur := t_end.Sub(t_start).Nanoseconds() / 1e6
fmt.Printf("来自%s的回复:时间= %dms\n", addr.String(), dur)
}
os.Exit(0)
}
/*
以4bit 为例
发送端计算:
数据: 1000 0100 校验和 0000
则反码:0111 1011 1111
叠加: 0111+1011+1111 = 0010 0001 高于4bit的, 叠加到低4位 0001 + 0010 = 0011 即为校验和
接收端计算:
数据: 1000 0100 检验和 0011
反码: 0111 1011 1100
叠加: 0111 + 1011 +1100 = 0001 1110 叠加为4bit为1111. 全为1,则正确
*/
func checkSum(msg []byte) uint16 {
var (
sum uint32
length int = len(msg)
n int
)
//按16位为单位,依次进行二进制求和
for n = 0; n < len(msg)-1; n += 2 {
sum += uint32(msg[n])*256 + uint32(msg[n+1])
}
if length%2 == 1 {
sum += uint32(msg[n])
}
//将计算的sum 32位中高16位与低16位进行计算
sum = (sum >> 16) + (sum & 0xffff)
//在上一步中,可能16位数字相加后会产生进位,所以,还要把高位中的数字加到低位中
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)
}
}