最全5,2024年最新2024年腾讯Golang高级面试题及答案

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

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)
}
}


![](https://img-blog.csdnimg.cn/direct/5560d90a1b6d4bab819b238ede0b2191.png)


### 5.TCP拆包


#### **为什么会出现TCP拆包**


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


![img](https://img-blog.csdnimg.cn/img_convert/76ddcb4d4291d3d91d0ec7be1f1aeaf9.png)
![img](https://img-blog.csdnimg.cn/img_convert/dfcefde3b301de632b269af015b8fd8d.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

9b238ede0b2191.png)


### 5.TCP拆包


#### **为什么会出现TCP拆包**


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


[外链图片转存中...(img-Ddwak6Ck-1715552696571)]
[外链图片转存中...(img-nHasZM05-1715552696571)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值