TCP
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议
如何保证连接可靠呢?(面试常考题)
- 三次握手
- 四次挥手
服务端
package main
import ( "fmt" "net" "time")
func main() { // net.ResolveTCPAddr 把字符串地址解析成一个 *TCPAddr 结构体对象 // "tcp" 是网络类型,"127.0.0.1:8081" 是本机 8081 端口 // 第二个返回值是错误,这里用 _ 忽略掉了(新手练习这样写可以,生产代码最好检查) addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8081")
// 在上面解析出的地址上创建一个 TCP 监听器,开始监听 8081 端口 // 类比:前端起 WebSocket 服务前,服务端得先 listen 一个端口 listener, err := net.ListenTCP("tcp", addr) if err != nil { fmt.Println(err) // 监听失败就打印错误(比如端口已被占用) return }
// 无限循环:服务端要持续接收新的客户端连接,每来一个就处理一个 for { // listener.Accept() 会阻塞(卡住等待),直到有客户端连进来 // 返回的 conn 代表和这一个客户端之间的连接通道 conn, err := listener.Accept() if err != nil { break // 接收连接出错就跳出循环(通常意味着监听器被关了) }
// conn.RemoteAddr() 返回对方的地址(客户端的 IP+端口),打印出来看看是谁连进来了 fmt.Println(conn.RemoteAddr())
// 给这个客户端发一条消息 "hello" // []byte("hello") 把字符串转成字节切片,因为 TCP 传输的是字节流 conn.Write([]byte("hello"))
// 睡眠 2 秒(类比前端的 await new Promise(r => setTimeout(r, 2000))) time.Sleep(2 * time.Second)
// 主动关闭这个连接(这里属于演示:发完消息等 2 秒就断开) conn.Close() }}客户端
package main
import ( "fmt" "io" // 提供标准错误值,比如 io.EOF(表示读到末尾/连接关闭) "net")
func main() { // net.Dial 主动去连接服务端,类似前端里 new WebSocket("ws://127.0.0.1:8081") // "tcp" 是协议,"127.0.0.1:8081" 是服务端地址 // 返回的 conn 就是和服务端之间的这条连接通道 conn, err := net.Dial("tcp", "127.0.0.1:8081") if err != nil { fmt.Println(err) // 连接失败就打印错误(比如服务端没开) return }
// 无限循环:持续从连接里读服务端发来的数据 for { // 准备一个 1024 字节的缓冲区,用来临时存放读到的数据 // 类比前端的 new Uint8Array(1024) var byteData = make([]byte, 1024)
// conn.Read 把数据读进 byteData,返回值 n 是实际读到的字节数 // 这一步会阻塞,直到服务端发来数据或连接关闭 n, err := conn.Read(byteData)
// 如果错误是 io.EOF,表示连接已经被对方(服务端)关闭,读到末尾了,就跳出循环 if err == io.EOF { break }
// byteData 里可能有没用到的空间,用 byteData[:n] 只取前 n 个实际读到的字节 // 再用 string(...) 转成字符串打印出来 fmt.Println(string(byteData[:n])) }}如果是客户端发送消息
for { var text string fmt.Printf("请输入你需要发送的消息:") fmt.Scan(&text) if text == "q" { conn.Close() break } conn.Write([]byte(text)) }服务端
buf := make([]byte, 1024) n, err := conn.Read(buf) if err != nil { // 连接关闭或出错就退出 break } fmt.Println("收到:", string(buf[:n]))如果服务端需要收发,则需要在开一个协程处理
//因为下面已经是一个死循环了,需要开一个协程来接数据 go func() { for { // 准备一个 1024 字节的缓冲区,用来临时存放读到的数据 // 类比前端的 new Uint8Array(1024) var byteData = make([]byte, 1024)
// conn.Read 把数据读进 byteData,返回值 n 是实际读到的字节数 // 这一步会阻塞,直到服务端发来数据或连接关闭 n, err := conn.Read(byteData)
// 如果错误是 io.EOF,表示连接已经被对方(服务端)关闭,读到末尾了,就跳出循环 if err == io.EOF { break }
// byteData 里可能有没用到的空间,用 byteData[:n] 只取前 n 个实际读到的字节 // 再用 string(...) 转成字符串打印出来 fmt.Println(string(byteData[:n])) } }() // 发数据 for { var text string fmt.Printf("请输入你需要发送的消息:") fmt.Scan(&text) if text == "q" { conn.Close() break } conn.Write([]byte(text)) }HTTP
服务端
package main
import ( "fmt" "net/http")
// Index 是一个「路由处理函数」,类比 Vue Router 里某个路径对应的处理逻辑// 例如前端写 router.get("/", handler) —— Go 这里就是把一个函数挂到某个 URL 路径上//// 两个固定参数(顺序和类型都不能变,这是 Go HTTP 框架约定好的签名):// - writer http.ResponseWriter :「响应写入器」,类比你要 return 给浏览器的内容,// 用 writer.Write(...) 把数据写回去,就像往响应体里塞东西// - request *http.Request :「请求对象」,类比浏览器 fetch 时构造的 Request,// 里面装着这次请求的 URL、方法、请求头、body 等//// ⚠️ 注意:Go 的函数参数「必须」有名字。// 之前写成 `*http.Request` 没给名字,导致下面用到 request 时编译器找不到它,报一堆错。func Index(writer http.ResponseWriter, request *http.Request) { // request.URL.Path 就是这次请求的路径,比如访问 http://127.0.0.1:8080/abc,这里就是 "/abc" fmt.Println(request.URL.Path)
// writer.Write 把字节切片写回给浏览器,类比前端的 res.send("hello") // 为什么是 []byte?因为 HTTP 响应体本质就是字节流,跟 TCP 那边一样 writer.Write([]byte("hello"))}
func main() { // http.HandleFunc 把一个 URL 路径绑定到一个处理函数 // 类比 Vue Router 的路由表注册:"/" 这个路径来了请求,就交给 Index 函数处理 // 第一个参数 "/" 表示匹配所有路径(Go 的默认 HTTP 路由里,"/" 是兜底路由) http.HandleFunc("/", Index)
fmt.Println("web server listen addr: http://127.0.0.1:8080")
// http.ListenAndServe 在指定地址上启动 HTTP 服务,开始监听 // 类比前端项目跑 npm run dev 起一个开发服务器监听某个端口 // "127.0.0.1:8080" = 本机 8080 端口;第二个参数 nil 表示用默认的路由(就是上面 HandleFunc 注册的那些) // // ⚠️ 端口尽量用 8080 这类大于 1024 的端口。 // 80、443 这种小于 1024 的叫「特权端口」,普通用户绑定会失败(需要 sudo),新手练习避开。 // // 它会一直阻塞在这里服务请求,只有在出错时才返回 error,所以这里检查一下错误 err := http.ListenAndServe("127.0.0.1:8080", nil) if err != nil { fmt.Println("服务器启动失败:", err) return }}客户端
package main
import ( "fmt" "io" "net/http")
func main() { res, err := http.Get("http://127.0.0.1:8080") if err != nil { fmt.Println(err) return } bytedata, _ := io.ReadAll(res.Body) fmt.Println(string(bytedata))}websocket
websocket是socket连接和http协议的结合体,可以实现网页和服务端的长连接
go get github.com/gorilla/websocket服务端
package main
import ( "fmt" "github.com/gorilla/websocket" "net/http")
var UP = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024,}
var connLis []*websocket.Conn
func handler(res http.ResponseWriter, req *http.Request) { // 服务升级 conn, err := UP.Upgrade(res, req, nil) if err != nil { fmt.Println(err) return } connLis = append(connLis, conn) for { // 消息类型,消息,错误 t, p, err := conn.ReadMessage() if err != nil { break } for index := range connLis { connLis[index].WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("你说的是:%s吗?", string(p)))) } fmt.Println(t, string(p)) } defer conn.Close() fmt.Println("服务关闭")}
func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil)}服务端给多个客户端发
package main
import ( "fmt" "github.com/gorilla/websocket" "net/http")
var UP = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024,}
var connLis []*websocket.Conn
func handler(res http.ResponseWriter, req *http.Request) { // 服务升级 conn, err := UP.Upgrade(res, req, nil) if err != nil { fmt.Println(err) return } connLis = append(connLis, conn) for { // 消息类型,消息,错误 t, p, err := conn.ReadMessage() if err != nil { break } for index := range connLis { connLis[index].WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("你说的是:%s吗?", string(p)))) } fmt.Println(t, string(p)) } defer conn.Close() fmt.Println("服务关闭")}
func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil)}客户端
package main
import ( "bufio" "fmt" "github.com/gorilla/websocket" "os")
func main() { dl := websocket.Dialer{} conn, _, err := dl.Dial("ws://127.0.0.1:8080", nil) if err != nil { fmt.Println(err) return } go send(conn) for { t, p, err := conn.ReadMessage() if err != nil { break } fmt.Println(t, string(p)) }}
func send(conn *websocket.Conn) { for { reader := bufio.NewReader(os.Stdin) l, _, _ := reader.ReadLine() conn.WriteMessage(websocket.TextMessage, l) }}


