在Golang中创建简单Web服务时遇到的问题
在Golang中创建简单Web服务时遇到的问题 我编写了一个基础服务器,用于从API获取模拟传感器数据,并将其传递给我打算构建的客户端应用程序。
以下是服务器代码:
package main
import (
"fmt"
"log"
"net/http"
"time"
"io/ioutil"
"encoding/json"
)
type MyData []struct {
TimeStamp int64 `json:"timeStamp"`
Y0 string `json:"y0"`
Y1 string `json:"y1"`
Y2 string `json:"y2"`
Y3 string `json:"y3"`
}
func main(){
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
func handler(w http.ResponseWriter,r *http.Request){
log.Println("handler Started ")
defer log.Println("handler Ended")
ctx := r.Context()
data_chan := make(chan interface{})
defer close(data_chan)
defer fmt.Println("ok")
cha := getLiveDataStream(data_chan)
for {
select {
case v := <-cha:
fmt.Printf("Hello i am hit by: %v \n", v)
fmt.Fprintln(w, v)
case <-time.After(time.Second * 10 ):
fmt.Println("overslept ")
case <- ctx.Done():
err := ctx.Err()
log.Print(err)
http.Error(w, err.Error(),http.StatusInternalServerError)
return
}
}
}
func getLiveDataStream(data_chan chan interface{}) chan interface {} {
url := "https://raw.githubusercontent.com/shahidammer/timeseries_data/master/data.json"
res, err := http.Get(url)
defer res.Body.Close()
if err != nil {log.Fatal(err)}
if res.StatusCode != http.StatusOK {log.Fatal(res.Status)}
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {log.Fatal(readErr)}
data := MyData{}
err = json.Unmarshal([]byte(body), &data)
if err != nil {fmt.Println(err);return nil}
go func(){
for _, v := range data {
time.Sleep(time.Second)
data_chan <- v
}
}()
return data_chan
}
当我使用 curl localhost:8080 进行测试时,没有任何输出,但当我移除 time.sleep 后,就能看到输出。
我想编写一个客户端脚本,用于监听服务器并在获取数据时打印出来。
对于这个简单的问题表示抱歉,我刚开始学习Go语言。
更多关于在Golang中创建简单Web服务时遇到的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我正在尝试模拟一个传感器服务器,该服务器每隔1秒接收一次数据。
更多关于在Golang中创建简单Web服务时遇到的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我想编写一个客户端脚本,用于监听服务器并在接收到数据时立即打印。
我不明白您为什么要为此使用 Go 协程和通道。我认为简单的反序列化并输出到控制台就足够了。
类似这样? 😉
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
)
type Sensor struct {
TimeStamp int64 `json:"timeStamp"`
Y0 string `json:"y0"`
Y1 string `json:"y1"`
Y2 string `json:"y2"`
Y3 string `json:"y3"`
}
var Sensors []Sensor
func main() {
// curl -X GET 'http://localhost:8080'
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
res, _ := http.Get("https://raw.githubusercontent.com/shahidammer/timeseries_data/master/data.json")
body, _ := ioutil.ReadAll(res.Body)
json.Unmarshal(body, &Sensors)
//fmt.Printf("%v", Sensors)
time.Sleep(time.Second)
for _, r := range Sensors {
fmt.Println(r.TimeStamp, r.Y0, r.Y1, r.Y2, r.Y3)
}
})
http.ListenAndServe(":8080", nil)
}
在您的代码中,问题出现在服务器处理程序的循环逻辑和客户端连接处理上。当使用 time.Sleep 时,数据流被延迟发送,而客户端(如 curl)在超时前没有接收到任何数据,导致无输出。以下是关键问题和修复方案:
问题分析:
- 数据流延迟:
getLiveDataStream中的time.Sleep(time.Second)导致每个数据项发送间隔1秒。curl 默认不会等待长时间流式响应,可能在第一个数据到达前就超时断开。 - 处理程序阻塞:
handler函数中的无限循环for { ... }会持续运行,即使客户端已断开(通过ctx.Done()检测),但如果没有数据或超时,循环不会退出,除非显式返回。 - 数据通道使用:通道
data_chan在getLiveDataStream中启动 goroutine 发送数据,但处理程序只读取一次数据(在case v := <-cha中),然后循环继续,导致后续数据可能无法被及时处理或客户端已断开。
修复后的代码示例:
调整处理程序逻辑,确保在客户端连接期间流式发送所有数据,并处理上下文取消。移除不必要的 time.After 超时,因为它会干扰数据流。
package main
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
type MyData []struct {
TimeStamp int64 `json:"timeStamp"`
Y0 string `json:"y0"`
Y1 string `json:"y1"`
Y2 string `json:"y2"`
Y3 string `json:"y3"`
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
log.Println("handler started")
defer log.Println("handler ended")
ctx := r.Context()
dataChan := getLiveDataStream(ctx) // 传递上下文以支持取消
for data := range dataChan {
select {
case <-ctx.Done():
log.Printf("client disconnected: %v", ctx.Err())
return
default:
fmt.Fprintf(w, "%v\n", data)
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush() // 立即发送数据到客户端
}
}
}
}
func getLiveDataStream(ctx context.Context) <-chan interface{} {
dataChan := make(chan interface{})
go func() {
defer close(dataChan)
url := "https://raw.githubusercontent.com/shahidammer/timeseries_data/master/data.json"
res, err := http.Get(url)
if err != nil {
log.Printf("HTTP request failed: %v", err)
return
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
log.Printf("HTTP status error: %s", res.Status)
return
}
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
log.Printf("read body failed: %v", readErr)
return
}
var data MyData
err = json.Unmarshal(body, &data)
if err != nil {
log.Printf("JSON unmarshal failed: %v", err)
return
}
for _, v := range data {
select {
case <-ctx.Done():
log.Printf("data stream cancelled: %v", ctx.Err())
return
case dataChan <- v:
time.Sleep(time.Second) // 模拟实时数据流间隔
}
}
}()
return dataChan
}
关键改进:
- 使用上下文传递取消信号:在
getLiveDataStream中检查ctx.Done(),确保当客户端断开时停止数据发送。 - 流式响应处理:通过
http.Flusher立即发送每个数据项到客户端,避免缓冲延迟。 - 通道范围循环:在
handler中使用for data := range dataChan读取所有数据,直到通道关闭。 - 错误处理:移除了
log.Fatal,改用log.Printf避免服务器崩溃,并返回适当错误。
客户端测试脚本示例(Go语言):
编写一个简单客户端来监听服务器并打印流式数据:
package main
import (
"fmt"
"io"
"log"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:8080/")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
buf := make([]byte, 1024)
for {
n, err := resp.Body.Read(buf)
if err != nil {
if err == io.EOF {
break
}
log.Fatal(err)
}
fmt.Print(string(buf[:n]))
}
}
运行此客户端将连续打印从服务器接收的数据。如果使用 curl,可以添加 -N 选项禁用缓冲以查看流式输出:curl -N localhost:8080。
此修复确保了服务器在客户端连接期间持续发送数据,并在客户端断开时正确清理资源。


