大部分底层网络的编程都离不开socket编程。HTTP编程、Web开发、IM通信、视频流传输的底层都是socket编程。关于socket编程的基础知识参考TCP/IP协议栈的相关知识。

socket源于Unix,C语言世界中的socket编程。Python采用C语言实现,其自带的socket接口可以看作是Unix socket的OOP风格的socket编程接口。大致的过程如下:

服务器端:

  1. 创建套接字
  2. 绑定地址
  3. 设置监听队列
  4. 监听请求
  5. 处理请求
  6. 关闭套接字

客户端:

  1. 创建套接字
  2. 连接服务器
  3. 发送数据
  4. 接收数据
  5. 关闭套接字

但Go语言采用一套新的接口,该接口比Unix中的socket简介多。由于Go的静态编译,使用Go编写的socket网络工具使用起来很方便。

本文先从TCP socket编程开始,然后到UDP socket,最后以一个简单的socket工具作为结尾。

TCP Socket

Go net包中TCPConn用来建立TCP客户端、TCP服务器端间的通信通道。类似于Python中的socket对象(通过socket.socket创建)通过DialTCP函数创建该对象。TCPListener服务器端socket,监听网络请求,通过ListenTCP函数创建该对象。形象地理解,可以把TCPConn看作是TCP的连接器;TCPListener看作是TCP监听器。监听器等待客户端连接,一旦有连接进来,创建TCP连接器,然后通过TCP连接器进行服务器端和客户端的通信。

下面通过TCP心跳(返回服务器端时间)来举例,编程中还有一些细节问题,这里不详细说,看代码注释。

TCP Server

TCP服务器端通过ListenTCP创建TCPListener对象。调用Accept方法进入LISTEN状态(参考TCP状态机)

编写tcp_tick_time.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
package main

import (
"fmt"
"log"
"net"
"os"
"time"
)

func echo(conn *net.TCPConn) {
tick := time.Tick(5 * time.Second) // 五秒的心跳间隔
for now := range tick {
n, err := conn.Write([]byte(now.String()))
if err != nil {
log.Println(err)
conn.Close()
return
}
fmt.Printf("send %d bytes to %s\n", n, conn.RemoteAddr())
}
}

func main() {

address := net.TCPAddr{
IP: net.ParseIP("127.0.0.1"), // 把字符串IP地址转换为net.IP类型
Port: 8000,
}

listener, err := net.ListenTCP("tcp4", &address) // 创建TCP4服务器端监听器

if err != nil {
log.Fatal(err) // Println + os.Exit(1)
}

for {
conn, err := listener.AcceptTCP()
if err != nil {
log.Fatal(err) // 错误直接退出
}

fmt.Println("remote address:", conn.RemoteAddr())
go echo(conn)
}
}


通过终端直接启动。

1
go run tcp_tick_time.go

通过简单的客户端连接,我们可以看到该程序终端的显示信息:

1
2
3
4
5
6
7
8
9
10
11
remote address: 127.0.0.1:1915
send 37 bytes to 127.0.0.1:1915
send 37 bytes to 127.0.0.1:1915
send 37 bytes to 127.0.0.1:1915
send 35 bytes to 127.0.0.1:1915
send 37 bytes to 127.0.0.1:1915
send 37 bytes to 127.0.0.1:1915
send 37 bytes to 127.0.0.1:1915
2017/08/29 9:23:49 write tcp4 127.0.0.1:8000->127.0.0.1:1915: wsasend: An established connection was aborted by the software in your host machine.
send 0 bytes to 127.0.0.1:1915

TCP Client

通过func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error)创建TCP客户端。net需要指定具体的TCP
网络类型:”tcp”, “tcp4”, “tcp6”。laddr表示本地地址,一般为nil。raddr为要连接的服务器地址。

我们写一个简单的HTTP客户端。

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 (
"net"
"log"
"fmt"
"io/ioutil"
"os"
)

func main() {
if len(os.Args) != 2 {
log.Fatalf("Usage: %s host:port", os.Args[0])
}

service := os.Args[1]
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
if err != nil {
log.Fatal(err)
}

conn, err := net.DialTCP("tcp4", nil, tcpAddr)
if err != nil {
log.Fatal(err)
}

n, err := conn.Write([]byte("HEAD / HTTP/1.1\r\n\r\n"))
if err != nil {
log.Fatal(err)
}