go语言网络编程

  • 网络编程
  • Go语言网络编程相关API
  • Go语言网络编程架构
  • Go语言的网络编程实现基于以下几个关键原理:
  • bufio
  • bufio 包的主要功能和使用场景
  • 主要类型
  • 示例 tcp通信
  • 解决粘包
  • 粘包和拆包的产生原因
  • 解决方法
  • 示例

网络编程

Go语言网络编程相关API

1.1 net包net.Listen(network, address string): 创建一个网络监听器,等待进入的连接。它的第一个参数是网络类型(如"tcp""udp"等),第二个参数是要监听的地址。net.Dial(network, address string): 连接到指定的地址,返回一个连接对象。net.Conn接口: 提供了与网络连接相关的方法,包括:Read(b []byte) (n int, err error): 从连接中读取数据。Write(b []byte) (n int, err error): 向连接写入数据。Close() error: 关闭连接。net.Listener接口: 用于接受传入连接的接口,包含方法:Accept() (Conn, error): 接受一个连接请求。1.2 net/http包http.HandleFunc(pattern string, handler func(ResponseWriter, *Request)): 注册处理函数,当请求的URL匹配指定模式时,执行该处理函数。http.ListenAndServe(addr string, handler Handler) error: 启动HTTP服务器并监听指定地址上的请求。http.Request结构体: 包含请求的各种信息,如方法、URL、头部信息等。http.ResponseWriter接口: 用于构造HTTP响应,包含方法来设置响应头和写入响应体。http.Get(url string): 发起一个GET请求并返回响应。

Go语言网络编程架构

Go的网络编程架构主要基于事件驱动和goroutine的并发模型。

每当一个新的连接被接受时,服务器会为其启动一个新的goroutine来处理该连接。

因此,Go语言能够轻松地处理大量的并发连接,而无需使用复杂的线程管理。

服务器架构:

监听端口:服务器通过net.Listen监听指定端口。
接受连接:使用listener.Accept()接收连接。
处理连接:为每个连接创建goroutine,执行自定义的处理逻辑。
关闭连接:在处理完成后,关闭连接释放资源。

客户端架构:

创建连接:通过net.Dial或http.Get创建连接。
发送请求:向服务器发送请求或数据。
接收响应:获取服务器的响应数据。
关闭连接:处理完成后,关闭连接。

Go语言的网络编程实现基于以下几个关键原理:

Goroutine和通道: Go的并发模型建立在轻量级goroutine之上。
每一个网络连接都可以在单独的goroutine中处理,这样可以有效利用系统资源,提高并发处理能力。
通道(channel)用于在多个goroutine之间传递数据,保证数据的安全和同步。

I/O多路复用: Go使用操作系统的I/O多路复用机制,如epoll(Linux)或kqueue(BSD),
来处理大量连接的读写事件,从而减少线程上下文切换的开销。

封装性和易用性: Go的net和net/http包提供了高层次的抽象,简化了网络编程的复杂性。
开发者不需要深入底层的socket编程,只需调用简单的API即可实现复杂的网络操作。

bufio

bufio 是 Go 语言标准库中的一个包,用于提供 buffered I/O(缓冲输入/输出)功能。

它通过对输入和输出操作进行缓冲处理,从而提高程序的性能,

减少系统调用的次数。bufio 常用于处理文件、网络连接等 I/O 操作。

bufio 包的主要功能和使用场景

提高效率:

bufio 使用内存中的缓冲区来存储数据,从而减少直接的系统调用次数(如读写磁盘或网络),提高了读写效率。

简单的接口:

bufio 提供了简单易用的接口,让开发者能够更方便地处理文本和二进制数据。

处理文本数据:

bufio 特别适合处理行或单词输入输出,可以方便地读取和写入文本数据。

主要类型

以下是 bufio 包中几个重要的类型:

Reader:bufio.Reader 是一个结构体,用于缓存输入。通过 Read() 方法,可以从底层的 io.Reader 中读取数据,使用缓冲可以减少调用次数。
常用方法:Read(p []byte) (n int, err error): 从缓冲区读取数据到切片 p 中。ReadString(delim byte) (string, error): 读取直到遇到分隔符 delim 的数据,并返回作为字符串。ReadBytes(delim byte) ([]byte, error): 与 ReadString 类似,但返回字节切片。示例:reader := bufio.NewReader(conn)message, err := reader.ReadString('\n') // 读取一行数据Writer:bufio.Writer 是一个结构体,用于缓存输出。可以将数据写入缓冲区,并在缓冲区填满后再一次性写入到底层的 io.Writer。
常用方法:Write(p []byte) (n int, err error): 将数据写入缓冲区。Flush() error: 将缓冲区中的数据写入到底层的 io.Writer。
示例:writer := bufio.NewWriter(conn)writer.Write([]byte("Hello, Client!\n")) // 写入数据到缓冲区writer.Flush() // 确保将数据写入连接Scanner:bufio.Scanner 提供了一个方便的方式来逐行读取输入,常用于处理文本数据。
常用方法:Scan() bool: 读取下一个 token,返回 true 表示成功。Text() string: 返回上一个 token 作为字符串。
示例:scanner := bufio.NewScanner(os.Stdin)for scanner.Scan() {line := scanner.Text() // 得到输入的一行fmt.Println(line)}    

示例 tcp通信

这段代码实现了一个简单的聊天服务器,能够接收并广播消息到所有连接的客户端。
使用了 goroutines 来处理每个客户端的连接,
确保服务器能够同时处理多个连接。
通过 sync.Mutex 来保护对共享数据结构 clients 的并发访问。package mainimport ("bufio"      // 导入 bufio 包,用于读取输入"fmt"        // 导入 fmt 包,用于格式化输出"net"        // 导入 net 包,用于网络操作"sync"       // 导入 sync 包,用于同步操作
)// 定义全局变量
var (// 保存所有连接的客户端,使用 map 结构clients    = make(map[net.Conn]bool)clientsMux sync.Mutex // 保护 clients 的并发访问,防止数据竞态
)func main() {// 监听指定的端口(8080)listener, err := net.Listen("tcp", ":8080")if err != nil {// 如果监听失败,打印错误信息并返回fmt.Println("Error starting server:", err)return}defer listener.Close() // 在 main 函数结束时关闭监听器fmt.Println("Chat server started on :8080") // 服务器启动成功提示for {// 接受新的连接conn, err := listener.Accept()if err != nil {// 如果接受连接失败,打印错误信息并继续下一次循环fmt.Println("Error accepting connection:", err)continue}// 将新客户端添加到客户端列表clientsMux.Lock() // 加锁以保护 clients 的并发访问clients[conn] = true // 将连接添加到 clientsclientsMux.Unlock() // 解锁// 启动一个 goroutine 来处理该连接go handleConnection(conn)}
}// 处理连接的函数
func handleConnection(conn net.Conn) {defer func() {// 关闭连接,并从 clients 中移除conn.Close() // 关闭连接clientsMux.Lock() // 加锁以保护 clientsdelete(clients, conn) // 从 clients 中移除该连接clientsMux.Unlock() // 解锁}()// 创建一个读取器reader := bufio.NewReader(conn)for {// 读取客户端发送的消息直到换行符message, err := reader.ReadString('\n')if err != nil {// 如果读取失败,打印错误信息并返回fmt.Println("Error reading from connection:", err)return}// 打印接收到的消息fmt.Printf("Received: %s", message)// 广播消息到所有其他客户端broadcastMessage(message, conn) // 调用广播函数}
}// 广播消息给所有连接的客户端
func broadcastMessage(message string, sender net.Conn) {clientsMux.Lock() // 加锁以保护 clients 的并发访问defer clientsMux.Unlock() // 在函数结束时解锁// 遍历所有连接的客户端for client := range clients {// 不向发送消息的客户端发送消息if client != sender {_, _ = client.Write([]byte(message)) // 发送消息}}
}
这段代码实现了一个简单的聊天客户端,能够连接到服务器并发送消息,同时也会接收并显示服务器发来的消息。
通过 goroutines 来异步处理接收消息和发送消息,确保用户可以一边发送消息一边接收来自服务器的消息。
使用 bufio.Scanner 和 bufio.Reader 来处理输入和输出的读取。
package mainimport ("bufio" // 导入 bufio 包,用于读取输入"fmt"   // 导入 fmt 包,用于格式化输出"net"   // 导入 net 包,用于网络操作"os"    // 导入 os 包,用于与操作系统交互
)func main() {// 连接到聊天服务器,指定服务器地址(localhost:8080)conn, err := net.Dial("tcp", "localhost:8080")if err != nil {// 如果连接失败,打印错误信息并返回fmt.Println("Error connecting to server:", err)return}defer conn.Close() // 在 main 函数结束时关闭连接// 启动一个 goroutine 来读取服务器的消息go readMessages(conn)// 从标准输入读取消息并发送给服务器sendMessages(conn)
}// 从连接中读取消息
func readMessages(conn net.Conn) {reader := bufio.NewReader(conn) // 创建一个读取器for {// 读取服务器发送的消息直到换行符message, err := reader.ReadString('\n')if err != nil {// 如果读取失败,打印错误信息并返回fmt.Println("Error reading from server:", err)return}// 打印接收到的消息fmt.Print("Received: ", message)}
}// 发送消息到服务器
func sendMessages(conn net.Conn) {scanner := bufio.NewScanner(os.Stdin) // 创建一个扫描器来读取标准输入fmt.Println("消息发送中:")                 // 提示用户开始输入消息for scanner.Scan() {// 从输入中读取一行消息message := scanner.Text()// 发送消息到服务器,并在结尾加上换行符_, err := conn.Write([]byte(message + "\n"))if err != nil {// 如果发送失败,打印错误信息并返回fmt.Println("Error sending message:", err)return}}
}

解决粘包

在网络编程中,尤其是使用TCP进行通信时,常会遇到“粘包”与“拆包”现象。这是因为TCP是一个流式协议,

数据在传输过程中可能会被合并成一个大的数据包,或者一个大的数据包可能被分割成多个小的数据包,从而导致接收方无法正确解析消息。

粘包和拆包的产生原因

粘包:发送方可能连续发送多个消息,而TCP将这些消息合并为一个包,接收方收到的数据中包含多个消息。

拆包:发送方发送一个较大的消息,TCP将其分成多个小包发送,接收方在一次读取中只获取到部分消息。

解决方法

为了防止粘包和拆包现象,通常采用以下几种策略来处理:

  1. 使用固定长度的消息
    在这种方法中,每个消息都使用固定的字节长度来进行编码。这样接收方可以根据固定的长度来读取数据。

    优点:实现简单。
    缺点:不适用于消息长度不一致的情形,浪费带宽(如果消息较短)。

  2. 使用分隔符
    在每个消息的末尾加入一个特定的分隔符,比如换行符、特定字符等,接收方在读取数据时,可以根据分隔符进行解析。

    优点:可以处理可变长度的消息。
    缺点:需要确保分隔符不会出现在消息内容中。

  3. 消息头部长度
    在每个消息前添加一个固定大小的头部,头部包含消息的长度信息,接收方首先读取头部,获取消息长度,再按长度读取数据。

    优点:适用于任意长度的消息。
    缺点:稍显复杂,需处理头部。

示例

此代码实现了一个聊天服务器,能够接受多个客户端连接,

接收消息并将其广播给所有连接的客户端。

它通过使用二进制数据处理来确保消息的完整性和准确性,使用协程来并发处理每个连接,

确保服务器在处理多个客户端时的高效性。

通过使用 sync.Mutex 来避免对共享数据结构 clients 的竞争访问。

package mainimport ("encoding/binary" // 导入 encoding/binary 包,用于在网络中进行字节序转换"fmt"            // 导入 fmt 包,用于格式化输出"net"            // 导入 net 包,用于网络操作"sync"           // 导入 sync 包,用于同步操作
)// 定义全局变量
var (clients    = make(map[net.Conn]bool) // 用于保存所有连接的客户端clientsMux sync.Mutex                 // 保护 clients 的并发访问
)func main() {// 监听指定的端口(8080)listener, err := net.Listen("tcp", ":8080")if err != nil {// 如果启动失败,打印错误信息并返回fmt.Println("Error starting server:", err)return}defer listener.Close() // 在 main 函数结束时关闭监听器fmt.Println("Chat server started on :8080") // 服务器启动成功提示for {// 接受新的连接conn, err := listener.Accept()if err != nil {// 如果接受连接失败,打印错误信息并继续下一次循环fmt.Println("Error accepting connection:", err)continue}// 将新客户端添加到客户端列表clientsMux.Lock() // 加锁以保护 clients 的并发访问clients[conn] = true // 将连接添加到 clientsclientsMux.Unlock() // 解锁// 启动一个 goroutine 来处理该连接go handleConnection(conn)}
}// 处理连接的函数
func handleConnection(conn net.Conn) {defer func() {// 关闭连接,并从 clients 中移除conn.Close() // 关闭连接clientsMux.Lock() // 加锁以保护 clientsdelete(clients, conn) // 从 clients 中移除该连接clientsMux.Unlock() // 解锁}()for {// 读取消息长度(前4个字节)lengthBuffer := make([]byte, 4) // 创建一个4字节的缓冲区_, err := conn.Read(lengthBuffer) // 从连接中读取消息长度if err != nil {// 如果读取长度失败,打印错误信息并返回fmt.Println("Error reading length:", err)return}// 根据读取到的长度转换成整型msgLength := int(binary.BigEndian.Uint32(lengthBuffer))messageBuffer := make([]byte, msgLength) // 根据长度创建消息缓冲区// 读取消息/* @param messageBuffer 接收消息的缓冲区 @param conn 客户端连接 @return 返回读取的字节数和错误信息*/_, err = conn.Read(messageBuffer) // 从连接中读取消息if err != nil {// 如果读取消息失败,打印错误信息并返回fmt.Println("Error reading message:", err)return}// 打印接收到的消息fmt.Printf("Received message: %s\n", string(messageBuffer))// 将接收到的消息广播给所有其他客户端broadcastMessage(messageBuffer, conn)}
}// 广播消息给所有连接的客户端
func broadcastMessage(message []byte, sender net.Conn) {clientsMux.Lock() // 加锁以保护 clients 的并发访问defer clientsMux.Unlock() // 在函数结束时解锁// 获取消息长度msgLength := uint32(len(message))lengthBuffer := make([]byte, 4) // 创建一个4字节的缓冲区binary.BigEndian.PutUint32(lengthBuffer, msgLength) // 将消息长度转换为大端字节序// 遍历所有连接的客户端for client := range clients {// 不向发送者发送消息if client != sender {// 首先发送消息长度_, _ = client.Write(lengthBuffer) // 发送长度_, _ = client.Write(message) // 然后发送消息}}
}

此代码实现了一个简单的聊天客户端,能够连接到服务器、发送消息并接收服务器的消息。
通过二进制传输消息长度,确保了消息的完整性。


package mainimport ("bufio"      // 导入 bufio 包,用于读取输入"encoding/binary" // 导入 encoding/binary 包,用于字节序转换"fmt"        // 导入 fmt 包,用于格式化输出"net"        // 导入 net 包,用于网络操作"os"         // 导入 os 包,用于与操作系统交互
)func main() {// 连接到聊天服务器,指定服务器地址(localhost:8080)conn, err := net.Dial("tcp", "localhost:8080")if err != nil {// 如果连接失败,打印错误信息并返回fmt.Println("Error connecting to server:", err)return}defer conn.Close() // 在 main 函数结束时关闭连接// 启动一个 goroutine 来读取服务器的消息go readMessages(conn)// 从标准输入读取消息并发送给服务器sendMessages(conn)
}// 从连接中读取消息的函数
func readMessages(conn net.Conn) {for {// 创建一个4字节的缓冲区用于读取消息长度lengthBuffer := make([]byte, 4)_, err := conn.Read(lengthBuffer) // 从连接中读取消息长度if err != nil {// 如果读取长度失败,打印错误信息并返回fmt.Println("Error reading length:", err)return}// 将读取到的长度转换为整型msgLength := int(binary.BigEndian.Uint32(lengthBuffer))messageBuffer := make([]byte, msgLength) // 根据长度创建消息缓冲区_, err = conn.Read(messageBuffer) // 从连接中读取消息if err != nil {// 如果读取消息失败,打印错误信息并返回fmt.Println("Error reading message:", err)return}// 打印接收到的消息fmt.Printf("Received message: %s\n", string(messageBuffer))}
}// 发送消息到服务器的函数
func sendMessages(conn net.Conn) {scanner := bufio.NewScanner(os.Stdin) // 创建一个扫描器来读取标准输入fmt.Println("Type your messages below (end with Enter):") // 提示用户开始输入消息for scanner.Scan() {// 从输入中读取一行消息message := scanner.Text()// 获取消息长度msgLength := uint32(len(message))lengthBuffer := make([]byte, 4) // 创建一个4字节的缓冲区binary.BigEndian.PutUint32(lengthBuffer, msgLength) // 将消息长度转换为大端字节序// 先发送消息长度_, err := conn.Write(lengthBuffer) // 发送长度if err != nil {// 如果发送失败,打印错误信息并返回fmt.Println("Error sending length:", err)return}// 再发送消息_, err = conn.Write([]byte(message)) // 发送消息if err != nil {// 如果发送失败,打印错误信息并返回fmt.Println("Error sending message:", err)return}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

vscode 顶部 Command Center,minimap

目录 vscode 顶部 Command Center 设置显示步骤: minimap设置 方法一:使用设置界面 方法二:使用命令面板 方法三:编辑 settings.json 文件 左侧目录树和编辑器字体不一致: vscode 顶部 Command Center Visual Studio Code (VSCode) 中的 Command Center 是一个集中…

11年408考研真题解析-计算机网络

第一题: 解析:网络层虚电路服务和数据报服务 传输服务只有:有连接可靠和无连接不可靠两种,直接排除BC。 网络层指的是IP协议,由图二可知:运输层,网际层,网络接口层唯一有连接可靠的协…

vue3 本地windows下的字体的引用

1、先上了张效果: 2、windows 字体的路径:c:/windows/fonts/ 我们用华文行楷来测试下,先将华文行楷拷贝到/src/assets/fonts目录下。 3、然后我们来定义css: font-face {font-family: fyxk;font-style: normal;src: local(Opensan…

图结构感知的Transformer:一种新的图表示学习方法

人工智能咨询培训老师叶梓 转载标明出处 尽管图神经网络(GNNs)在处理图数据方面取得了显著成就,但它们在表达能力和捕获长距离依赖方面存在局限性。为了突破这些局限,研究者们开始探索将Transformer架构应用于图表示学习。在此基…

人工智能的前景与未来就业市场:机遇、挑战与社会影响

随着科技的飞速发展,人工智能(AI)已经逐渐渗透到我们生活的方方面面,它不仅引领着技术革新的浪潮,更在无声中重塑着我们的就业市场和社会结构。站在这个时代的交汇点上,我们不禁要问:人工智能将…

Web Components之继承

我们在使用Web Components自定义组件的时候,我们需要继承HTMLElement这个浏览器内置对象,但是如果我要一些高级封装,给组件内置一些方法的话。我们就需要使用继承的方式,在父类中实现基本功能的封装。 1 父类的封装 以下是我的继…

Java多线程(1)—线程基础

一、关于线程 1.1 简介 计算机线程(Thread)是操作系统能够进行运算调度的最小单位。线程的优势在于提高了程序的效率和响应能力,尤其在处理 I/O 操作或多任务时。多线程编程能够充分利用多核处理器的计算能力,达到更高的性能。 …

Spring 源码分析

Spring 源码版本 4.2.8.RELEASE Bean 生命周期 动态代理 代理模式 优点: 在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展缺点: 代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对…

Apifox 「定时任务」操作指南,解锁自动化测试的新利器

定时任务是按照预设时间自动执行的任务,它可以有效解决一些常见问题,比如频繁执行的回归测试和大规模的接口测试,这些任务需要在固定时间点或间隔周期内自动运行,以确保软件的持续集成和持续交付过程中的稳定性和可靠性。通过使用…

Windows下 批量重命名文件【bat实现】-两个小问题

Windows下 批量重命名文件【bat实现】_bat批量重命名文件ren-CSDN博客 上面是原来的教程,我遇到了两个小问题,问题及解决如下: ①dir/b>rename.csv : 无法将“dir/b>rename.csv”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。…

【每天学个新注解】Day 2 Lombok注解简解(一)—@Data、@Build、@Value

Data 相当于同时使用了 Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode 1、如何使用 需要同时使用Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode注解一个Bean的时候。 2、代码示例 例: Data public cla…

CCC SPAKE2+流程解析

1、SPAKE2流程及SCP03通道介绍 SPAKE2流程发生在CCC车主配对过程中的Phase2。 SPAKE2流程为车辆和手机之间的数据交换建立了一个安全通道SCP03。 那这个SCP03通道是干啥的? 我们可以先简单的理解为:建立安全通道前,车辆和手机之间交互的AP…

剖解反转链表

剖解反转链表 思路: 1.若链表为空或者只存在一个节点,就无需反转,直接返回head 2.若存在多个节点 首先将head.next给到cur,并将head.next置为null 剩余的节点就利用头插法,反转链表 class Solution {public ListNo…

基于SpringBoot+Vue+MySQL的特色旅游网站系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着旅游业的蓬勃发展,人们对旅游体验的需求日益多样化与个性化。传统的旅游信息查询与预订方式已难以满足现代游客的需求。因此,我们开发了这款基于SpringBootVueMySQL的特色旅游网站系统。该系统旨在通…

MySQL高阶1917-Leetcodify好友推荐

目录 题目 准备数据 分析数据 总结 题目 为 Leetcodify 用户推荐好友。我们将符合下列条件的用户 x 推荐给用户 y : 用户 x 和 y 不是好友,且用户 x 和 y 在同一天收听了相同的三首或更多不同歌曲。 注意,好友推荐是单向的&#xff0c…

web前端字段大小写下划线转换工具

文章目录 前言一、如何使用?二、相关代码总结 前言 程序员在敲代码的过程中都要命名一些字段,但是Java语言对字段的命名规范和sql命名规范不一样,如下图所示,这种机械性的转换工作很劳神费力,为了省点劲写了一个web小…

pdf怎么编辑修改内容?试试这四款工具!

作为一个经常探索各种办公软件的人,今天我打算和大家聊聊一个我们工作中经常会遇到的问题——编辑PDF文件。我们都知道,PDF文件以其格式稳定、不易被篡改而受到青睐,但这也意味着一旦需要修改内容,就变得相当棘手。不过&#xff0…

设计模式之类结构模式例题

答案:B A 知识点: 设计模式中类结构模式分别是:工厂方法模式,适配器模式,模板方法模式,解释器模式 记忆方法:公司里有个模特小姐姐,公是工厂模式,司是适配器模式&…

Mapper代理

文章目录 Mapper代理路径一定要写对Mapper代理方式加载配置文件 (包扫描的方式) Mapper代理 好像就是能 包名.方法。 反正就是防止硬编码,更灵活,更适用。 路径一定要写对 Mapper代理方式加载配置文件 (包扫描的方式…

fiddler抓包08_抓Android手机请求

课程大纲 手机抓包,电脑端的设置和IOS端相同,设置一次即可,无需重复设置。 前提:电脑和手机连接同一个局域网 土小帽电脑和手机都连了自己的无线网“tuxiaomao”。 Step1. 电脑端设置 ① 打开Fiddler - 开启抓包(F12…