使用Go语言构建一个最小可用的TCP交互程序
Go 网络编程实战:构建一个最小可用的 TCP 交互程序
TCP 服务端 和 TCP 客户端 两部分,运行后能实现:
服务端:启动后监听本地 8888 端口,能同时处理多个客户端的连接(并发);
客户端:主动连接服务端,你可以在客户端输入任意文字,服务端会 “原样返回”(Echo 回声功能);
交互效果:
- 客户端输入
hello→ 服务端返回【服务端回声】hello; - 客户端输入
ip→ 服务端返回服务端回复:127.0.0.1:63341(net.ResolveTCPAddr); - 客户端输入
exit→ 客户端主动断开连接,服务端提示 “客户端已断开”。

实例代码
// server.go
package main
import (
"bufio"
"fmt"
"net"
"strings"
)
// 启动TCP服务端
func startTCPServer() {
// 解析地址
tcpAddr, err := net.ResolveTCPAddr("tcp", ":8888")
if err != nil {
fmt.Printf("解析地址失败:%v\n", err)
return
}
// 监听端口
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
fmt.Printf("监听端口失败:%v\n", err)
return
}
defer listener.Close()
fmt.Println("等待客户端连接...")
// 接受客户端连接
for {
conn, err := listener.AcceptTCP() // 阻塞等待新连接
if err != nil {
fmt.Printf("接受连接失败:%v\n", err)
continue
}
// 并发处理每个客户端连接(避免阻塞其他连接)
go handleClientConn(conn)
}
}
// 处理单个客户端连接
func handleClientConn(conn *net.TCPConn) {
clientAddr := conn.RemoteAddr().String() // 获取客户端地址
fmt.Printf("客户端 [%s] 已连接\n", clientAddr)
defer func() {
conn.Close()
fmt.Printf("客户端 [%s] 已断开连接\n", clientAddr)
}()
// 创建缓冲区读取客户端数据
reader := bufio.NewReader(conn)
for {
// 读取客户端发送的数据
msg, err := reader.ReadString('\n')
if err != nil {
if err.Error() == "EOF" {
return
}
fmt.Printf("读取客户端 [%s] 数据失败:%v\n", clientAddr, err)
return
}
msg = strings.TrimSpace(msg)
if msg == "exit" {
return
}
// special command: return client address
var response string
if msg == "ip" {
response = clientAddr + "\n"
} else {
// 普通回声响应
response = fmt.Sprintf("【服务端回声】%s\n", msg)
}
_, err = conn.Write([]byte(response))
if err != nil {
fmt.Printf("向客户端 [%s] 发送数据失败:%v\n", clientAddr, err)
return
}
fmt.Printf("客户端 [%s] 发送:%s → 响应:%s", clientAddr, msg, strings.TrimSpace(response))
}
}
func main() {
fmt.Println("启动服务...")
startTCPServer()
}
// client.go
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
// 启动TCP客户端
func startTCPClient() {
// 解析服务端地址
tcpAddr, err := net.ResolveTCPAddr("tcp", ":8888")
if err != nil {
fmt.Printf("解析服务端地址失败:%v\n", err)
return
}
// 连接服务端
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
fmt.Printf("连接服务端失败:%v\n", err)
return
}
defer conn.Close()
fmt.Println("输入任意文字发送(输入 exit 断开连接):")
// 读取用户输入
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("> ")
input, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("读取输入失败:%v\n", err)
return
}
// 处理退出指令
input = strings.TrimSpace(input)
if input == "exit" {
conn.Write([]byte("exit\n"))
fmt.Println("已断开与服务端的连接")
return
}
// 向服务端发送数据
_, err = conn.Write([]byte(input + "\n"))
if err != nil {
fmt.Printf("发送数据失败:%v\n", err)
return
}
// 读取服务端响应
response, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
fmt.Printf("读取服务端响应失败:%v\n", err)
return
}
fmt.Printf("服务端回复:%s", strings.TrimSpace(response))
}
}
func main() {
fmt.Println("启动客户端...")
startTCPClient()
}基础概念
Net 包介绍
- 核心协议:
TCP、HTTP、RPC都是基于 net 包或其上层封装实现的。 - 实际用途:日常开发中,我们更多使用 Gin、gRPC 等框架,直接写原生 net 包的场景有限。
- 学习价值:理解底层原理,能让你在排查网络问题、调优框架时更有底气,也能更好地理解 “框架到底帮你做了什么”。
TCP 基本概念
这部分是理论基础,理解了这些,后面的代码就顺理成章了:
数据结构:TCP 是面向字节流的协议,数据以流的形式传输,没有天然的消息边界。
传输协议:可靠、面向连接的传输层协议,通过三次握手建立连接,四次挥手断开连接。
握手 / 挥手机制:
- 三次握手:确保双方都具备收发能力,建立可靠连接。
- 四次挥手:确保双方数据都传输完毕,优雅断开连接。
服务端流程:监听并接受连接
1.ResolveTCPAddr
作用:将字符串形式的地址(如 "localhost:8088")解析成 *net.TCPAddr 对象。
参数:
- 网络类型:
"tcp" - 地址字符串:
"localhost:8088"
意义:为后续的监听和连接操作提供统一的地址对象。
2.ListenTCP
作用:在指定的网络和地址上启动监听。
步骤:
- 使用
ResolveTCPAddr创建的地址对象。 - 设置网络协议为
"tcp"。
返回:一个 net.Listener 接口,用于后续接受连接。
3.AcceptTCP
- 作用:从监听队列中接受一个新的客户端连接。
- 返回:一个
net.Conn接口,代表与客户端的连接。 - 注意:这是一个阻塞操作,直到有新连接到来或发生错误。
4.循环监听新连接
- 核心模式:在一个无限
for循环中反复调用AcceptTCP。 - 并发处理:每次接受新连接后,必须启动一个新的 goroutine 去处理它(例如
go handleConn(conn)),否则服务端会阻塞,无法处理下一个连接。 - 回调函数:
handleConn就是你之前问的handler,它负责与客户端进行具体的交互。
客户端流程:主动发起连接
1.DialTCP
作用:主动向服务端发起 TCP 连接。
参数:
- 本地地址(可选)。
- 由
ResolveTCPAddr创建的服务端地址。
返回:一个 net.Conn 接口,代表与服务端的连接。
2.发送接收信息
WriteToConn:通过conn.Write()方法,将数据发送到服务端。ReadFromConn:通过conn.Read()方法,从服务端读取响应数据。- 注意:TCP 是流协议,需要自己处理粘包问题,比如定义消息长度前缀或特殊分隔符。
3.长连接维护
循环读取客户端输入:客户端可以在一个循环中,不断读取用户输入并发送给服务端,保持连接活跃。
Error 检测:
- EOF 异常处理:当
Read返回io.EOF时,说明服务端已经关闭了连接。 - 断开连接通知:客户端或服务端都可以主动关闭连接,另一方需要检测到并做相应清理。
使用场景
TCP 解决「可靠传输」的问题,Go 的 net.TCP 让实现高并发、高性能的 TCP 服务变得简单。
要求「可靠传输」的长连接服务(最核心场景)
物联网(IoT)设备通信:智能家居、工业传感器、车载终端等设备,需要和云端服务端保持长连接:
- 设备定时上报温湿度、位置、状态(数据丢包会导致监控失效);
- 云端下发控制指令(如「开关灯」「调整温度」),需要设备确认收到;
- Go 服务端用
ListenTCP监听,goroutine处理每个设备的TCPConn,稳定支撑上千台设备并发。
游戏服务器:网游 / 手游的实时通信层(如走位、攻击、聊天):
- 玩家操作指令(如「移动」「释放技能」)必须有序到达服务端,否则画面错乱;
- 服务端实时推送其他玩家状态,需要全双工通信;
- Go 的
TCPConn结合bufio做消息拆包,性能和并发都能满足中小游戏需求。
金融 / 支付系统:银行、支付网关的私有通信协议:
- 转账、交易指令绝对不能丢包 / 重复(TCP 可靠传输是底线);
- 长连接减少多次建立连接的开销,提升交易速度;
- Go 的
TCP编程可轻松加解密、签名验证,保障数据安全。
自定义私有协议的服务(替代 HTTP,更轻量 / 灵活)
内部运维工具:公司内部的服务器监控 / 管理工具,自定义极简协议:
- 客户端发
status→ 服务端返回 CPU / 内存占用; - 客户端发
restart→ 服务端执行重启逻辑;比 HTTP 少了Header/Cookie等开销,通信更高效。
- 客户端发
高性能数据传输:大数据平台的节点间通信,自定义协议(如「数据类型 + 数据长度 + 数据内容」):
- 避免 HTTP 的文本解析开销,直接二进制传输;
- TCP 可靠传输保证大数据块完整到达。
高吞吐的大数据传输(文件 / 视频 / 日志)
文件传输工具:替代
FTP/SFTP的轻量文件传输服务:- 客户端把文件按块通过
TCPConn.Write发送,服务端TCPConn.Read拼接; - TCP 保证文件块有序、不丢,实现完整文件传输。
- 客户端把文件按块通过
日志实时采集:分布式系统的日志中心,业务服务器(客户端)通过 TCP 把日志实时发送到日志服务端:
- 日志数据不能丢失(否则排查问题无依据);
- 长连接持续传输,比 HTTP 轮询更高效。
分布式系统 / 微服务的内部通信
- 微服务间的 RPC 底层:gRPC 底层基于 HTTP/2,但 HTTP/2 本质还是 TCP 长连接;自定义 RPC 框架(如内部私有 RPC)直接基于
net.TCP实现,更灵活。 - 集群节点心跳 / 同步:分布式缓存(如 Redis 集群)、分布式数据库(如 TiDB)的节点间,通过 TCP 长连接发送心跳包(确认节点在线)、同步数据(如分片信息、事务日志)。
以上就是使用Go语言构建一个最小可用的TCP交互程序的详细内容,更多关于Go构建TCP交互程序的资料请关注脚本之家其它相关文章!


最新评论