22.网络编程

news/2025/11/10 0:28:19/文章来源:https://www.cnblogs.com/surpassme/p/19205608

22.1 TCP/IP协议栈

    TCP/IP(Transmission Control Protocol/Internet Protocol),中文翻译为传输控制协议/因特网互联协议。它是一个包含很多工作在不同层的协议族,其中最著名的两个协议分别是TCP和IP协议。TCP/IP协议一共定义了四层:网络接入层网络层传输层应用层

2201-TCP-IP协议.png

TCP/IP协议是事实标准,目前在广域网和局域网使用非常广泛。

22.2 TCP与UDP区别

    TCP/IP协议在传输层又可以分为TCP和UDP两种,主要区分如下所示:

TCP UDP
连接类型 面向连接 无连接
可靠性 可靠 不可靠
有序 数据包有序号 没有包序
使用场景 适用于大多数场景,数据不能出现任何问题 视频和音频
服务对象 一对一 支持一对一、一对多和多对多

    以下解释如下所示:

1.连接

  • 使用TCP时,需要通信双方提前建立连接,建立连接前需要经过三次握手,结束连接时,需要四次挥手
  • UPD不需要提前建立连接,即刻传输数据

2.可靠性

  • TCP需要确定每一个包是否收到,如遇丢包,则需要重新发送数据包。可靠性高,但会牺牲效率
  • UDP是尽最大努力保证数据传输,但不需要确认数据包,因此会出现丢包也无法知道,也不重新发送数据包,可靠性低,但效率高一些

3.有序

  • TCP包有序号,可以进行顺序控制。第一个包序号随机生成,之后的序号都会与第一个序号相关
  • UDP无序号,无法纠正,只能应用层进行验证

4.数据

  • TCP协议是流协议,也就是一大段数据看作字节流,一段段持续发送这些字节,各字节流之间没有边界,通过序号保证顺序和可靠
  • UDP协议是数据报协议,每一份数据封装在一个单独的数据包中,一份一份发送数据,各个数据包之间存在边界,但可能会导致丢包和乱序。

5.服务对象

  • TCP是一对一的点对点服务,即一个连接只有两个端点
  • UDP支持一对一、一对多、多对多的交互式通信

22.3 TCP/UDP编程

    无论是基于TCP还是UDP编程,其底层原理都是基于Socket实现的。在Go语言中Socket都封装在内置net包。即通过net包就能进行TCP和UDP的网络编程。

22.3.1 TCP服务端编程

    服务器端进行编程的步骤如下所示:

  • 创建Socket对象
  • 绑定IP地址和端口
  • 开始在指定的IP端口上监听
  • 获取用于传输数据的新Socket对象。

    示意图如下所示:

2202-TCP-Server通信流程示意图.png

以上示意图来自于马哥教育

    示例代码如下所示:

package mainimport ("log""net"
)func main() {tcpAddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:9999")if err != nil {log.Panicf("解析IP和端口出错:%v\n", err)}// 底层创建Socket、bind、Listen,非阻塞IOserver, err := net.ListenTCP("tcp", tcpAddr)if err != nil {log.Panicf("绑定IP和端口出错%v\n", err)}// 关闭Serverdefer server.Close()// 循环接收客户端的连接for {// 开始接收数据,并分配新的Socketconn, _ := server.Accept()go func() {// Client退出前,关闭连接defer conn.Close()// 创建缓冲区,进行收发操作buffer := make([]byte, 4096)// 成功接收到数据n, err := conn.Read(buffer)if err != nil {log.Fatalf("读取数据出错:%v\n", err)}clientAddr := conn.RemoteAddr().String()if n == 0 {log.Printf("客户端%s主动断开连接", clientAddr)return}data := buffer[:n]log.Printf("从 %v 成功接收到数据长度为:%d,数据内容为:%s\n", clientAddr, n, data)// Server 返回响应数据conn.Write([]byte("Hello"))}()}
}

22.3.2 TCP客户端编程

    客户端编程与服务端非常相似,其基本步骤如下所示:

  • 创建Socket对象
  • 随机选择端口即可以向服务端发起连接
  • 连接成功后,就可以发起操作
  • 最后关闭连接
package mainimport ("fmt""log""net""runtime""time"
)func main() {rAddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:9999")if err != nil {log.Panicf("解析IP和端口出错:%v\n", err)}// 底层创建Socket、并发起连接client, err := net.DialTCP("tcp", nil, rAddr)if err != nil {log.Panicf("连接远程IP和端口出错%v\n", err)}log.Printf("本地连接地址和端口%v\n", client.LocalAddr())// 关闭Serverdefer client.Close()quitFlag := make(chan struct{})go func() {// 收发数据buffer := make([]byte, 4096)for {// 设置一个超时时间,超出设定的时间,则不再等待client.SetReadDeadline(time.Now().Add(10 * time.Second))// 会出现阻塞情况出现n, err := client.Read(buffer)if err != nil {log.Printf("client接收数据出错%v\n", err)if _, ok := err.(*net.OpError); !ok {// 不是读超时quitFlag <- struct{}{}return}continue}log.Printf("client接收数据的为:%v\n", string(buffer[:n]))}}()go func() {var input stringfor {// 用户中止输入命令if input == "q" {quitFlag <- struct{}{}return}n, err := fmt.Scan(&input)if err != nil {log.Printf("读取输入数据出错%v\n", err)}log.Printf("用户输入数据长度为%v\n", n)_, err = client.Write([]byte(input))if err != nil {log.Printf("client发送的数据出错%v\n", err)}log.Printf("client发送的数据为:%v\n", input)}}()t := time.NewTicker(1 * time.Second)LOOP:for {select {case <-quitFlag:break LOOPcase <-t.C:log.Printf("Goroutine number is: %d\n", runtime.NumGoroutine())}}log.Println("客户端退出")
}

22.3.3 UDP服务端编程

    UDP是无需建立连接就可以相互发送数据的网络传输协议,因此UDP存在不可靠,通信没有时序待缺点,但却非常适合于视频、音频等场景。其服务端通信示例如下所示:

2203-UDP-Server通信流程示意图.png

以上示意图来自于马哥教育

    UDP 基本创建步骤如下所示:

  • 创建Socker对象
  • 绑定IP和端口
  • 收发传输数据
  • 释放资源
package mainimport ("fmt""log""net""runtime""time"
)func main() {udpAddr, err := net.ResolveUDPAddr("udp", "0.0.0.0:9999")if err != nil {log.Fatalln("解析UDP IP和Port错误")}// 创建Socker对象,并绑定IP和端口server, err := net.ListenUDP("udp", udpAddr)if err != nil {log.Fatalf("监听%v出错\n", udpAddr)}// 不需要Acceptdefer server.Close()quitFlag := make(chan struct{})go func() {// 进行收发buffer := make([]byte, 4096)for {server.SetReadDeadline(time.Now().Add(5 * time.Second))n, raddr, err := server.ReadFromUDP(buffer)log.Printf("Server从%v读取到的数据为:%v\n", raddr, string(buffer[:n]))if err != nil {log.Printf("%v远程读取数据出错%v 数据类型%[2]T\n", raddr, err)if _, ok := err.(*net.OpError); !ok {log.Println("ok:", ok)quitFlag <- struct{}{}return}continue}s := fmt.Sprintf("client: %v ,data: %v \n", raddr, string(buffer[:n]))server.WriteToUDP([]byte(s), raddr)}}()t := time.NewTicker(5 * time.Second)
LOOP:for {select {case <-quitFlag:break LOOPcase <-t.C:log.Printf("Goroutine Num:%d\n", runtime.NumGoroutine())}}log.Println("主协程结束")
}

22.3.4 UDP客户端编程

    UDP客户端编程与服务端也非常相似,基本操作步骤如下所示:

  • 创建Socker对象
  • 传输数据
  • 释放资源
package mainimport ("fmt""log""net""runtime""time"
)func main() {raddr, err := net.ResolveUDPAddr("udp", "0.0.0.0:9999")if err != nil {log.Fatalln("解析UDP IP和Port错误")}// 创建Socker对象,无需绑定client, err := net.DialUDP("udp", nil, raddr)if err != nil {log.Fatalf("连接%v出错\n", raddr)}// 不需要Acceptdefer client.Close()quitFlag := make(chan struct{})go func() {// 进行收发buffer := make([]byte, 4096)for {client.SetReadDeadline(time.Now().Add(5 * time.Second))n, raddr, err := client.ReadFromUDP(buffer)log.Printf("Client从%v读取到的数据为:%v\n", raddr, string(buffer[:n]))if err != nil {log.Printf("%v远程读取数据出错%v 数据类型%[2]T\n", raddr, err)if _, ok := err.(*net.OpError); !ok {log.Println("ok:", ok)quitFlag <- struct{}{}return}continue}}}()go func() {var input stringfor {fmt.Scan(&input)// 客户端退出if input == "q" {quitFlag <- struct{}{}return}n, err := client.Write([]byte(input))if err != nil {log.Printf("向%v发送数据失败", raddr)}log.Printf("向 %v 发送数据长度为:%d 数据为:%v\n", raddr, n, input)}}()t := time.NewTicker(5 * time.Second)
LOOP:for {select {case <-quitFlag:break LOOPcase <-t.C:log.Printf("Goroutine Num:%d\n", runtime.NumGoroutine())}}log.Println("主协程结束")
}

22.4 WebSocket

22.4.1 WebSocket基本原理

    HTTP协议,必须由客户端主动发起请求,服务器收到请求后被动响应请求,并返回信息给客户端,而且底层建立的TCP连接用完即断,并不会维持很久。但有些场景下,HTTP协议做不到。

  • 例如服务端需要为某些客户端主动发送请求,解决方案只能定时发起请求轮询服务器,效率低下
  • 例如客户端需要快速刷新数据,依然需要连续和服务器建立连接,HTTP协议会频繁建立、断开TCP连接、成本很高,而HTTP每一次发起请求的请求报文或响应报文,都需要携带请求头部信息,两端通信又存在冗余数据

    为了解决这个问题,引入了WebSocket技术。WebSocket是一种网络传输协议,它实现在单个TCP连接上进行全双工通信,位于OSI模型中的应用层。其主要特点如下所示:

  • WebSocket是一种在单个TCP连接上进行全双工通信的协议
  • WebSocket使得客户端和服务端之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
  • WebSocket允许客户端和服务端在完成一次握手后,便可以直接创建持久性的连接,并进行数据双向传输

    HTTP与WebSocket通信示意图如下所示:

2204-HTTP与WebSocket通信方式.png

    WebSocket是一个基于应用层的协议,建立在TCP协议之上,和HTTP协议是兄弟关系,但却又依赖HTTP。因为在需要使用WebSocket时,在建立HTTP连接后,在应用层完成一次握手和确认后,需要进行协议升级至WebSocket协议。

    HTTP的请求-响应模式每次新连接时均需要建立新的三次握手,不仅增加了延迟、也对资源造成浪费,不适宜实时响应速度要求高的应用。WebSocket的双向通信以及基于事件的传输模式,为应用提供了低延迟和高效率的实时通信能力。优化了性能和资源利用。主要应用场景如下所示:

  • 聊天室
  • 在线协同编辑
  • 实时数据更新
  • 弹幕
  • 股票等数据实时报价
  • WebShell

22.4.2 WebSocket服务端编程

    在Go中进行WebSocket需要使用到第三方库https://github.com/gorilla/websocket/,安装命令如下所示:

go get github.com/gorilla/websocket

    服务端示例代码如下所示:

  • 后端代码:
package mainimport ("flag""log""net/http""github.com/gorilla/websocket"
)var addr = flag.String("addr", "0.0.0.0:9999", "HTTP Server Address")
var upgrader = websocket.Upgrader{}func home(w http.ResponseWriter, r *http.Request) {log.Printf("%s %s", r.Method, r.URL)w.Header().Add("X-Protocol", "HTTP")// 返回一个网页,所有文件都可以看作为一个二进制字节序列http.ServeFile(w, r, "./index.html")// w.Write([]byte("Hello http"))
}func ws(w http.ResponseWriter, r *http.Request) {conn, err := upgrader.Upgrade(w, r, nil)if err != nil {log.Printf("Upgrade Protocol error :%v", err)}defer conn.Close()for {mt, message, err := conn.ReadMessage()if err != nil {log.Println("read:", err)break}log.Printf("recv: %s", message)err = conn.WriteMessage(mt, message)log.Printf("write: %s", message)if err != nil {log.Println("write:", err)break}}
}func main() {flag.Parse()http.HandleFunc("/ws", ws)http.HandleFunc("/http", home)http.ListenAndServe(*addr, nil)
}
  • 前端代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Web Sockert 测试</title><script>// Create WebSocket connection.// 1.握手http 2.协议升级 3.建立长连接const ws = new WebSocket("ws://127.0.0.1:9999/ws");// Connection opened// 建立某个事件绑定到函数addEventListener,js回调函数ws.addEventListener("open", function (event) {ws.send("Hello Server!");});// Listen for messages// 服务器端消息到了事件ws.addEventListener("message", function (event) {console.log("Message from server ", event.data);});ws.addEventListener("onclose", function (event) {console.log("WebSocket is closed now.");});ws.addEventListener("error", function (event) {console.log("WebSocket error: ", event);});</script>
</head><h1>Go WebSocket 测试示例</h1><br><a href="ws://127.0.0.1:9999/ws">切换至WebSocket测试</a>
</body>
</html>

WebSocket 前端页面参考资料:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

22.4.3 WebSocket客户端编程

    大部分情况下,WebSocket的客户端可以使用Postman、Apifox这类工具来进行测试,也可以参考代码 https://github.com/gorilla/websocket/blob/main/examples/echo/client.go

本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/960852.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

11.4总结

1.今天完成了系统页面 2.系统基本完成 3.明天继续完成系统的简约化设计

11.5总结

1.今天自主开发了一日三餐搭配系统的页面和程序 2.明天继续完成数据库部分

10.31总结

1.今天去泰山累了啥都没干

cve-2014-4148 利用样本分析

文章主要分析cve-2014-4148在该样本中的利用过程,不对该漏洞的成因进行梳理。该样本利用cve-201404148进行任意代码的执行,通过替换Token实现提权。关键步骤: 通过NtQueueThreadApcEx传入异常的参数,构建一个短小的…

2025ccpc女生赛题解

补题链接 : https://qoj.ac/contest/2564 J. 后鼻嘤 在每个以 n 结尾的字符串后添加 g 并输出 读入一整行法 //2025-11-02 #include <iostream> #include <cstdio> #include <algorithm> #include …

Day16盒子模型

盒子模型的基本组成<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-s…

OpenOCD简明指南

OpenOCD(Open On-Chip Debugger)是一个开源的片上调试工具,支持多种调试器和目标芯片。它通过 JTAG、SWD 等接口与目标设备通信,提供固件烧录、调试、内存操作等功能。一、OpenOCD 简介 OpenOCD(Open On-Chip Deb…

2025Dec.居家集训游记

在家集训也能叫游记吗? 总所周知每一个OIer都爱写这东西 (欸我CSP-S游记还没写) ,所以在家集训也要写。 DAY1 11.9 有点忘了,游记是10号写的。 好像不安了一天,因为我太菜了,也有点想学校里的朋友。 学习了 kru…

电商财务不求人!一张图看懂工作流程,算清每一笔账 - 智慧园区

做电商公司的财务,是不是经常感觉像个“救火队员”?刷单、推广费、平台账单、多店核算……传统行业那套流程在这里好像失灵了。 别慌!电商账务看似复杂,但只要抓住核心流程,就能化繁为简。这篇文章将带你走一遍电…

OI 笑传 #26

Seikai小清新 DP 回。解说会补的。 Luogu P14460 mx 的 NOIP 模拟 T1,赛时连猜带蒙结果 30min 切了( codeShow me the code #define rd read() #define mkp make_pair #define ls p<<1 #define rs p<<1|…

20232327 2025-2026-1 《网络与系统攻防技术》实验四实验报告

20232327 2025-2026-1 《网络与系统攻防技术》实验四实验报告 1. 实验内容 1.1 本周学习内容 本周学习了恶意代码分析的基本方法,静态分析和动态分析的核心概念。静态分析主要通过代码结构和API调用等特征来识别恶意行…

Gas 优化技巧

Gas 优化技巧 gas 优化技巧并不总是有效 注意复杂性和可读性 这里无法对每个主题进行全面处理 我们不讨论特定应用的技巧最重要的是:尽可能避免零到一的存储写入 缓存存储变量:仅写入和读取存储变量一次 打包相关变量…

2025.11.9总结

用AI写项目,太坐牢,今天继续写项目,用ai写,虽然能写出来,但是这是对于小型功能来说,能写,能改小bug,一旦项目的量级上来的,ai也写不出来。 于是这里就引申出了写项目更高级的做法,将大项目拆分成数个独立的小…

Python与C语言术语及概念差异全景总结

Python与C语言术语及概念差异全景总结 基于“易混淆术语(同名不同义)、对应概念(功能相似名不同)、体系差异(机制完全不同)”的严格分类标准,结合实践中高频踩坑点,系统梳理两种语言的核心差异,全程以表格为核…

Appium vs uiautomator2 优势劣势对比表

Appium vs uiautomator2 优势劣势对比表 本文档提供了Appium和uiautomator2两个Android自动化测试框架的详细对比,帮助开发者根据具体需求选择合适的工具。 核心能力对比表对比维度 Appium + Selenium uiautomator2嵌…

Android自动化测试中的嵌套元素查找

Android自动化测试中的嵌套元素查找 本文档总结了Android自动化测试中各种框架对嵌套元素查找的支持情况,包括实现方式、代码示例和性能对比。 目录支持嵌套查找的框架 Appium + Selenium UiAutomator (原生Java) Esp…

solidity面试题

solidity面试题(一)私有、内部、公共和外部函数之间的区别? 答:私有private只能当前合约内部使用,子合约继承也不能调用;内部internal可以当前合约和子合约调用;公共public合约内部外部和子合约都可以调用;外部…

对象存储基本知识

1 存储桶 存储桶是对象存储的核心组织单元,相当于“云端文件夹”,所有对象(文件、数据)都必须存储在存储桶中,是管理对象的基础载体。 1.1 存储桶的核心定位 存储桶是对象存储的顶层命名空间,用于隔离和管理不同…

基于GF域的多进制QC-LDPC误码率matlab仿真,译码采用EMS算法

1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印): 本课题实现的是四进制QC-LDPC2.算法涉及理论知识概要 多进制QC - LDPC码是一种基于GF域的线性分组码,它具有稀疏的校验矩阵。QC - LDPC码的校验矩…