Go语言TCP通信详解(附带实例)
TCP/IP(transmission control protocol/internet protocol)协议,即传输控制协议/网络协议,是一种面向连接的、可靠的、基于字节流的传输层(transport layer)通信协议。
因为是面向连接的协议,所以数据像水流一样传输,存在黏包问题。所谓黏包问题,主要还是因为发送方一次性把所有数据都存入缓存区,接收方不知道消息之间的界限,不知道一次性提取多少字节的数据。
如下图所示,一个 TCP 服务端可以同时连接多个 TCP 客户端:

图 1 一个TCP服务端可以同时连接多个TCP客户端
例如,我国各个地区的用户都使用自己计算机的浏览器访问淘宝网。因为 Go 语言通过创建多个goroutine实现并发非常方便和高效,所以可以每建立一次链接就创建一个goroutine。
下面演示如何编写简单的 TCP 服务端。代码如下:
TCP 服务端编写完毕后,下面需要编写 TCP 客户端。在使用 Go 语言编写 TCP 客户端时,一般按照如下步骤展开:
下面演示如何编写 TCP 客户端。代码如下:
为了保证上述程序成功运行,要打开两个 VS Code 窗口:
一个窗口是 TCP 服务端(见下图):

图 2 TCP服务端
一个窗口是 TCP 客户端(见下图):

图 3 TCP 客户端
当运行程序时,要先运行 TCP 服务端,随即在 TCP 服务端所在 VS Code 窗口的控制台上打印如下信息:
再运行 TCP 客户端,随即在 TCP 客户端所在 VS Code 窗口的控制台上打印如下信息:
打开 TCP 服务端所在的 VS Code 窗口,在控制台上打印一行新信息:
为了让 TCP 服务端与 TCP 客户端交互通信,TCP 服务端除了要使用 net.Listen() 函数得到连接信息,还要使用 for 循环不停响应由 TCP 客户端发送的连接请求。每响应一次由 TCP 客户端发送的连接请求,就创建一个用于执行发送和接收数据操作的 goroutine。
对上述实现 TCP 服务端的代码修改如下:
此外,还要修改上述实现 TCP 客户端的代码:
因为是面向连接的协议,所以数据像水流一样传输,存在黏包问题。所谓黏包问题,主要还是因为发送方一次性把所有数据都存入缓存区,接收方不知道消息之间的界限,不知道一次性提取多少字节的数据。
如下图所示,一个 TCP 服务端可以同时连接多个 TCP 客户端:

图 1 一个TCP服务端可以同时连接多个TCP客户端
例如,我国各个地区的用户都使用自己计算机的浏览器访问淘宝网。因为 Go 语言通过创建多个goroutine实现并发非常方便和高效,所以可以每建立一次链接就创建一个goroutine。
建立TCP连接
在使用 Go 语言编写 TCP 服务端时,一般按照如下步骤展开:- 定义通信的地址和端口;
- 使用 Listen() 函数监听 TCP 的地址和端口信息,并得到连接信息;
- 使用连接信息的 Accept 函数等待连接;
- 关闭 TCP 连接。
下面演示如何编写简单的 TCP 服务端。代码如下:
package main
import (
"fmt"
"net"
)
func main() {
// 使用 net.Listen() 函数监听连接的地址与端口
listener, err := net.Listen("tcp", "127.0.0.1:3000")
if err != nil {
fmt.Printf("监听失败!发生错误:%v\n", err)
return
}
fmt.Println("服务端已开启!等待客户端的连接请求......")
// 响应由 TCP 客户端发送的连接请求
conn, err := listener.Accept()
if err != nil {
fmt.Printf("响应失败!发生错误:%v\n", err)
return
}
fmt.Println("服务端已成功连接客户端!")
defer conn.Close()
}
在编写上述 TCP 服务端时,需要明确一个重点:为了创建 TCP 连接,需要调用 net.Listen() 函数,并向该函数传入 3 个参数,即协议类型(tcp)、IP 地址和端口号。TCP 服务端编写完毕后,下面需要编写 TCP 客户端。在使用 Go 语言编写 TCP 客户端时,一般按照如下步骤展开:
- 定义通信的地址和端口;
- 使用 Dial() 函数建立与服务端的连接;
- 关闭 TCP 连接。
下面演示如何编写 TCP 客户端。代码如下:
package main
import (
"fmt"
"net"
)
func main() {
// 建立与服务端的连接
conn, err := net.Dial("tcp", "127.0.0.1:3000")
if err != nil {
fmt.Printf("连接失败!发生错误:%v\n", err.Error())
return
}
fmt.Println("客户端向服务端发送连接请求......")
defer conn.Close() // 关闭 TCP 连接
}
在编写上述 TCP 客户端时需要明确一个重点:为了创建一个 TCP 连接需要调用 net.Dial() 函数,并向该函数传入 3 个参数,即协议类型(tcp)、IP 地址和端口号。为了保证上述程序成功运行,要打开两个 VS Code 窗口:
一个窗口是 TCP 服务端(见下图):

图 2 TCP服务端
一个窗口是 TCP 客户端(见下图):

图 3 TCP 客户端
当运行程序时,要先运行 TCP 服务端,随即在 TCP 服务端所在 VS Code 窗口的控制台上打印如下信息:
服务端已开启!等待客户端的连接请求……
再运行 TCP 客户端,随即在 TCP 客户端所在 VS Code 窗口的控制台上打印如下信息:
客户端向服务端发送连接请求……
打开 TCP 服务端所在的 VS Code 窗口,在控制台上打印一行新信息:
服务端已成功连接客户端!
这说明 TCP 服务端与 TCP 客户端成功建立了连接。实现交互通信
TCP 服务端与 TCP 客户端建立连接后,就可以发送和接收数据,进而实现 TCP 服务端与 TCP 客户端交互通信。通过连接对象调用 Write() 函数,发送数据;通过连接对象调用 Read() 函数,接收数据。为了让 TCP 服务端与 TCP 客户端交互通信,TCP 服务端除了要使用 net.Listen() 函数得到连接信息,还要使用 for 循环不停响应由 TCP 客户端发送的连接请求。每响应一次由 TCP 客户端发送的连接请求,就创建一个用于执行发送和接收数据操作的 goroutine。
对上述实现 TCP 服务端的代码修改如下:
package main
import (
"fmt"
"net"
)
func main() {
// 使用 net.Listen() 函数监听连接的地址与端口
listener, err := net.Listen("tcp", "127.0.0.1:3000")
if err != nil {
fmt.Printf("监听失败!发生错误:%v\n", err)
return
}
fmt.Println("服务端已开启!等待客户端的连接请求......")
for {
// 响应由 TCP 客户端发送的连接请求
conn, err := listener.Accept()
if err != nil {
fmt.Printf("响应失败!发生错误:%v\n", err)
continue
}
// 对每个新连接创建协程收发数据
go process(conn)
}
}
func process(conn net.Conn) {
defer conn.Close()
for {
var buf [128]byte
// 接收数据
n, err := conn.Read(buf[:])
if err != nil {
fmt.Printf("接收数据失败!发生错误:%v\n", err)
break
}
fmt.Printf("已成功接收数据:%v\n", string(buf[:n]))
// 发送数据
if _, err = conn.Write([]byte("服务端消息!")); err != nil {
fmt.Printf("发送数据失败!发生错误:%v\n", err)
break
}
}
}
此外,还要修改上述实现 TCP 客户端的代码:
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
func main() {
// 建立与服务端的连接
conn, err := net.Dial("tcp", "127.0.0.1:3000")
if err != nil {
fmt.Printf("连接失败!发生错误:%v\n", err.Error())
return
}
fmt.Println("客户端向服务端发送连接请求......")
defer conn.Close()
inputReader := bufio.NewReader(os.Stdin)
for {
// 读取用户在控制台的输入
input, err := inputReader.ReadString('\n')
if err != nil {
fmt.Printf("无法读取在控制台上输入的数据!发生错误:%v\n", err)
break
}
trimmedInput := strings.TrimSpace(input)
if trimmedInput == "Q" { // 输入 Q 退出
break
}
// 发送数据
if _, err = conn.Write([]byte(trimmedInput)); err != nil {
fmt.Printf("发送数据失败!发生错误:%v\n", err)
break
}
// 接收数据
var recvData = make([]byte, 1024)
if _, err = conn.Read(recvData); err != nil {
fmt.Printf("接收数据失败!发生错误:%v\n", err)
break
}
fmt.Printf("已成功接收数据:%v\n", string(recvData))
}
}
修改后的 TCP 客户端代码中,加入了用户能在控制台上输入任意数据,并且把输入的数据发送给服务器端的功能。不过,VS Code 窗口不支持控制台的输入,因此这里不提供运行结果。读者可以使用其他开发工具运行修改后的 TCP 服务端和 TCP 客户端的代码,以查看运行结果。
ICP备案:
公安联网备案: