在go语言标准库中,net包提供了可移植的网络I/O接口,包括TCP/IP、UDP、域名解析和Unix域socket。
1 服务端
1. 解析地址
在TCP服务端我们需要监听一个TCP地址,因此建立服务端前我们需要生成一个正确的TCP地址,这就需要用到 Resolve
函数。
1
2
3
4
5
6
7
8
|
// ResolveTCPAddr函数会输出一个TCP连接地址和一个错误信息
func ResolveTCPAddr(network, address string) (*TCPAddr, error)
// 解析IP地址
func ResolveIPAddr(net, addr string) (*IPAddr, error)
// 解析UDP地址
func ResolveUDPAddr(net, addr string) (*UDPAddr, error)
// 解析Unix地址
func ResolveUnixAddr(net, addr string) (*UnixAddr, error)
|
2. 监听请求
我们可以通过 Listen
方法监听我们解析后的网络地址。
1
2
3
4
5
6
7
8
9
10
11
12
|
// 监听net类型,地址为laddr的地址
func Listen(net, laddr string) (Listener, error)
// 监听TCP地址
func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
// 监听IP地址
func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error)
// 监听UDP地址
func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error)
func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error)
// 监听Unix地址
func ListenUnixgram(net string, laddr *UnixAddr) (*UnixConn, error)
func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error)
|
3. 接受请求
TCPAddr 实现了两个接受请求的 Accept
方法,两者代码实现其实是一样的,唯一的区别是第一种返回了一个对象,第二种返回了一个接口。
1
2
|
func (l *TCPListener) AcceptTCP() (*TCPConn, error)
func (l *TCPListener) Accept() (Conn, error)
|
4. 连接配置
1
2
3
4
|
// 配置监听器超时时间:超过t之后监听器自动关闭,0表示不设置超时时间
func (l *TCPListener) SetDeadline(t time.Time) error
// 关闭监听器
func (l *TCPListener) Close() error
|
5. 编写一个服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func main() {
// 解析服务端监听地址,本例以tcp为例
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8000")
if err != nil {
log.Panic(err)
}
// 创建监听器
listen, err := net.ListenTCP("tcp", addr)
if err != nil {
log.Panic(err)
}
for {
// 监听客户端连接请求
conn, err := listen.AcceptTCP()
if err != nil {
continue
}
// 处理客户端请求 这个函数可以自己编写
go HandleConnectionForServer(conn)
}
}
|
2 客户端
1. 解析地址
在TCP服务端我们需要监听一个TCP地址,因此建立服务端前我们需要生成一个正确的TCP地址,这就需要用到 Resolve
函数了。
1
2
|
// ResolveTCPAddr函数会输出一个TCP连接地址和一个错误信息
func ResolveTCPAddr(network, address string) (*TCPAddr, error)
|
2. 发送连接请求
1
2
3
4
5
6
7
8
9
10
11
12
|
// DialIP的作用类似于IP网络的拨号
func DialIP(network string, laddr, raddr *IPAddr) (*IPConn, error)
// Dial 连接到指定网络上的地址,涵盖
func Dial(network, address string) (Conn, error)
// 这个方法只是在Dial上面设置了超时时间
func DialTimeout(network, address string, timeout time.Duration) (Conn, error)
// DialTCP 专门用来进行TCP通信的
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
// DialUDP 专门用来进行UDP通信的
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
// DialUnix 专门用来进行 Unix 通信
func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)
|
3. 编写一个客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func main() {
// 解析服务端地址
RemoteAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8000")
if err != nil {
panic(err)
}
// 解析本地连接地址
LocalAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1")
if err != nil {
panic(err)
}
// 连接服务端
conn, err := net.DialTCP("tcp", LocalAddr, RemoteAddr)
if err != nil {
panic(err)
}
// 连接管理
HandleConnectionForClient(conn)
}
|
3 域名解析
1. DNS正向解析
CNAME 被称为规范名字。这种记录允许您将多个名字映射到同一台计算机。 通常用于同时提供WWW和MAIL服务的计算机。
1
2
3
4
5
6
|
//域名解析到cname
func LookupCNAME(name string) (cname string, err error)
//域名解析到地址
func LookupHost(host string) (addrs []string, err error)
//域名解析到地址[]IP结构体.可以对具体ip进行相关操作(是否回环地址,子网,网络号等)
func LookupIP(host string) (addrs []IP, err error)
|
2. DNS反向解析
1
2
|
// 根据ip地址查找主机名地址(必须得是可以解析到的域名)[dig -x ipaddress]
func LookupAddr(addr string) (name []string, err error)
|
4 其他常用接口
1. Conn接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
type Conn interface {
// Read从连接中读取数据
// Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Read(b []byte) (n int, err error)
// Write从连接中写入数据
// Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Write(b []byte) (n int, err error)
// Close方法关闭该连接
// 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误
Close() error
// 返回本地网络地址
LocalAddr() Addr
// 返回远端网络地址
RemoteAddr() Addr
// 设定该连接的读写deadline,等价于同时调用SetReadDeadline和SetWriteDeadline
// deadline是一个绝对时间,超过该时间后I/O操作就会直接因超时失败返回而不会阻塞
// deadline对之后的所有I/O操作都起效,而不仅仅是下一次的读或写操作
// 参数t为零值表示不设置期限
SetDeadline(t time.Time) error
// 设定该连接的读操作deadline,参数t为零值表示不设置期限
SetReadDeadline(t time.Time) error
// 设定该连接的写操作deadline,参数t为零值表示不设置期限
// 即使写入超时,返回值n也可能>0,说明成功写入了部分数据
SetWriteDeadline(t time.Time) error
}
|
2. PacketConn接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
type PacketConn interface {
// ReadFrom方法从连接读取一个数据包,并将有效信息写入b
// ReadFrom方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
// 返回写入的字节数和该数据包的来源地址
ReadFrom(b []byte) (n int, addr Addr, err error)
// WriteTo方法将有效数据b写入一个数据包发送给addr
// WriteTo方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
// 在面向数据包的连接中,写入超时非常罕见
WriteTo(b []byte, addr Addr) (n int, err error)
// Close方法关闭该连接
// 会导致任何阻塞中的ReadFrom或WriteTo方法不再阻塞并返回错误
Close() error
// 返回本地网络地址
LocalAddr() Addr
// 设定该连接的读写deadline
SetDeadline(t time.Time) error
// 设定该连接的读操作deadline,参数t为零值表示不设置期限
// 如果时间到达deadline,读操作就会直接因超时失败返回而不会阻塞
SetReadDeadline(t time.Time) error
// 设定该连接的写操作deadline,参数t为零值表示不设置期限
// 如果时间到达deadline,写操作就会直接因超时失败返回而不会阻塞
// 即使写入超时,返回值n也可能>0,说明成功写入了部分数据
SetWriteDeadline(t time.Time) error
}
|
3. Error接口
1
2
3
4
5
6
|
package net
type Error interface{
Timeout() bool // 错误是否超时
Temporary() bool // 是否是临时错误
}
|
5 示例
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
/*服务器代码*/
package main
import (
"fmt"
"net"
)
func main() {
//创建listener
listener, err := net.Listen("tcp", "localhost:50000") //用来监听和接收来自客户端的请求
if err != nil {
fmt.Println("Error Listening", err.Error()) //Error是个什么?
return //终止程序
}
//无限循环,监听并接受来自客户端的连接
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting", err.Error())
return //终止程序
}
go doServerStuff(conn) //?
}
}
func doServerStuff(conn net.Conn) {
for {
buf := make([]byte, 512)
len, err := conn.Read(buf) //获取客户端发送字节数
if err != nil {
fmt.Println("Error reading", err.Error())
return //终止程序
}
fmt.Printf("Received data:%v\n", string(buf[:len]))
}
}
|
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
func main() {
//创建和服务器的连接
conn, err := net.Dial("tcp", "localhost:50000")
if err != nil {
fmt.Println("Error dialing", err.Error()) //由于目标计算机积极拒绝而无法创建连接
return //终止程序
}
inputReader := bufio.NewReader(os.Stdin) //接收来自键盘的输入
fmt.Println("First,what is your name?")
clientName, _ := inputReader.ReadString('\n')
trimmedClient := strings.Trim(clientName, "\r\n") // Windows 平台下用 "\r\n",Linux平台下使用 "\n"
//给服务器发送信息知道程序退出
for {
fmt.Println("What to send to the server? Type Q to quit.")
input, _ := inputReader.ReadString('\n')
trimmedInput := strings.Trim(input, "\r\n")
if trimmedInput=="Q"{
return
}
_,err=conn.Write([]byte(trimmedClient+" says: "+trimmedInput))
}
}
|
socket.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package main
import(
"fmt"
"io"
"net"
)
func main(){
var(
host="www.apache.org"
port="80"
remote=host+":"+port
msg string="GET / \n"
data=make([]uint8,4096)
read=true
count=0
)
//创建一个socket
conn,err:=net.Dial("tcp",remote)
//发送我们的消息:一个http GET请求
io.WriteString(conn,msg)
//读取服务器的响应
for read{
count,err=conn.Read(data)
read=(err==nil)
fmt.Printf(string(data[0:count]))
}
conn.Close()
}
|
dial.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//make a connection with www.example.org:
package main
import(
"fmt"
"net"
"os"
)
func main(){
conn,err:=net.Dial("tcp","192.0.32.10:80") //tcp ipv4
checkConnection(conn,err)
conn,err=net.Dial("udp","192.0.32.10:80") //udp
checkConnection(conn,err)
conn,err=net.Dial("tcp","[2620:0:2d0:200::10]:80") //tcp ipv6
checkConnection(conn,err)
}
func checkConnection(conn net.Conn,err error){
if err!=nil{
fmt.Printf("error %v connecting!",err)
os.Exit(1)
}
fmt.Printf("Connection is made with %v\n",conn)
}
|
server_simple.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
package main
import(
"flag"
"fmt"
"net"
"syscall"
)
const maxread=25
func main(){
flag.Parse() //服务器地址和端口通过命令行传入参数,并通过flag包来读取这些参数
if flag.NArg() != 2 { //检查是否按照期望传入了2个参数
panic("usage: host port") //此函数停止执行,并将控制权返还给其调用者
}
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1)) //格式化成字符串
listener := initServer(hostAndPort)
for {
conn, err := listener.Accept() //接受请求,返回conn对象
checkError(err, "Accept: ")
go connectionHandler(conn)
}
}
func initServer(hostAndPort string)net.Listener{
serverAddr,err:=net.ResolveTCPAddr("tcp",hostAndPort) //解析TCP地址
checkError(err,"Resolving address:port failed: '"+hostAndPort+"'")
listener,err:=net.ListenTCP("tcp",serverAddr) //监听请求
checkError(err,"ListenTCP: ")
println("Listening to: ",listener.Addr().String())
return listener
}
func connectionHandler(conn net.Conn){
connFrom:=conn.RemoteAddr().String() //获取客户端的地址
println("Connection from: ",connFrom)
sayHello(conn)
for{
var ibuf []byte=make([]byte, maxread+1) //设置maxread防止溢出
length,err:=conn.Read(ibuf[0:maxread]) //读取连接中的内容
ibuf[maxread]=0
switch err{
case nil:
handleMsg(length,err,ibuf)
case syscall.EAGAIN:
continue //重新尝试连接
default:
goto DISCONNECT
}
}
DISCONNECT:
err:=conn.Close() //关闭连接
println("Closed connection: ",connFrom)
checkError(err,"Close: ")
}
func sayHello(to net.Conn) {
obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'}
wrote, err := to.Write(obuf) //发送message给客户端
checkError(err, "Write: wrote "+string(wrote)+" bytes.")
}
func handleMsg(length int, err error, msg []byte) {
if length > 0 {
print("<", length, ":")
for i := 0; ; i++ {
if msg[i] == 0 {
break
}
fmt.Printf("%c", msg[i])
}
print(">")
}
}
func checkError(error error, info string) {
if error != nil {
panic("ERROR: " + info + " " + error.Error()) // terminate program
}
}
|