今天学流的 I/O
hertz server 的流读/写示例
流式读
/*
* Copyright 2022 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"context"
"io/ioutil"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main() {
h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true))
h.POST("/bodyStream", handler)
h.Spin()
}
func handler(ctx context.Context, c *app.RequestContext) {
// Acquire body streaming
bodyStream := c.RequestBodyStream()
// Read half of body bytes
p := make([]byte, c.Request.Header.ContentLength()/2)
r, err := bodyStream.Read(p)
if err != nil {
panic(err)
}
left, _ := ioutil.ReadAll(bodyStream)
c.String(consts.StatusOK, "bytes streaming_read: %d\nbytes left: %d\n", r, len(left))
}
这个代码要运行需要配合客户端的代码(此处略过客户端代码):
go run streaming/streaming_read/main.go
这个代码仔细看可以发现每次读 request 的一半,然后值得借鉴的是这里把 POST 请求的逻辑写到外面了。
流式写
/*
* Copyright 2022 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"bytes"
"context"
"fmt"
"sync"
"time"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
)
func main() {
h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true))
h.GET("/streamWrite", handler1)
// Demo: synchronized reading and writing
h.GET("/syncWrite", handler2)
h.Spin()
}
func handler1(ctx context.Context, c *app.RequestContext) {
bs := []byte("hello, hertz!")
wb := bytes.NewBuffer(bs)
c.SetBodyStream(wb, len(bs))
}
func handler2(ctx context.Context, c *app.RequestContext) {
rw := newChunkReader()
// Content-Length may be negative:
// -1 means Transfer-Encoding: chunked.
// -2 means Transfer-Encoding: identity.
c.SetBodyStream(rw, -1)
go func() {
for i := 1; i < 1000; i++ {
// For each streaming_write, the upload_file prints
rw.Write([]byte(fmt.Sprintf("===%d===\n", i)))
fmt.Println(i)
time.Sleep(100 * time.Millisecond)
}
rw.Close()
}()
go func() {
<-c.Finished()
fmt.Println("request process end")
}()
}
type ChunkReader struct {
rw bytes.Buffer
w2r chan struct{}
r2w chan struct{}
}
func newChunkReader() *ChunkReader {
var rw bytes.Buffer
w2r := make(chan struct{})
r2w := make(chan struct{})
cr := &ChunkReader{rw, w2r, r2w}
return cr
}
var closeOnce = new(sync.Once)
func (cr *ChunkReader) Read(p []byte) (n int, err error) {
for {
_, ok := <-cr.w2r
if !ok {
closeOnce.Do(func() {
close(cr.r2w)
})
n, err = cr.rw.Read(p)
return
}
n, err = cr.rw.Read(p)
cr.r2w <- struct{}{}
if n == 0 {
continue
}
return
}
}
func (cr *ChunkReader) Write(p []byte) (n int, err error) {
n, err = cr.rw.Write(p)
cr.w2r <- struct{}{}
<-cr.r2w
return
}
func (cr *ChunkReader) Close() {
close(cr.w2r)
}
要运行还是分服务端和客户端,第一个终端跑服务端代码:
go run streaming/streaming_write/main.go
第二个终端跑客户端代码:
go run client/streaming_read/main.go
这段代码里有两个 GET 请求,第一个是服务端纯写客户端纯读,第二个是同时读写。纯写这个没啥好讲的,看同时读写的这个例子。首先定义一个结构体 ChunkReader,里面 rw 代表一块 buffer,然后两个 channel w2r 和 r2w 控制当前是读还是写。
读的逻辑:一个死循环里,首先等 w2r 的信号。如果没等到这个信号,就关一次 r2w,直接调用 buffer 的读方法然后返回,这里就跳出死循环了,说明此时应该是没有写的东西了。如果等到了这个信号,调用 buffer 的读方法,然后发 r2w 的信号,只要 n 不是 0 就继续死循环。
写的逻辑:直接调用 buffer 的写方法,发 w2r 的信号,然后再等 r2w 的信号。写的逻辑里没有死循环是因为在之前的 GET 请求里就处在一个循环内了。
这个地方的读写逻辑不对称这样容易分辨,整个思路顺下来感觉这里的同时读写实际上读的是写的内容,这样发的时候能看到自己发的东西对不对,便于实时监测。感觉只要是高速的应用总会有类似的结构,单片机和 FPGA 里面有双向 I/O 口,网络设备(七层架构里面的物理层)里面绝对会用到,发送数据的同时也需要自己看到自己发出去的数据格式对不对。找一张网上的截图,一看就通了:
也可能是我理解错了,这里给自己挖个坑,如果错了评论区帮帮忙纠正一下,我改成对的,以防误导之后和我一样的小白。