Go套接字教程展示了如何在Golang中使用套接字。套接字编程是低级的。本教程的目的是介绍网络编程,包括这些底层细节。有更高级别的API在现实场景中可能更实用。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
网络协议
TCP/IP是设备用来通过Internet和大多数本地网络进行通信的一套协议。TCP更可靠,具有广泛的错误检查,并且需要更多资源。它由HTTP、SMTP或FTP等服务使用。UDP的可靠性低得多,错误检查有限,并且需要的资源较少。它被VoIP等服务使用。
去网包
Gonet
包为网络I/O提供了一个可移植的接口,包括TCP/IP、UDP、域名解析和Unix域套接字。
func Dial(network, address string) (Conn, error)
Dial
函数连接到指定网络上的地址——它打开一个套接字。我们使用Write
函数写入套接字,使用Read
函数从套接字读取数据。
已知的网络是:
- tcp
- tcp4
- tcp6
- udp
- udp4
- udp6
- ip
- ip4
- ip6
- unix
- unixgram
- unixpacket
对于TCP和UDP网络,地址的格式为host:port
。host必须是文字IP地址,或者可以解析为IP地址的主机名。使用TCP时,主机解析为多个IP地址,Dial
函数会按顺序尝试每个IP地址,直到成功为止。
GoUDP套接字示例
UDP是一种在网络上传输独立数据包的通信协议,不保证到达且不保证传递顺序。使用UDP的一种服务是echo。
Echo协议是RFC862中定义的Internet协议套件中的一项服务。Echo协议可以在端口号7上使用TCP或UDP。服务器发回它收到的数据的相同副本。
我们在本地Debian系统上设置了回显服务。
$ cat /etc/services | grep echo | head -4 echo 7/tcp echo 7/udp echo 4/ddp # AppleTalk Echo Protocol
端口7保留用于回显服务。
出于安全考虑,回声服务在大多数情况下被禁用。因此,我们在本地网络中创建自己的服务。
我们在本地网络中的另一台计算机上启动echo服务。
# apt install xinetd
我们安装xinetd
包。该软件包包含xinetd
守护进程,它是一个TCP封装的超级服务,用于访问流行网络服务的子集,包括echo、FTP、IMAP和telnet。
... # This is the udp version. service echo { disable = no type = INTERNAL id = echo-dgram socket_type = dgram protocol = udp user = root wait = yes }
在/etc/xinetd.d/echo
文件中,我们将disable
选项设置为no。
# systemctl start xinetd
我们启动服务。
package main import ( "fmt" "log" "net" "os" ) func main() { if len(os.Args) != 2 { fmt.Println("Usage: echo_client message") os.Exit(1) } msg := os.Args[1] con, err := net.Dial("udp", "debian:7") checkErr(err) defer con.Close() _, err = con.Write([]byte(msg)) checkErr(err) reply := make([]byte, 1024) _, err = con.Read(reply) checkErr(err) fmt.Println("reply:", string(reply)) } func checkErr(err error) { if err != nil { log.Fatal(err) } }
该示例向本地网络机器上的回显服务发送一条小消息。该消息被回显。
con, err := net.Dial("udp", "debian:7")
使用Dial
函数,我们在UDP网络的端口7上创建到Debian系统的套接字。
_, err = con.Write([]byte(msg))
我们使用Write
将消息写入套接字。
reply := make([]byte, 1024) _, err = con.Read(reply)
我们使用make
函数创建一个字节切片。然后我们创建对该切片的响应。
fmt.Println("reply:", string(reply))
最后,我们在终端上显示响应。
$ go run echo_client.go cau reply: cau
GoTCP套接字示例
TCP在通过IP网络通信的主机上运行的应用程序之间提供可靠、有序和错误检查的八位字节流传输。
$ cat /etc/services | grep qotd qotd 17/tcp quote
端口17保留用于当日服务的报价。
每日报价服务是一种有用的调试和测量工具。每日报价服务只发送一条短消息,而不考虑输入。
package main import ( "fmt" "log" "net" ) func main() { con, err := net.Dial("tcp", "djxmmx.net:17") checkErr(err) defer con.Close() msg := "" _, err = con.Write([]byte(msg)) checkErr(err) reply := make([]byte, 1024) _, err = con.Read(reply) checkErr(err) fmt.Println(string(reply)) } func checkErr(err error) { if err != nil { log.Fatal(err) } }
该示例创建了一个连接到QOTD服务的客户端程序。
con, err := net.Dial("tcp", "djxmmx.net:17")
TCP套接字是用net.Dial
创建的。我们提供主机名和端口号。请注意,像这样的服务是短暂的;它们可能随时被移除。
msg := "" _, err = con.Write([]byte(msg))
我们向套接字发送一条空消息。
_, err = con.Read(reply)
我们使用Read
读取响应。
fmt.Println(string(reply))
我们打印响应。
$ go run qotd.go "The secret of being miserable is to have leisure to bother about whether you are happy or not. The cure for it is occupation." George Bernard Shaw (1856-1950)
去socketHEAD请求
HEAD请求是没有消息正文的HTTPGET请求。请求/响应的标头包含元数据,例如HTTP协议版本或内容类型。
package main import ( "fmt" "io/ioutil" "log" "net" ) func main() { con, err := net.Dial("tcp", "webcode.me:80") checkError(err) req := "HEAD / HTTP/1.0\r\n\r\n" _, err = con.Write([]byte(req)) checkError(err) res, err := ioutil.ReadAll(con) checkError(err) fmt.Println(string(res)) } func checkError(err error) { if err != nil { log.Fatal(err) } }
在代码示例中,我们向webcode.me发送了一个HEAD请求。
req := "HEAD / HTTP/1.0\r\n\r\n"
head请求是通过HEAD
命令发出的,后跟资源URL和HTTP协议版本。请注意,\r\n
字符是通信过程的必需部分。RFC7231文档中描述了详细信息。
$ go run head_req.go HTTP/1.1 200 OK Server: nginx/1.6.2 Date: Tue, 29 Jun 2021 13:09:11 GMT Content-Type: text/html Content-Length: 348 Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT Connection: close ETag: "5d32ffc5-15c" Accept-Ranges: bytes
去HTTPGET请求
HTTPGET方法请求指定资源的表示。使用GET的请求应该只检索数据。
package main import ( "fmt" "io/ioutil" "log" "net" ) func main() { con, err := net.Dial("tcp", "webcode.me:80") checkError(err) req := "GET / HTTP/1.0\r\n" + "Host: webcode.me\r\n" + "User-Agent: Go client\r\n\r\n" _, err = con.Write([]byte(req)) checkError(err) res, err := ioutil.ReadAll(con) checkError(err) fmt.Println(string(res)) } func checkError(err error) { if err != nil { log.Fatal(err) } }
该示例使用GET请求读取webcode.me的主页。
req := "GET / HTTP/1.0\r\n" + "Host: webcode.me\r\n" + "User-Agent: Go client\r\n\r\n"
我们向套接字写入一个简单的GET请求。
$ go run get_req.go HTTP/1.1 200 OK Server: nginx/1.6.2 Date: Tue, 29 Jun 2021 13:12:48 GMT Content-Type: text/html Content-Length: 348 Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT Connection: close ETag: "5d32ffc5-15c" Access-Control-Allow-Origin: * Accept-Ranges: bytes <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My html page</title> </head> <body> <p> Today is a beautiful day. We go swimming and fishing. </p> <p> Hello there. How are you? </p> </body> </html>
去套接字发送邮件
要通过套接字发送电子邮件,我们使用SMTP命令,例如HELO、MAILFROM和DATA。
package main import ( "fmt" "io/ioutil" "log" "net" ) func main() { from := "john.doe@example.com" to := "root@core9" name := "John Doe" subject := "Hello" body := "Hello there" host := "core9:25" con, err := net.Dial("tcp", host) checkError(err) req := "HELO core9\r\n" + "MAIL FROM: " + from + "\r\n" + "RCPT TO: " + to + "\r\n" + "DATA\r\n" + "From: " + name + "\r\n" + "Subject: " + subject + "\r\n" + body + "\r\n.\r\n" + "QUIT\r\n" _, err = con.Write([]byte(req)) checkError(err) res, err := ioutil.ReadAll(con) checkError(err) fmt.Println(string(res)) } func checkError(err error) { if err != nil { log.Fatal(err) } }
该示例将电子邮件发送到本地网络上托管电子邮件服务器的计算机。
$ go run send_mail.go 220 core9 ESMTP Sendmail 8.15.2/8.15.2; Wed, 30 Jun 2021 14:21:21 +0200 (CEST) 250 core9 Hello spartan.local [192.168.0.20], pleased to meet you 250 2.1.0 john.doe@example.com... Sender ok 250 2.1.5 root@core9... Recipient ok 354 Enter mail, end with "." on a line by itself 250 2.0.0 15UCLLd3001374 Message accepted for delivery 221 2.0.0 core9 closing connection
我们发送电子邮件。
From john.doe@example.com Wed Jun 30 14:21:21 2021 Return-Path: <john.doe@example.com> Received: from core9 (spartan.local [192.168.0.20]) by core9 (8.15.2/8.15.2) with SMTP id 15UCLLd3001374 for root@core9; Wed, 30 Jun 2021 14:21:21 +0200 (CEST) (envelope-from john.doe@example.com) Date: Wed, 30 Jun 2021 14:21:21 +0200 (CEST) Message-Id: <202106301221.15UCLLd3001374@core9> From: John.Doe Subject: Hello To: undisclosed-recipients:; Status: RO Hello there
我们在接收端检查电子邮件。
在本教程中,我们使用了Go中的套接字。
列出所有Go教程。