Go语言Socket编程(基于TCP,附带实例)
Socket 起源于 UNIX,在 UNIX 系统中,Socket 作为一种特殊的文件描述符,用于实现网络通信。
UNIX 的基本哲学是一切皆文件。在 UNIX 中,所有的文件都可以基于打开→读写→关闭模式进行操作,而 Socket 就是这种模式的一种实现。
使用 Socket() 函数创建一个新的 Socket,它会返回一个整型的 Socket,这个描述符在后续的建立连接、数据传输等操作中都会被使用,使用方式类似于其他文件描述符。
Socket 通信需要用到两个要素,它们分别是网络协议和地址:
下面来看看基于 TCP 协议的 Socket 编程的实现方式。在基于 TCP 协议的网络通信中,服务端每次与客户端通信都必须建立握手。
建立握手的示意图如下图所示:

图 1 基于TCP协议建立握手的示意图
① 导入 net 包,它提供了与网络相关的基本功能。
② 使用 net.Listen 方法创建监听器(Listener)。具体操作为先绑定并监听端口,然后等待客户端与其建立连接。关键代码如下:
③ 接受客户端连接。net.Listen 的 listener.Accept 方法用于监听并接受来自客户端的连接。如果客户端尚未建立连接,该方法会阻塞等待。当客户端连接成功时,Accept 方法返回一个用于与客户端通信的新连接对象(net.Conn 接口类型的对象),开发者可以通过该连接对象进行数据的读、写操作。
如果在等待新连接的过程中发生了错误(例如监听器被关闭),listener.Accept 方法会返回一个非 nil 的错误。想要持续接受客户端的连接,可以使用 for 循环持续地调用 Accept 方法。关键代码如下:
④ 创建协程处理客户端请求。为了实现并发地处理多个客户端请求,服务端通常会针对每个连接创建一个协程。
⑤ 使用新连接(Socket)进行通信。此过程通常使用 conn.Read 和 conn.Write 方法实现。关键代码如下:
⑥ 处理客户端请求。根据客户端发送的数据执行相应的操作,并返回结果给客户端。
⑦ 关闭连接。在完成与客户端的通信后,使用 conn.Close 方法关闭 Socket 连接。
⑧ 关闭监听器。如果不再接受新的客户端连接,使用 listener.Close 方法关闭监听器。
① 导入 net 包,它提供了与网络相关的基本功能。
② 建立连接。使用 net.Dial 函数连接到服务端,指定使用的协议(如 TCP 协议)和服务端的地址、端口。如果连接成功,该函数将返回一个用于与服务端通信的 ocket 连接。关键代码如下:
③ 使用连接进行通信。通过 Socket 连接向服务端发送数据,这里通常会使用 conn.Write 函数实现相应的功能。数据可以是字节切片或字符串等。关键代码如下:
④ 读取和处理服务端返回的数据。先通过 Socket 连接读取服务端返回的数据,使用 conn.Read 函数读取数据并存储在一个字节切片中,然后解析服务端返回的数据,并执行相应的操作。关键代码如下:
⑤ 关闭连接。在完成与服务端的通信后,使用 conn.Close 函数关闭 Socket 连接。
服务端与客户端的通信是双向的。客户端需要不断地向服务端发送数据,同时也要接收来自服务端数据。因此,发送和接收操作应分别放在不同的协程中进行。主协程负责循环接收服务器返回的数据并输出,子协程则负责循环读取用户从键盘输入的数据,并将其发送给服务端。
在读取键盘输入数据的子协程中定义一个切片 str,使用 os.Stdin.Read(str) 函数将读取到的数据保存起来。这样,客户端也实现了并发多任务处理。
在基于 UDP 协议的并发 Socket 编程中,由于 UDP 协议没有握手,因此服务器端无须额外创建监听套接字,只需要指定 IP 地址和端口,然后监听该地址,等待客户端发起连接即可。一旦建立连接,便可以进行通信。
UNIX 的基本哲学是一切皆文件。在 UNIX 中,所有的文件都可以基于打开→读写→关闭模式进行操作,而 Socket 就是这种模式的一种实现。
使用 Socket() 函数创建一个新的 Socket,它会返回一个整型的 Socket,这个描述符在后续的建立连接、数据传输等操作中都会被使用,使用方式类似于其他文件描述符。
Socket 通信需要用到两个要素,它们分别是网络协议和地址:
- 网络协议:分为 TCP 和 UDP 两种。
- 地址:IP地址+端口号,如 127.0.0.1:8000 或 :8000。
下面来看看基于 TCP 协议的 Socket 编程的实现方式。在基于 TCP 协议的网络通信中,服务端每次与客户端通信都必须建立握手。
建立握手的示意图如下图所示:

图 1 基于TCP协议建立握手的示意图
Go语言Socket通信的过程
为了更深入地理解基于 TCP 协议的 Socket 通信,接下来我们详细分析服务端和客户端是如何利用 Go 语言实现这一过程的。1) 服务端
要使用 Go 语言实现基于 TCP 协议的 Socket 通信,服务端需要执行以下步骤:① 导入 net 包,它提供了与网络相关的基本功能。
② 使用 net.Listen 方法创建监听器(Listener)。具体操作为先绑定并监听端口,然后等待客户端与其建立连接。关键代码如下:
//创建监听器 listener, err := net.Listen("tcp", "127.0.0.1:8000") if err != nil { fmt.Printf("net.Listen() err:%v\n", err) return }
③ 接受客户端连接。net.Listen 的 listener.Accept 方法用于监听并接受来自客户端的连接。如果客户端尚未建立连接,该方法会阻塞等待。当客户端连接成功时,Accept 方法返回一个用于与客户端通信的新连接对象(net.Conn 接口类型的对象),开发者可以通过该连接对象进行数据的读、写操作。
如果在等待新连接的过程中发生了错误(例如监听器被关闭),listener.Accept 方法会返回一个非 nil 的错误。想要持续接受客户端的连接,可以使用 for 循环持续地调用 Accept 方法。关键代码如下:
for { fmt.Println("服务器等待客户端连接...") conn, err := listener.Accept() if err != nil { fmt.Printf("listener.Accept() err:%v\n", err) return } … }
④ 创建协程处理客户端请求。为了实现并发地处理多个客户端请求,服务端通常会针对每个连接创建一个协程。
⑤ 使用新连接(Socket)进行通信。此过程通常使用 conn.Read 和 conn.Write 方法实现。关键代码如下:
//注意,conn.Read方法在执行读取操作时,会将命令行里的换行符也给读取了 //在UNIX上换行符是\n,在Windows上是\r\n n, err := conn.Read(buf) if err != nil { if err == io.EOF { fmt.Println("客户端退出!") break } else { fmt.Printf("conn.Read() err:%v\n", err) return } } fmt.Printf("服务器读到数据:%v", string(buf[:n])) //小写转大写,发回给客户端 conn.Write(bytes.ToUpper(buf[:n]))
⑥ 处理客户端请求。根据客户端发送的数据执行相应的操作,并返回结果给客户端。
⑦ 关闭连接。在完成与客户端的通信后,使用 conn.Close 方法关闭 Socket 连接。
⑧ 关闭监听器。如果不再接受新的客户端连接,使用 listener.Close 方法关闭监听器。
2) 客户端
要使用 Go语言实现基于 TCP 协议的 Socket 通信,客户端需要执行以下步骤:① 导入 net 包,它提供了与网络相关的基本功能。
② 建立连接。使用 net.Dial 函数连接到服务端,指定使用的协议(如 TCP 协议)和服务端的地址、端口。如果连接成功,该函数将返回一个用于与服务端通信的 ocket 连接。关键代码如下:
//发起连接请求 conn, err := net.Dial("tcp", "127.0.0.1:8000") if err != nil { fmt.Printf("net.Dial() err:%v\n", err) return }
③ 使用连接进行通信。通过 Socket 连接向服务端发送数据,这里通常会使用 conn.Write 函数实现相应的功能。数据可以是字节切片或字符串等。关键代码如下:
//获取用户的键盘输入(os.Stdin),并将输入的数据发送给服务器 go func() { str := make([]byte, 1024) for { n, err := os.Stdin.Read(str) if err != nil { fmt.Printf("os.Stdin.Read() err:%v\n", err) continue } //将数据发送给服务器 conn.Write(str[:n]) } }()
④ 读取和处理服务端返回的数据。先通过 Socket 连接读取服务端返回的数据,使用 conn.Read 函数读取数据并存储在一个字节切片中,然后解析服务端返回的数据,并执行相应的操作。关键代码如下:
for { buf := make([]byte, 1024) n, err := conn.Read(buf) if err != nil { if err == io.EOF { fmt.Println("服务端退出了!!!") return } else { fmt.Printf("conn.Read() err:%v\n", err) continue } } fmt.Printf("客户端读到服务器返回的数据:%s",buf[:n]) }
⑤ 关闭连接。在完成与服务端的通信后,使用 conn.Close 函数关闭 Socket 连接。
服务端与客户端的通信是双向的。客户端需要不断地向服务端发送数据,同时也要接收来自服务端数据。因此,发送和接收操作应分别放在不同的协程中进行。主协程负责循环接收服务器返回的数据并输出,子协程则负责循环读取用户从键盘输入的数据,并将其发送给服务端。
在读取键盘输入数据的子协程中定义一个切片 str,使用 os.Stdin.Read(str) 函数将读取到的数据保存起来。这样,客户端也实现了并发多任务处理。
在基于 UDP 协议的并发 Socket 编程中,由于 UDP 协议没有握手,因此服务器端无须额外创建监听套接字,只需要指定 IP 地址和端口,然后监听该地址,等待客户端发起连接即可。一旦建立连接,便可以进行通信。