Go微服务网关的实现
本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《Go微服务网关的实现》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~
Go微服务网关
从核心原理理解网关的本质
网关具备的基本功能:
- 支持多种协议代理:tcp/http/ websocket/grpc
- 支持多种负载均衡策略:轮询,权重轮询,hash一致性轮询
- 支持下游的服务发现:主动探测 / 自动服务发现
- 支持横向扩展: 加机器就能解决高并发
借助网关处理高可用,高并发
- 限流:请求QPS限制
- 熔断:错误率达阈值则服务熔断
- 降级:确保核心业务可用
- 权限认证:请求拦截
网络基础大纲
OSI七层网络协议

经典协议与数据包
![ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geNZSVqb-1648194571597)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1647745113748.png)]](/uploads/20221231/167246551763afcc6d6f330.png)
http 协议
GET/HTTP/1.1 Host:www.baidu.com User-Agent:curl/7.55.1 Accept:*/*

Websocket握手协议


三次握手 与 四次挥手
三次握手的最主要的目的是保证连接是全双工的,可靠更多的是通过重传机制来保证的
因为连接是全双工的,双方必须都收到对方的FIN包及确认才可关闭
TCP报文格式:

其中比较重要的字段有:
(1)序号(sequence number):Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(2)确认号(acknowledgement number):Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
(3)标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:
URG:紧急指针(urgent pointer)有效。ACK:确认序号有效。PSH:接收方应该尽快将这个报文交给应用层。RST:重置连接。SYN:发起一个新连接。FIN:释放一个连接。
需要注意的是:
不要将确认序号Ack与标志位中的ACK搞混了。确认方Ack=发起方Seq+1,两端配对。
三次握手连接:

(1)首先客户端向服务器端发送一段TCP报文,其中:
- 标记位为`SYN,表示“请求建立新连接”;
- 序号为Seq=X(X一般为1);
- 随后客户端进入SYN-SENT阶段。
(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:
- 标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);
- 序号为Seq=y;
- 确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。
(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:
- 标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);
- 序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
- 确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
- 随后客户端进入ESTABLISHED阶段。
服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。
四次挥手:

(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:
- 标记位为FIN,表示“请求释放连接“;
- 序号为Seq=U;
- 随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。
(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:
- 标记位为ACK,表示“接收到客户端发送的释放连接的请求”;
- 序号为Seq=V;
- 确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;
- 随后服务器端开始准备释放服务器端到客户端方向上的连接。客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段
前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了
(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:
- 标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。
- 序号为Seq=W;
- 确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。
随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。
(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:
- 标记位为ACK,表示“接收到服务器准备好释放连接的信号”。
- 序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。
- 确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。随后客户端开始在TIME-WAIT阶段等待2MSL
为什么要客户端要等待2MSL呢?见后文。
服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。
客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。
后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。于是,可以确认关闭服务器端到客户端方向上的连接了,由此完成“四次挥手”。
与“三次挥手”一样,在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性,一旦出现某一方发出的TCP报文丢失,便无法继续"挥手",以此确保了"四次挥手"的顺利完成。
为什么客户端在TIME-WAIT阶段要等2MSL?
- 为的是确认服务器端是否收到客户端发出的ACK确认报文
- 保证TCP协议的全双共连接能够可靠关闭
- 保证这次连接的重复数据段从网络中消失
当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。MSL指的是(最大的生命周期)Maximum Segment Lifetime:(30秒–1分钟)一段TCP报文在传输过程中的最大生命周期。2MSL即是服务器端发出为FIN报文和客户端发出的ACK确认报文所能保持有效的最大时长。
服务器端在1MSL内没有收到客户端发出的ACK确认报文,就会再次向客户端发出FIN报文;
如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时;否则客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。
所以,客户端要经历时长为2SML的TIME-WAIT阶段;这也是为什么客户端比服务器端晚进入CLOSED阶段的原因
为啥会出现大量的close_wait
- 首先close_wait一般出现在被动关闭方
- 并发请求太多导致
- 被动关闭方未及时释放端口资源导致
func main() {
//1、监听端口
listener, err := net.Listen("tcp", "0.0.0.0:9090")
if err != nil {
fmt.Printf("listen fail, err: %v\n", err)
return
}
//2.建立套接字连接
for {
conn, err := listener.Accept()
if err != nil {
fmt.Printf("accept fail, err: %v\n", err)
continue
}
//3. 创建处理协程
go func(conn net.Conn) {
defer conn.Close() //思考题:这里不填写会有啥问题?
//服务端就有一个close,wait状态,客户端就有一个finally 状态
for {
var buf [128]byte
n, err := conn.Read(buf[:])
if err != nil {
fmt.Printf("read from connect failed, err: %v\n", err)
break
}
str := string(buf[:n])
fmt.Printf("receive from client, data: %v\n", str)
}
}(conn)
}
}
TCP为啥需要流量控制
- 由于通讯双方,网速不同。通讯方任一方发送过快都会导致对方的消息处理不过来,所以就需要数据放到缓冲区中
- 如果缓冲区满了,发送方还在疯狂发送,那接收方只能把数据包丢弃,因此我们需要控制发送速率
- 我们缓冲区剩余大小称之为接收窗口,用变量win表示,如果win=0,则发送方停止发送
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPPRtwrp-1648194571601)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1647790417307.png)]](/uploads/20221231/167246552063afcc705aa1c.png)
TCP 为啥需要拥塞控制
- 流量控制与拥塞控制是两个概念,拥塞控制是调节网络的负载
- 接收方网络资源繁忙,因未及时响应ACK导致发送方重传大量的数据,这样将会导致网络更加的拥堵
- 拥塞控制是动态调整win大小,不只是依赖缓冲区大小去确定窗口大小
TCP 拥塞控制
- 慢开始和拥塞避免
- 快速重传和快速恢复


优化步骤3到步骤4:因为网络拥塞,有24直接降到1 ,会造成堵塞
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g8ZHBvyy-1648194571602)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1647791444149.png)]](/uploads/20221231/167246552163afcc7166a58.png)
为啥会出现粘包,拆包,如何处理
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vWmWpIu-1648194571603)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1647791724023.png)]](/uploads/20221231/167246552163afcc71db0e6.png)
粘包、拆包表现形式
现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:
第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。

第二种情况,接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。

第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。

产生tcp粘包和拆包的原因
我们知道tcp是以流动的方式传输数据,传输的最小单位为一个报文段(segment)。tcp Header中有个Options标识位,常见的标识为mss(Maximum Segment Size)指的是,连接层每次传输的数据有个最大限制MTU(Maximum Transmission Unit),一般是1500比特,超过这个量要分成多个报文段,mss则是这个最大限制减去TCP的header,光是要传输的数据的大小,一般为1460比特。换算成字节,也就是180多字节。
tcp为提高性能,发送端会将需要发送的数据发送到缓冲区,等待缓冲区满了之后,再将缓冲中的数据发送到接收方。同理,接收方也有缓冲区这样的机制,来接收数据。
发生TCP粘包、拆包主要是由于下面一些原因:
- 应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
- 应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
- 进行mss(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>mss的时候将发生拆包。
- 接收方法不及时读取套接字(socket)缓冲区数据,这将发生粘包。
如何解决拆包粘包
既然知道了tcp是无界的数据流,且协议本身无法避免粘包,拆包的发生,那我们只能在应用层数据协议上,加以控制。通常在制定传输数据时,可以使用如下方法:
- 使用带消息头的协议、消息头存储消息开始标识及消息长度信息,服务端获取消息头的时候解析出消息长度,然后向后读取该长度的内容。
- 设置定长消息,服务端每次读取既定长度的内容作为一条完整消息。
- 设置消息边界,服务端从网络流中按消息编辑分离出消息内容。
如何获取完整应用数据报文
- 使用带消息头的协议,头部写入包长度,然后在读取包内容
- 设置定长消息,每次读取定长内容,长度不够时空位补固定字符
- 设置消息边界,服务端从网络流中按消息边界分离出消息内容,一般使用 ‘\n’
- 更为复杂的协议:json,protobuf
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FNUamEZv-1648194571603)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1647843018454.png)]](/uploads/20221231/167246552363afcc735c6ed.png)
如何获取完整的数据报文
func main() {
//类比接收缓冲区
bytesBuffer := bytes.NewBuffer([]byte{})
// 发送
if err := Encode(bytesBuffer, "hello world 0 !!"); err != nil {
panic(err)
}
if err := Encode(bytesBuffer, "hello world 1 !!"); err != nil {
panic(err)
}
//读取
for {
if bt, err := Decode(bytesBuffer); err == nil {
fmt.Println(string(bt))
continue
}
break
}
}
如何获取完整的数据报文
tcp_server
func main() {
//simple tcp server
//1.监听端口
listener, err := net.Listen("tcp", "127.0.0.1:9090")
if err != nil {
fmt.Printf("tcp Listen fail,err: %v\n", err)
return
}
//2.接受请求
for {
conn, err := listener.Accept()
if err != nil {
fmt.Printf("tcp Accept fail,err: %v\n", err)
continue
}
//3.创建协程
go process(conn)
}
}
//4.创建的协程里面实现解码的功能
func process(conn net.Conn) {
defer conn.Close()
for {
bt, err := unpack.Decode(conn)
if err != nil {
fmt.Printf("read from connect failed, err: %v\n", err)
break
}
str := string(bt)
fmt.Printf("receive from client, data: %v\n", str)
}
}
tcp_client
func main() {
//1.连接tcp服务器
conn, err := net.Dial("tcp", "localhost:9090")
defer conn.Close()
if err != nil {
fmt.Printf("connect failed, err : %v\n", err.Error())
return
}
//2.实现编码
unpack.Encode(conn, "hello world 0!!!")
}
**unpack ** : 实现编码(encode)和解码(docode)功能
const Msg_Header = "12345678"
// 编码
func Encode(bytesBuffer io.Writer, content string) error {
//msg_header+content_len+content
//8+4+content_len
if err := binary.Write(bytesBuffer, binary.BigEndian, []byte(Msg_Header)); err != nil {
return err
}
clen := int32(len([]byte(content)))
// binary.BigEndian 大端字节实现的加密 ,
if err := binary.Write(bytesBuffer, binary.BigEndian, clen); err != nil {
return err
}
if err := binary.Write(bytesBuffer, binary.BigEndian, []byte(content)); err != nil {
return err
}
return nil
}
// 解码
func Decode(bytesBuffer io.Reader) (bodyBuf []byte, err error) {
MagicBuf := make([]byte, len(Msg_Header))
//先读取header的大小
if _, err = io.ReadFull(bytesBuffer, MagicBuf); err != nil {
return nil, err
}
//比较得到的header和实际的Msg_Header 是否相同
if string(MagicBuf) != Msg_Header {
return nil, errors.New("msg_header error")
}
lengthBuf := make([]byte, 4)
if _, err = io.ReadFull(bytesBuffer, lengthBuf); err != nil {
return nil, err
}
// binary.BigEndian 大端字节实现的解密 ,得到实际数据的长度
length := binary.BigEndian.Uint32(lengthBuf)
bodyBuf = make([]byte, length)
if _, err = io.ReadFull(bytesBuffer, bodyBuf); err != nil {
return nil, err
}
return bodyBuf, err
}
基于golang 实现TCP,UDP,Http服务端与客户端
golang 实现UDP 服务端与客户端
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LR962weH-1648194571604)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1647852602546.png)]](/uploads/20221231/167246552363afcc73ce0be.png)
UDP服务端:
func main() {
//1.监听端口
listen, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 9090,
})
if err != nil {
fmt.Printf("listen udp failed ,err:%v\n", err)
return
}
//2.循环读取消息内容
for {
var data [1024]byte
n, addr, err := listen.ReadFromUDP(data[:])
if err != nil {
fmt.Printf("read failed from addr :%v,err%v\n", addr, err)
break
}
go func() {
//3.回复数据
fmt.Printf("addr:%v data:%v count:%v\n", addr, string(data[:n]), n)
_, err = listen.WriteToUDP([]byte("received success!"), addr)
if err != nil {
fmt.Printf("write failed,err :%v\n", err)
return
}
}()
}
}
udp客户端
func main() {
//1. 连接udp服务器
conn, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 9090,
})
if err != nil {
fmt.Printf("connect failed ,err %v\n", err)
return
}
for i := 0; i
<p>golang实现tcp的服务端和客户端</p>
<p style="text-align:center"><img alt="[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vIA4cqIv-1648194571605)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1647858500637.png)]" src="/uploads/20221231/167246552463afcc7419843.png"></p>
<p><strong>tcp 服务端</strong></p>
<pre class="brush:go;">func main() {
//1、监听端口
listener, err := net.Listen("tcp", "0.0.0.0:9090")
if err != nil {
fmt.Printf("listen fail, err: %v\n", err)
return
}
//2.建立套接字连接
for {
conn, err := listener.Accept()
if err != nil {
fmt.Printf("accept fail, err: %v\n", err)
continue
}
//3. 创建处理协程
go process(conn)
}
}
func process(conn net.Conn) {
defer conn.Close() //思考题:这里不填写会有啥问题?
for {
var buf [128]byte
n, err := conn.Read(buf[:])
if err != nil {
fmt.Printf("read from connect failed, err: %v\n", err)
break
}
str := string(buf[:n])
fmt.Printf(" from client, data: %v\n", str)
}
}
tcp客户端
golang实现Http的服务端和客户端
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUVM7LpJ-1648194571605)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1647960142348.png)]](/uploads/20221231/167246552463afcc74767f4.png)
http服务端
var (
Addr = ":8000"
)
// http的服务器
func main() {
//1.创建路由器
mux := http.NewServeMux()
// 2. 设置路由规则
mux.HandleFunc("/bye", sayBye)
// 3.创建服务器
server := &http.Server{
Addr: Addr,
WriteTimeout: time.Second * 3,
Handler: mux,
}
// 4. 监听端口并提供服务
log.Println("starting httpServer at" + Addr)
log.Fatal(server.ListenAndServe())
}
func sayBye(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
w.Write([]byte("bye bye,this is httpserver"))
}
http客户端
func main() {
//1. 创建连接池
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, // 超时时间
KeepAlive: 30 * time.Second, //长连接时间
}).DialContext,
MaxIdleConns: 100, //最大空闲连接数
IdleConnTimeout: 90 * time.Second, // 空闲超时时间
TLSHandshakeTimeout: 10 * time.Second, // tls握手超时时间
ExpectContinueTimeout: 1 * time.Second, // 100-continue 状态码超时时间
}
//2. 创建客户端
client := &http.Client{
Timeout: 30 * time.Second,
Transport: transport,
}
//3.请求数据
resp, err := client.Get("http://127.0.0.1:8000/bye")
if err != nil {
fmt.Println("client get url failed ", err)
return
}
defer resp.Body.Close()
//4.读取内容
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Read body failed ", err)
return
}
fmt.Println(string(b))
}
Http 服务器源码解读
- 阅读源代码的原则:先整体在局部,先看脑图在逐一分析
- 注册路由:理解函数是一等公民以及注册原理
- 开启服务
- 处理连接
函数是一等公民
type HandleFunc func(http.ResponseWriter, *http.Request)
func (f HandleFunc) ServerHTTP(w http.ResponseWriter, r *http.Request) {
f(w, r)
}
//函数是一等公民
func main() {
hf := HandleFunc(HelloHandler)
resp := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", bytes.NewBuffer([]byte("test")))
hf.ServerHTTP(resp, req)
b, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(b))
}
func HelloHandler(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("hello youMe "))
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MjVQl5gV-1648194571606)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1648017007628.png)]](/uploads/20221231/167246552463afcc74e7f1c.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7JakmfrM-1648194571606)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1648017357816.png)]](/uploads/20221231/167246552563afcc7568099.png)
今天关于《Go微服务网关的实现》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
Golang基础教程之字符串string实例详解
- 上一篇
- Golang基础教程之字符串string实例详解
- 下一篇
- Golang爬虫框架colly的使用
-
- Golang · Go教程 | 3小时前 | 格式化输出 printf fmt库 格式化动词 Stringer接口
- Golangfmt库用法与格式化技巧解析
- 140浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Golang配置Protobuf安装教程
- 147浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Golang中介者模式实现与通信解耦技巧
- 378浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Golang多协程通信技巧分享
- 255浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Golang如何判断变量类型?
- 393浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Golang云原生微服务实战教程
- 310浏览 收藏
-
- Golang · Go教程 | 4小时前 |
- Golang迭代器与懒加载结合应用
- 110浏览 收藏
-
- Golang · Go教程 | 4小时前 | 性能优化 并发安全 Golangslicemap 预设容量 指针拷贝
- Golangslicemap优化技巧分享
- 412浏览 收藏
-
- Golang · Go教程 | 4小时前 |
- Golang代理模式与访问控制实现解析
- 423浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- Golang事件管理模块实现教程
- 274浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3164次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3376次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3405次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4509次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3785次使用
-
- Golangmap实践及实现原理解析
- 2022-12-28 505浏览
-
- go和golang的区别解析:帮你选择合适的编程语言
- 2023-12-29 503浏览
-
- 试了下Golang实现try catch的方法
- 2022-12-27 502浏览
-
- 如何在go语言中实现高并发的服务器架构
- 2023-08-27 502浏览
-
- 提升工作效率的Go语言项目开发经验分享
- 2023-11-03 502浏览

