当前位置: 首页 > news >正文

案例速成GO+Socket,个人笔记

更多个人笔记:(仅供参考,非盈利)
gitee: https://gitee.com/harryhack/it_note
github: https://github.com/ZHLOVEYY/IT_note

文章目录

    • 简单知识了解
    • 实现一个TCP 服务器与客户端(聊天室)
    • UDP客户端和服务端
    • WebSocket 服务器与客户端(实时聊天)

简单知识了解

Socket位于应用层和传输层之间,属于传输的媒介
tcp三次握手和四次挥手是基于socket实现的
三次: A:syn(连接么) B:syn+ack(连,你也发个连) A:ack(连)
四次:A:fin(断吧) B:ack (ok) B:fin(断吧) A:ack(ok)

Go中用net包操作socket

  • 客户端net.Dial无论什么形式都能连接
  • 服务器端使用net.listen (进一步net.accept进行监听)

Dial支持TCP,UDP,ICMP,等

实现一个TCP 服务器与客户端(聊天室)

分为server和client 可以一个serer对多个client

server代码:

package mainimport ("bufio""fmt""log""net""sync""os"
)func main() {listener, err := net.Listen("tcp", ":8080")if err != nil {log.Fatal("监听失败:", err)}defer listener.Close()fmt.Println("TCP 服务器启动,监听 :8080")// 客户端连接管理var clients []net.Connvar mutex sync.Mutex// 添加服务器发送消息的 goroutinego func() {reader := bufio.NewReader(os.Stdin)for {msg, err := reader.ReadString('\n')if err != nil {log.Println("读取控制台输入失败:", err)continue}// 广播服务器消息broadcast := fmt.Sprintf("[Server]: %s", msg)mutex.Lock()for _, c := range clients {c.Write([]byte(broadcast))}mutex.Unlock()}}()// 继续处理客户端连接for {conn, err := listener.Accept()if err != nil {log.Println("接受连接失败:", err)continue}fmt.Printf("新客户端连接: %s\n", conn.RemoteAddr().String())// 添加到客户端列表mutex.Lock()clients = append(clients, conn)mutex.Unlock()// 启动 goroutine 处理客户端go handleClient(conn, &clients, &mutex)}}
func handleClient(conn net.Conn, clients *[]net.Conn, mutex *sync.Mutex) {defer conn.Close()// 读取客户端消息reader := bufio.NewReader(conn)for {msg, err := reader.ReadString('\n')if err != nil {fmt.Printf("客户端 %s 断开: %v\n", conn.RemoteAddr().String(), err)// 从客户端列表移除mutex.Lock()for i, c := range *clients {if c == conn {*clients = append((*clients)[:i], (*clients)[i+1:]...)break}}mutex.Unlock()return // 退出这个协程}// 广播消息broadcast := fmt.Sprintf("[%s]: %s", conn.RemoteAddr().String(), msg) //这里是整合起来fmt.Print(broadcast)mutex.Lock()for _, c := range *clients {if c != conn {c.Write([]byte(broadcast))}}mutex.Unlock()}}

这一类包名一般都是记不住的,需要掌握构建逻辑:

  • 建立listen
  • 因为可能有多个client连接,所以需要list,所以为了解决并发问题所以才引入的lock
  • 服务器发送消息一个协程 (广播 )
  • listen accept建立conn,每个conn对应客户端
  • conn中处理读取客户端的信息,通过bufio
  • 如果客户端断开,通过msg能不能读取的到进行判断

client代码:

package mainimport ("bufio""fmt""log""net""os"
)func main() {// 连接服务器conn, err := net.Dial("tcp", "localhost:8080")if err != nil {log.Fatal("连接失败:", err)}defer conn.Close()fmt.Println("已连接到服务器")// 启动 goroutine 读取服务器消息go func() {reader := bufio.NewReader(conn)for {msg, err := reader.ReadString('\n')if err != nil {log.Println("服务器断开:", err)return}fmt.Print(msg)}}()// 从标准输入读取并发送消息scanner := bufio.NewScanner(os.Stdin)for scanner.Scan() {msg := scanner.Text() + "\n"_, err := conn.Write([]byte(msg))if err != nil {log.Println("发送失败:", err)return}}
}

client部分思路:

  • 通过dial进行连接,生成conn
  • 通过bufio读取conn,从服务器传递的信息
  • 设置发送给服务器的信息,通过write写入

UDP客户端和服务端

多开几个终端跑,会发现一个client发送的信息,所有的client都能收到,是UDP的群发,server在中间充当转接和管理的作用

serer.go:

package mainimport ("fmt""log""net""sync"
)func main() {// 监听 UDP 端口addr, err := net.ResolveUDPAddr("udp", ":8081") //进行地址转换if err != nil {log.Fatal("解析地址失败:", err)}conn, err := net.ListenUDP("udp", addr)if err != nil {log.Fatal("监听端口失败:", err)}defer conn.Close()fmt.Println("已监听 UDP 端口:", addr)// 客户端地址列表var clients []net.Addrvar mutex sync.Mutex// 读取和广播buffer := make([]byte, 1024)for {n, clientAddr, err := conn.ReadFromUDP(buffer)if err != nil {log.Println("读取失败:", err)continue}msg := string(buffer[:n])fmt.Printf("收到 %s: %s", clientAddr.String(), msg)// 添加新客户端mutex.Lock()if !contains(clients, clientAddr) {clients = append(clients, clientAddr)fmt.Printf("新客户端: %s\n", clientAddr.String())}mutex.Unlock()// 广播消息mutex.Lock()for _, addr := range clients {if addr.String() != clientAddr.String() { //避免消息回显给发送者,实现群体发送的效果conn.WriteToUDP([]byte(fmt.Sprintf("[%s]: %s", clientAddr.String(), msg)), addr.(*net.UDPAddr))}}mutex.Unlock()}}func contains(clients []net.Addr, addr net.Addr) bool {for _, c := range clients {if c.String() == addr.String() {return true}}return false
}
  • 进行UDP地址转换
  • 建立conn连接
  • 建立客户列表
  • 通过buffer读取信息
  • 通过list进行广播,防止发送给发送者

client.go


package mainimport ("bufio""fmt""log""net""os"
)func main() {// 连接服务器addr, err := net.ResolveUDPAddr("udp", "localhost:8081")if err != nil {log.Fatal("解析地址失败:", err)}conn, err := net.DialUDP("udp", nil, addr)if err != nil {log.Fatal("连接失败:", err)}defer conn.Close()fmt.Println("已连接到 UDP 服务器")// 启动 goroutine 读取消息go func() {buffer := make([]byte, 1024)for {n, _, err := conn.ReadFromUDP(buffer)if err != nil {log.Println("读取失败:", err)return}fmt.Print(string(buffer[:n]))}}()// 发送消息scanner := bufio.NewScanner(os.Stdin)for scanner.Scan() {msg := scanner.Text() + "\n"_, err := conn.Write([]byte(msg))if err != nil {log.Println("发送失败:", err)return}}
}
  • udp地址转换
  • 通过conn连接
  • 启动一个协程一直监听,这样能输出收到的消息
  • 同时利用scan,自己也能write发送消息

WebSocket 服务器与客户端(实时聊天)

WebSocket 提供全双工通信,适合实时应用。以下使用 gorilla/websocket 实现聊天室
多启动启动几个前端文件,然后可以实现一个发消息,别的接收到

server.go:


package mainimport ("fmt""log""net/http""sync""github.com/gorilla/websocket"
)var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return true // 允许所有来源的连接},
}func main() {// 客户端连接管理var clients = make(map[*websocket.Conn]bool) //定义函数在外部,每次访问都调用函数,修改 var mutex sync.Mutex//WebSocket路由http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {conn, err := upgrader.Upgrade(w, r, nil) //upgrader.Upgrade 的作用是将 HTTP 连接升级为 WebSocket 连接if err != nil {log.Println("升级失败:", err)return}fmt.Printf("新客户端连接:%s\n", conn.RemoteAddr().String())// 添加客户端mutex.Lock()clients[conn] = truemutex.Unlock()//处理消息for {_, msg, err := conn.ReadMessage()if err != nil {fmt.Printf("客户端断开连接:%s\n", conn.RemoteAddr().String())mutex.Lock()delete(clients, conn)mutex.Unlock()conn.Close()return}broadcast := fmt.Sprintf("[%s]: %s\n", conn.RemoteAddr().String(), msg)fmt.Print(broadcast)//广播消息mutex.Lock()for c := range clients {if c != conn {c.WriteMessage(websocket.TextMessage, []byte(broadcast))}}mutex.Unlock()}})// 启动服务器fmt.Println("WebSocket 服务器启动,监听 :8080")log.Fatal(http.ListenAndServe(":8080", nil)) //使用 log.Fatal 可以立即记录错误并终止程序
}
  • 构建conn,每个客户端访问都会调用函数修改clients
  • 通过conn读取信息,并判断客户端是否还连接着
  • 通过clients列表广播消息
  • 正常到http包,启动服务

前端html文件: (了解就行)

<!DOCTYPE html>
<html>
<head><title>WebSocket 聊天室</title>
</head>
<body><input id="message" type="text" placeholder="输入消息"><button onclick="sendMessage()">发送</button><div id="output"></div><script>const ws = new WebSocket("ws://localhost:8080/ws"); //建立 WebSocket 连接ws.onmessage = function(event) { //处理接收消息(事件监听)const output = document.getElementById("output");output.innerHTML += "<p>" + event.data + "</p>";};ws.onclose = function() { //处理连接断开alert("连接断开");};function sendMessage() { //发送消息函数:const input = document.getElementById("message"); //获取输入ws.send(input.value);input.value = "";}</script>
</body></html>
  • 简单的输入框设计
  • js部分见注释,先建立连接,然后处理消息接收,断开和发送 三个组成部分

终端中打开html文件:

# Windows
start file.html  # 或 start chrome file.html# macOS
open file.html  # 或 open -a "Google Chrome" file.html# Linux
xdg-open file.html

然后进行测试就可以

相关文章:

  • Linux 服务管理两种方式service和systemctl
  • 《数学物理方程》——第一章 引入与基本概念
  • AGILE:开启LLM Agent强化学习的创新框架
  • 【dify—3】拉取镜像、本地访问dify
  • AimRT 从零到一:官方示例精讲 —— 六、pb_chn示例.md
  • 香港科技大学广州|可持续能源与环境学域博士招生宣讲会—四川大学专场
  • 【Fifty Project - D20】
  • 【自然语言处理与大模型】LangChain大模型应用框架入门②
  • 8分钟快速掌握Markdiwn
  • 阿里Qwen3 8款模型全面开源,免费商用,成本仅为 DeepSeek-R1 的三分之一
  • AIGC(生成式AI)技术全景图:从文本到图像的革命
  • Unity PBR基础知识
  • VS Code 的新AI Agent 模式绝了
  • OpenGL学习笔记(PBR)
  • 在Spark中通过jps命令看到的进程名,是哪个命令产生有什么作用
  • 【图片识别成表格】批量图片识别成excel表格,批量识别图片区域文字保存到excel表格,基于WPF和腾讯OCR的识别方案
  • 使用Forge API制作《我的世界》Java版MOD的一般步骤
  • 51LA使用方法与悟空统计,网站数据分析的双重选择
  • 证券项目场景题深度解析
  • 物联网平台厂商有哪些?2025物联网平台推荐?国内有哪些比较好的物联网平台?
  • 张炜琳已任三明市委常委、宣传部部长
  • 海量数据处于“原矿”状态,数据价值释放如何破局?
  • 游客曝九寨沟打网约车被出租车围堵,景区回应:当地无合规网约车
  • 青海省林业和草原局副局长旦增主动投案,正接受审查调查
  • 言短意长|政府食堂、停车场开放的示范效应
  • 新造古镇丨上海古镇朱家角一年接待164万境外游客,凭啥?