5.网络编程-socker(golang版)

news2024/11/25 4:58:54

目录

一、什么是socket?

二、Golang中使用TCP

TCP服务端

TCP客户端​​​​​​​

三、TCP黏包,拆包

1.什么是粘包,拆包?

2.为什么UDP没有粘包,拆包?

3.粘包拆包发生场景

4.TCP黏包

黏包服务端

黏包客户端

为什么会出现粘包

解决方案

自定义协议

服务端

客户端

5.TCP拆包

为什么会出现TCP拆包

常见方案

四、Golang中使用UDP

UDP服务端

UDP客户端


一、什么是socket?

Socket是BSD UNIX的进程通信机制,通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。Socket可以理解为TCP/IP网络的API,它定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。电脑上运行的应用程序通常通过”套接字”向网络发出请求或者应答网络请求。

Socket是应用层与TCP/IP协议族通信的中间软件抽象层。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket后面,对用户来说只需要调用Socket规定的相关函数,让Socket去组织符合指定的协议数据然后进行通信。

网络编程即socket编程

二、Golang中使用TCP

在Go语言中使用Socket编程,通常指的是使用标准库net包中的net.TCPConnnet.UDPConn接口来实现TCP或UDP网络通信。以下分别给出一个简单的TCP服务器和客户端示例:

TCP服务端

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
)

func handleConnection(conn net.Conn) {
	defer conn.Close() // 关闭连接
	for {
		reader := bufio.NewReader(conn)
		var buf [128]byte
		n, err := reader.Read(buf[:]) // 读取数据
		if err != nil {
			fmt.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("收到client端发来的数据:", recvStr)
		conn.Write([]byte(recvStr)) // 发送数据
	}
}

func main() {
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatal(err)
	}
	defer listener.Close()

	log.Println("Listening for connections on :8080...")

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("Error accepting connection:", err)
			continue
		}

		go handleConnection(conn) // 在新的goroutine中处理每个连接
	}
}

TCP客户端

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:8080")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	log.Println("Connected to localhost:8080")

	inputReader := bufio.NewReader(os.Stdin)
	for {
		input, _ := inputReader.ReadString('\n') // 读取用户输入
		inputInfo := strings.Trim(input, "\r\n")
		if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
			return
		}
		_, err = conn.Write([]byte(inputInfo)) // 发送数据
		if err != nil {
			return
		}
		buf := [512]byte{}
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("recv failed, err:", err)
			return
		}
		fmt.Printf("recv server msg: %s", buf[:n])
	}
}

以上示例展示了如何使用Go语言创建一个简单的TCP服务器和客户端。服务器监听8080端口,接受客户端连接并转发数据。客户端连接到本地的8080端口,发送一个消息并接收服务器的响应。

对于UDP编程,可以使用net.ListenUDP创建UDP监听器,net.DialUDP创建UDP连接,以及net.UDPConn接口进行数据收发。具体的代码实现与上述TCP示例类似,但需要注意UDP是无连接的、不可靠的传输协议,相应的编程模型和错误处理会有所不同。

三、TCP黏包,拆包

1.什么是粘包,拆包?

  • TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中,比如RPC框架。
  • 在使用TCP进行数据传输时,由于TCP是基于字节流的协议,而不是基于消息的协议,可能会出现粘包(多个消息粘在一起)和拆包(一个消息被拆分成多个部分)的问题。这些问题可能会导致数据解析错误或数据不完整。

2.为什么UDP没有粘包,拆包?

UDP(User Datagram Protocol)没有粘包和拆包现象,这是由其设计特性和工作方式所决定的。以下是详细解释:

  1. 面向数据报的传输方式

    • UDP是一种面向数据报的协议,每个数据报(datagram)都是一个独立、完整的信息单元,具有明确的边界。每个数据报包含源端口、目的端口、长度、校验和等信息,以及用户数据。发送端将数据封装成一个个数据报发送,接收端则按数据报的完整单位接收,不存在数据报之间的合并或拆分。
  2. 无连接状态

    • UDP是无连接的协议,发送数据前无需建立连接,发送后也不维持连接状态。每个数据报的发送与接收都是独立的事件,没有前后关联。因此,不存在因连接状态导致的数据包合并(即“粘包”)。
  3. 无顺序保证、重传机制

    • UDP不保证数据报的顺序传递,也不进行数据重传。每个数据报在网络中独立传输,可能因为网络条件等因素导致乱序、丢失或重复,但这些都由应用层自行处理。由于每个数据报都是单独处理的,不会因为等待其他数据报而被“粘”在一起,也不会因为重传而“拆包”。
  4. 固定报头长度

    • UDP数据报的报头长度是固定的(8字节),没有可变长度的选项字段,这使得接收端可以很容易地定位到用户数据的起始位置,无需担心因为解析报头时遇到“粘包”问题。
  5. 无流控和拥塞控制

    • TCP具有滑动窗口、流量控制和拥塞控制机制,这些机制可能导致发送端积攒多个小数据包合并成一个大的TCP段发送,或者在接收端因窗口大小限制而暂时缓存数据,从而形成“粘包”。而UDP没有这些复杂的控制机制,数据报一旦发送出去,就不受发送端或接收端的流量控制和拥塞控制影响,不会发生“粘包”或“拆包”。

综上所述,由于UDP的面向数据报、无连接状态、无顺序保证、无重传、固定报头长度以及无流控和拥塞控制等特性,每个UDP数据报在发送和接收时都是独立处理的,具有明确的边界,不会与其他数据报合并(“粘包”)或需要拆分(“拆包”)。应用程序使用UDP时,需要自行处理数据完整性、顺序和重传等问题。

3.粘包拆包发生场景

因为TCP是面向流,没有边界,而操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。

  • 如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。
  • 如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。

关于粘包和拆包可以参考下图的几种情况:

  • 理想状况:两个数据包逐一分开发送。
  • 粘包:两个包一同发送。
  • 拆包:Server接收到不完整的或多出一部分的数据包。

4.TCP黏包

黏包服务端

package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
)

// socket_stick/server/main.go

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	var buf [1024]byte
	for {
		n, err := reader.Read(buf[:])
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("收到client发来的数据:", recvStr)
	}
}

func main() {

	listen, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		go process(conn)
	}
}

黏包客户端

package main

import (
	"fmt"
	"net"
)

// socket_stick/client/main.go

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("dial failed, err", err)
		return
	}
	defer conn.Close()
	for i := 0; i < 10; i++ {
		msg := `你好,吃了吗?`
		conn.Write([]byte(msg))
	}
}

客户端分10次发送的数据,在服务端并没有成功的输出10次,而是多条数据“粘”到了一起。 

为什么会出现粘包

主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。

“粘包"可发生在发送端也可发生在接收端:

  1. 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
  2. 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

出现"粘包"的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。

封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入"包尾"内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。

我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。

解决方案

自定义协议
package proto

import (
	"bufio"
	"bytes"
	"encoding/binary"
)

// Encode 将消息编码
func Encode(message string) ([]byte, error) {
	// 读取消息的长度,转换成int32类型(占4个字节)
	var length = int32(len(message))
	var pkg = new(bytes.Buffer)
	// 写入消息头
	err := binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		return nil, err
	}
	// 写入消息实体
	err = binary.Write(pkg, binary.LittleEndian, []byte(message))
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}

// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {
	// 读取消息的长度
	lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length int32
	err := binary.Read(lengthBuff, binary.LittleEndian, &length)
	if err != nil {
		return "", err
	}
	// Buffered返回缓冲中现有的可读取的字节数。
	if int32(reader.Buffered()) < length+4 {
		return "", err
	}

	// 读取真正的消息数据
	pack := make([]byte, int(4+length))
	_, err = reader.Read(pack)
	if err != nil {
		return "", err
	}
	return string(pack[4:]), nil
}
服务端
package main

import (
	"bufio"
	"csdn/proto"
	"fmt"
	"io"
	"net"
)

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	for {
		msg, err := proto.Decode(reader)
		if err == io.EOF {
			return
		}
		if err != nil {
			fmt.Println("decode msg failed, err:", err)
			return
		}
		fmt.Println("收到client发来的数据:", msg)
	}
}

func main() {

	listen, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		go process(conn)
	}
}
客户端
package main

import (
	"csdn/proto"
	"fmt"
	"net"
)

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("dial failed, err", err)
		return
	}
	defer conn.Close()
	for i := 0; i < 10; i++ {
		msg := `你好,吃了吗?`
		data, err := proto.Encode(msg)
		if err != nil {
			fmt.Println("encode msg failed, err:", err)
			return
		}
		conn.Write(data)
	}
}

5.TCP拆包

为什么会出现TCP拆包

TCP拆包现象主要由以下几个原因导致:

  1. 网络传输限制 - 最大传输单元(MTU):TCP协议允许发送端将数据划分为多个数据段(segments)进行发送。这些数据段在传输过程中可能会遇到网络路径中不同设备的最大传输单元(MTU)限制。MTU是指网络层(如IP层)能够承载的最大数据包尺寸,通常以字节为单位。如果一个TCP数据段的大小超过了路径中某个设备的MTU,该设备会将数据包进行分片(fragmentation),将其分割成多个较小的数据包以便通过网络。这些分片数据包可能独立地经过网络传输,并在接收端以不同的顺序到达。尽管TCP协议会确保这些分片数据包在接收端按正确的顺序重组,但应用层接收到的数据可能不再是发送端最初连续发送的形式,从而表现为拆包。

  2. 接收端缓冲区满:接收端的TCP连接有一个缓冲区用于暂存接收到的数据。当接收端处理数据的速度赶不上数据到达的速度,或者由于其他原因导致缓冲区空间不足时,新的数据段可能会被推迟接收。这种情况下,即使原本连续的数据段在传输过程中没有被网络设备分片,也可能因为接收端暂时无法接纳而被迫分开接收,从而在应用层看起来像是发生了拆包。

  3. 网络拥塞:在网络拥塞时,数据包可能会在网络中经历更长的延迟,甚至被路由器暂时存储(队列化)。这可能导致原本连续发送的数据段到达接收端的顺序被打乱,或者由于接收端TCP窗口大小限制,只能接收部分数据,从而出现拆包现象。

综上所述,TCP拆包主要源于网络传输中的MTU限制导致的数据分片、接收端缓冲区处理能力的限制,以及网络拥塞等因素。为应对拆包问题,通常需要在应用层设计合适的协议或策略,如消息定界、使用更高层协议、合理设置接收缓冲区大小、实现应用层重传机制等。

常见方案

  1. 固定长度:发送端将每个消息固定为相同的长度,接收端按照固定长度进行拆包。这样可以确保每个消息的长度是一致的,但是对于不同长度的消息可能会浪费一些空间。
  2. 分隔符:发送端在每个消息的末尾添加一个特殊的分隔符(比如换行符或特殊字符),接收端根据分隔符进行拆包。这种方法适用于消息中不会出现分隔符的情况。
  3. 消息长度前缀:发送端在每个消息前面添加一个固定长度的消息长度字段,接收端先读取消息长度字段,然后根据长度读取相应长度的数据。这种方法可以准确地拆分消息,但需要保证消息长度字段的一致性。

四、Golang中使用UDP

在Go语言中使用UDP(User Datagram Protocol)进行网络通信,您可以使用标准库net包提供的net.UDPConn接口。以下是一个简单的UDP服务器和客户端示例:

UDP服务端

package main

import (
	"fmt"
	"net"
)

func main() {
	// 监听指定的UDP端口
	serverAddr, err := net.ResolveUDPAddr("udp", ":8080")
	if err != nil {
		fmt.Println("Error resolving address:", err)
		return
	}

	listener, err := net.ListenUDP("udp", serverAddr)
	if err != nil {
		fmt.Println("Error listening:", err)
		return
	}
	defer listener.Close()

	fmt.Println("UDP server listening on :8080")

	buf := make([]byte, 1024)

	for {
		n, remoteAddr, err := listener.ReadFromUDP(buf)
		if err != nil {
			fmt.Println("Error reading:", err)
			continue
		}

		message := buf[:n]
		fmt.Printf("Received message from %v: %s\n", remoteAddr, message)

		// 处理消息并回复(可选)
		response := "Hello, UDP client!"
		_, err = listener.WriteToUDP([]byte(response), remoteAddr)
		if err != nil {
			fmt.Println("Error writing:", err)
		}
	}
}

UDP客户端

package main

import (
	"fmt"
	"net"
)

func main() {
	serverAddr, err := net.ResolveUDPAddr("udp", "localhost:8080")
	if err != nil {
		fmt.Println("Error resolving address:", err)
		return
	}

	conn, err := net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		fmt.Println("Error connecting:", err)
		return
	}
	defer conn.Close()

	message := "Hello, UDP server!"
	_, err = conn.Write([]byte(message))
	if err != nil {
		fmt.Println("Error sending:", err)
		return
	}

	fmt.Println("Sent message:", message)

	buf := make([]byte, 1024)
	n, _, err := conn.ReadFromUDP(buf)
	if err != nil {
		fmt.Println("Error reading:", err)
		return
	}

	response := buf[:n]
	fmt.Printf("Received response: %s\n", response)
}

在这个示例中,服务器监听8080端口,接收客户端发送的消息并打印出来,然后向客户端发送一条回复消息。客户端连接到服务器,发送一条消息并接收服务器的响应。注意,UDP是无连接的、不可靠的协议,不保证消息的顺序、可靠传输或重传,适用于对实时性要求较高但能容忍一定程度数据丢失的应用场景。在实际使用时,可能需要根据应用需求添加适当的错误处理、重传机制和数据校验等逻辑。

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

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

相关文章

RocketMQ 之 IoT 消息解析:物联网需要什么样的消息技术?

作者&#xff1a;林清山&#xff08;隆基&#xff09; 前言&#xff1a; 从初代开源消息队列崛起&#xff0c;到 PC 互联网、移动互联网爆发式发展&#xff0c;再到如今 IoT、云计算、云原生引领了新的技术趋势&#xff0c;消息中间件的发展已经走过了 30 多个年头。 目前&a…

整理的微信小程序日历(单选/多选/筛选)

一、日历横向多选&#xff0c;支持单日、双日、三日、工作日等选择 效果图 wxml文件 <view class"calendar"><view class"section"><view class"title flex-box"><button bindtap"past">上一页</button&…

00-JAVA基础-JVM类加载机制及自定义类加载器

JVM 类加载机制 JVM类加载机制是Java运行时环境的核心部分&#xff0c;它负责将类的.class文件加载到JVM中&#xff0c;并将其转换为可以被JVM执行的数据结构。 类加载的整体流程 类加载的整体流程可以分为五个阶段&#xff1a;加载&#xff08;Loading&#xff09;、链接&a…

java数据结构与算法刷题-----LeetCode238. 除自身以外数组的乘积

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 动态规划&#xff1a;左右乘积列表2. 滚动数组对动态规划过程…

flutter组件_AlertDialog

官方说明&#xff1a;A Material Design alert dialog. 翻译&#xff1a;一个材料设计警告对话框。 作者释义&#xff1a;显示弹窗&#xff0c;类似于element ui中的Dialog组件。 AlertDialog的定义 const AlertDialog({super.key,this.icon,this.iconPadding,this.iconColor,t…

Cortex-M4架构

第一章 嵌入式系统概论 1.1 嵌入式系统概念 用于控制、监视或者辅助操作机器和设备的装置&#xff0c;是一种专用计算机系统。 更宽泛的定义&#xff1a;是在产品内部&#xff0c;具有特定功能的计算机系统。 1.2 嵌入式系统组成 硬件 ①处理器&#xff1a;CPU ②存储器…

分布式事务 - 个人笔记 @by_TWJ

目录 1. 传统事务1.1. 事务特征1.2. 事务隔离级别1.2.1. 表格展示1.2.2. oracle和mysql可支持的事务隔离级别 2. 分布式事务2.1. CAP指标2.2. BASE理论2.3. 7种常见的分布式事务方案2.3.1. 2PC2.3.2. 3PC2.3.3. TCC2.3.3.1. TCC的注意事项&#xff1a;2.3.3.2. TCC方案的优缺点…

【Java面试题】JVM(26道)

文章目录 JVM面试题基础1.什么是JVM&#xff1f;2.JVM的组织架构&#xff1f; 内存管理3.JVM的内存区域是什么&#xff1f;3.1堆3.2方法区3.3程序计数器3.4Java虚拟机栈3.5本地方法栈 4.堆和栈的区别是什么&#xff1f;5.JDK1.6、1.7、1.8内存区域的变化&#xff1f;6.内存泄露…

mynet开源库

1.介绍 个人实现的c开源网络库&#xff0e; 2.软件架构 1.结构图 2.基于event的自动分发机制 3.多优先级分发队列&#xff0c;延迟分发队列 内部event服务于通知机制的优先级为0&#xff0c;外部event优先级为1&#xff0e; 当集中处理分发的event_callback时&#xff0c…

鸿蒙ArkUI声明式学习:【UI资源管理】

OpenHarmony 应用的资源分类和资源的访问以及应用开发使用的像素单位以及各单位之间相互转换的方法。 资源分类 移动端应用开发常用到的资源比如图片&#xff0c;音视频&#xff0c;字符串等都有固定的存放目录&#xff0c;OpenHarmony 把这些应用的资源文件统一放在 resourc…

线程的666种状态

文章目录 在Java中&#xff0c;线程有以下六种状态&#xff1a; NEW&#xff1a;新建状态&#xff0c;表示线程对象已经被创建但还未启动。RUNNABLE&#xff1a;可运行状态&#xff0c;表示线程处于就绪状态&#xff0c;等待系统分配CPU资源执行。BLOCKED&#xff1a;阻塞状态…

Centos Docker Oracle11g 密码过期修改

症状&#xff1a; Centos Oracle11g环境变量配置 如果没有配置环境变量&#xff0c;需要先配置Oracle环境变量&#xff0c;否则执行sqlplus时会提示&#xff1a;SP2-0750: You may need to set ORACLE_HOME to your Oracle software directory 配置方法&#xff1a; 第一步&a…

《系统架构设计师教程(第2版)》第8章-系统质量属性与架构评估-03-ATAM方法架构评估实践(下)

文章目录 3. 测试阶段3.1 头脑风暴和优先场景&#xff08;第7步&#xff09;3.1.1 理论部分3.1.2 示例 3.2 分析架构方法&#xff08;第8步&#xff09;3.2.1 调查架构方法1&#xff09;安全性2&#xff09;性能 3.2.2 创建分析问题3.2.3 分析问题的答案胡佛架构银行体系结构 3…

初学ELK - elk部署

一、简介 ELK是3个开源软件组合&#xff0c;分别是 Elasticsearch &#xff0c;Logstash&#xff0c;Kibana Elasticsearch &#xff1a;是个开源分布式搜索引擎&#xff0c;提供搜集、分析、存储数据三大功能。它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自…

开源免费的多功能PDF工具箱

它支持修改PDF、编辑PDF书签、导出PDF书签、导入书签、生成、合并、拆分、提取页面内容、提取图片、OCR 功能介绍: 修改PDF信息&#xff1a;修改文档属性、页码编号、页面链接、页面尺寸&#xff1b;删除自动打开网页等动作&#xff0c;去除复制及打印限制&#xff1b;设置阅读…

ios swift5 “Sign in with Apple“(使用苹果登录)怎样接入(第三方登录)集成AppleID登录

文章目录 截图1.在开发者网站的app id中添加Sign in with Apple功能2.在Xcode中添加Sign in with Apple功能3.代码&#xff1a;只有第一次登录的时候可以获取到用户名参考博客chatGPT答案 截图 1.在开发者网站的app id中添加Sign in with Apple功能 1.1 如果你新建app id,记得在…

OpenHarmony实战:物联网解决方案之芯海cst85芯片移植案例

本文介绍基于芯海cst85芯片的cst85_wblink开发板移植OpenHarmony LiteOS-M轻量系统的移植案例。 开发了Wi-Fi连接样例和XTS测试样例&#xff0c;同时实现了wifi_lite, lwip, startup, utils, xts, hdf等部件基于OpenHarmony LiteOS-M内核的适配。 移植架构上采用Board和Soc分…

流式密集视频字幕

流式密集视频字幕 摘要1 IntroductionRelated Work3 Streaming Dense Video Captioning Streaming Dense Video Captioning 摘要 对于一个密集视频字幕生成模型&#xff0c;预测在视频中时间上定位的字幕&#xff0c;理想情况下应该能够处理长的输入视频&#xff0c;预测丰富、…

GEE:样本点的样式设置

作者:CSDN @ _养乐多_ 本文将介绍在Google Earth Engine (GEE)平台上为样本点设置样式的方法和代码,样本点可以设置成任何颜色,以及7种形状,以便更直观了解数据的分布和特征。 文章目录 一、统一设置样式1.1 示例代码1.2 示例代码链接二、每一类一个样式2.1 示例代码2.2…

React - 你使用过高阶组件吗

难度级别:初级及以上 提问概率:55% 高阶组件并不能单纯的说它是一个函数,或是一个组件,在React中,函数也可以做为一种组件。而高阶组件就是将一个组件做为入参,被传入一个函数或者组件中,经过一定的加工处理,最终再返回一个组件的组合…