练习
学完本章你应该掌握
- 能使用
net.Listen、listener.Accept、net.Dial、conn.Read、conn.Write写出基础 TCP 服务端和客户端,并正确处理buffer[:n]、连接关闭与地址一致性。 - 能使用
http.HandleFunc、http.ListenAndServe、http.Get、resp.Body.Close、io.ReadAll写出基础 HTTP 接口和客户端请求代码。 - 能理解
net/rpc的服务端与客户端配合方式,写对“导出类型 + 导出方法 + 请求参数 + 响应指针 + error 返回值”这组核心规则。 - 能使用
github.com/gorilla/websocket完成 WebSocket 服务端升级连接、客户端连接、消息循环收发,并区分ws://和http://。 - 能识别本章常见错误,例如服务端未启动、地址或路径不一致、忘记关闭连接、把整个 buffer 当有效数据、
reply没传指针、响应体未关闭等问题。 - 能把前面学过的函数、结构体、切片、map、判断、循环、goroutine、错误处理与超时控制自然放进网络题目里,而不是只会照着 API 写最小示例。
简单
第 1 题:搭一个最小 TCP 服务端
编写一个 Go 程序,完成下面要求:
- 监听地址
127.0.0.1:9100 - 每当有客户端连接进来时,读取一条消息
- 在服务端打印:
| |
- 再向客户端回复固定文本
ack - 每个连接处理完成后关闭连接
要求:
- 使用
net.Listen - 使用
listener.Accept - 使用单独的
handleConn(conn net.Conn)函数处理连接
查看参考答案
| |
说明:这里顺手复习了前面函数和 goroutine 的知识,但主角仍然是 TCP 服务端的监听、接收连接和读写流程。
第 2 题:编写对应的 TCP 客户端
请基于上一题的 TCP 服务端,编写一个客户端程序,完成下面要求:
- 连接
127.0.0.1:9100 - 发送文本
hello tcp - 读取服务端返回内容
- 在控制台输出返回结果
要求:
- 使用
net.Dial - 使用
conn.Write发送数据 - 使用
conn.Read读取响应 - 读取时只处理本次真正读到的数据
查看参考答案
| |
输出结果:
| |
说明:运行客户端前要先启动上一题的服务端,否则会出现连接失败。
第 3 题:修正 TCP 读取函数里的两个问题
下面这个连接处理函数有两个明显问题:
- 没有关闭连接
- 打印消息时直接使用了整个 buffer
请改正它。
| |
查看参考答案
修正后的代码如下:
| |
两个关键点分别是:
- 连接处理结束后要
defer conn.Close(),避免资源泄漏。 - 读取时只能使用
buffer[:n],否则可能把未写入的空字节或旧数据一起打印出来。
第 4 题:编写最小 HTTP 服务端
编写一个 Go 程序,完成下面要求:
- 注册路由
/hello - 当浏览器或客户端访问这个路径时,返回文本
hello http - 服务端监听
:8080
要求:
- 使用
http.HandleFunc - 使用
http.ListenAndServe - 处理函数签名写成
func(w http.ResponseWriter, r *http.Request)
查看参考答案
| |
访问地址:
| |
第 5 题:使用 HTTP 客户端读取一条公告
编写一个 Go 程序,完成下面要求:
- 使用
httptest.NewServer启动一个临时 HTTP 服务 - 这个临时服务返回文本
network ready - 再使用
http.Get发起请求 - 读取响应体并输出
要求:
- 读取完响应后关闭
resp.Body - 使用
io.ReadAll一次性读取响应内容
查看参考答案
| |
输出结果:
| |
说明:这里用 httptest.NewServer 是为了让这道题可以单独运行,不需要你手动先起一个 HTTP 服务。
一般
第 6 题:实现一个带查询参数的 HTTP 欢迎接口
编写一个 Go 程序,完成下面要求:
- 注册两个路由:
/ping/welcome - 访问
/ping时返回pong - 访问
/welcome?name=阿斌时返回hello, 阿斌 - 如果访问
/welcome时没有传name,返回hello, guest - 服务端监听
:8081
要求:
- 在
/welcome处理函数里从*http.Request读取查询参数 - 把路由处理逻辑拆成两个独立函数
查看参考答案
| |
访问示例:
| |
说明:这道题看起来还是 HTTP 基础题,但已经开始自然用到前面函数、判断和字符串处理的知识了。
第 7 题:编写一个 RPC 服务端 Multiply
编写一个 Go 程序,完成下面要求:
- 定义请求结构体
Args,字段包括:A intB int - 定义服务对象
Calculator - 给它绑定方法
Multiply(args Args, reply *int) error - 这个方法把
A * B的结果写入reply - 把服务注册到 RPC 框架
- 监听
127.0.0.1:9101
要求:
- 使用
rpc.Register - 使用
rpc.ServeConn - 每个连接都交给新的 goroutine 处理
查看参考答案
| |
说明:reply 必须是指针,方法最后也必须返回 error,这两个点是 net/rpc 最容易写错的地方。
第 8 题:编写对应的 RPC 客户端
请基于上一题的 RPC 服务端,编写一个客户端程序,完成下面要求:
- 连接
127.0.0.1:9101 - 调用
Calculator.Multiply - 传入参数
Args{A: 6, B: 7} - 输出远程调用结果
要求:
- 使用
rpc.Dial - 使用
client.Call reply必须传指针
查看参考答案
| |
输出结果:
| |
说明:运行客户端前要先启动上一题的 RPC 服务端,否则会连接失败。
第 9 题:实现一个带条件回显的 WebSocket 服务端
从这一题开始,如果你是在一个全新的空目录里练习,请先初始化模块并安装依赖;如果你已经在现成的 Go 模块里,可以跳过第 1 步:
| |
请编写一个 WebSocket 服务端,完成下面要求:
- 监听
:8082 - 注册路由
/ws - 当客户端发送
ping时,返回pong - 当客户端发送其他文本时,返回
echo: 原消息 - 服务端要持续读取消息,而不是只读一次
查看参考答案
| |
连接地址:
| |
说明:这道题虽然还是回显模型,但已经把前面学过的 if、循环和字符串处理都自然带进来了。
第 10 题:编写 WebSocket 客户端连续发送两条消息
请基于上一题的 WebSocket 服务端,编写一个客户端程序,完成下面要求:
- 连接
ws://127.0.0.1:8082/ws - 依次发送两条消息:
pinglesson - 每发送一条消息后,立刻读取并输出服务端响应
要求:
- 使用切片保存两条待发送消息
- 使用
for range循环发送
查看参考答案
| |
一种可能的输出结果:
| |
说明:这道题的主考点还是 WebSocket 客户端的连接、发送和读取,但你在实现时会自然复习切片和循环。
进阶
下面 5 题会把网络编程和前面学过的函数、结构体、map、错误处理、goroutine、字符串处理与超时控制自然组合起来,更接近真实的小型网络程序。
第 11 题:实现一个并发 TCP 课程查询服务
请完成一个稍微完整一点的小场景:
- 监听地址
127.0.0.1:9102 - 准备一张课程说明表:
| |
- 每个客户端发送一个课程关键字,例如
tcp - 服务端根据关键字返回对应说明
- 如果找不到,返回
not found - 服务端要支持多个客户端并发连接
要求:
- 把“根据关键字生成回复”的逻辑拆成单独函数
- 读取消息后使用
strings.TrimSpace去掉首尾空白 - 每个连接使用 goroutine 处理
查看参考答案
| |
说明:这道题真正练的是“网络层读到原始文本后,再交给普通函数和 map 去完成业务处理”,也就是把前面章节的基础能力接进网络场景。
第 12 题:实现一个带超时的 HTTP 获取函数
编写一个 Go 程序,完成下面要求:
- 编写函数
fetchWithTimeout(url string, timeout time.Duration) (string, error) - 这个函数使用
http.Client发起 GET 请求 - 如果在超时时间内拿到响应,就返回响应文本
- 如果超时,直接把错误返回给调用方
- 在
main中使用httptest.NewServer启动一个临时服务 - 这个临时服务先等待
50ms,再返回slow hello - 至少测试两次:
一次超时时间是
100ms一次超时时间是10ms
查看参考答案
| |
一种可能的输出结果:
| |
说明:
http.Client{Timeout: ...}是本章 HTTP 客户端内容的一个自然延伸,真实项目里很常见。- 这道题顺手复习了前面异常处理章节里的
error返回和调用方判断。
第 13 题:实现一个带业务错误的 RPC 成绩查询服务
请完成一个稍微完整一点的小场景:
- 定义请求结构体
QueryArgs,字段为:Name string - 定义响应结构体
ScoreReply,字段为:Score intLevel string - 定义服务对象
StudentService - 给它绑定方法
GetScore(args QueryArgs, reply *ScoreReply) error - 准备一张成绩表:
| |
- 如果姓名存在:
把分数和等级写入
reply - 如果姓名不存在:
返回错误
student xxx not found - 服务端监听
127.0.0.1:9103
等级规则:
>= 90是A>= 80是B>= 60是C- 其他是
D
查看参考答案
| |
说明:这道题把结构体、map、判断、错误返回和 RPC 方法规则全都串起来了。真正要练的是“远程调用不仅能返回正常结果,也能返回业务错误”。
第 14 题:实现一个 WebSocket 打卡器
编写一个 WebSocket 服务端,完成下面要求:
- 监听
:8083 - 注册路由
/ws - 每个客户端建立连接后,都维护自己的打卡次数
- 当客户端发送普通文本时,例如
变量、接口: 服务端返回第1次打卡:变量服务端返回第2次打卡:接口 - 当客户端发送
quit时: 服务端返回bye然后结束当前连接处理
要求:
- 使用
for循环持续读取消息 - 使用一个整数变量记录当前连接的打卡次数
- 收到
quit后要主动结束当前处理函数
查看参考答案
| |
连接地址:
| |
说明:这道题虽然还是 WebSocket,但比前面的固定回显更接近真实交互场景,因为服务端已经开始维护“当前连接的状态”了。
第 15 题:使用 Postman 验证 WebSocket 打卡器
请基于上一题的 WebSocket 打卡器,使用 Postman 做一次手动验证。要求:
- 连接地址写对
- 至少发送三次消息:
变量接口quit - 写出每次应该看到的响应
- 再说明如果连接失败,你会优先检查哪 3 个地方
查看参考答案
一种可行的验证过程如下:
- 先运行第 14 题的 WebSocket 服务端。
- 在 Postman 中新建 WebSocket 请求。
- 连接地址填写:
| |
- 连接成功后依次发送:
| |
预期响应分别是:
| |
如果连接失败,我会优先检查这 3 个地方:
- 地址协议有没有写错,WebSocket 应该使用
ws://,不能写成http://。 - 服务端有没有真正启动,以及端口是不是
8083。 - 路径是不是
/ws,如果路径写错,即使端口对了也无法按预期升级连接。
说明:这一题主考的是“会不会验证网络程序”,因为真实开发里,手动测试能力和写客户端代码同样重要。