在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

5 回复

我正在尝试模拟一个传感器服务器,该服务器每隔1秒接收一次数据。

更多关于在Golang中创建简单Web服务时遇到的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢geosoft1,但我需要的不是这样的方案。我想要的是能够每隔一秒持续发送消息(数据)的方案。目前我的服务器已经在发送数据了,现在只需要一个能够每秒获取数据的客户端应用程序,希望您能明白我的需求。

我想编写一个客户端脚本,用于监听服务器并在接收到数据时立即打印。

我不明白您为什么要为此使用 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)在超时前没有接收到任何数据,导致无输出。以下是关键问题和修复方案:

问题分析:

  1. 数据流延迟getLiveDataStream 中的 time.Sleep(time.Second) 导致每个数据项发送间隔1秒。curl 默认不会等待长时间流式响应,可能在第一个数据到达前就超时断开。
  2. 处理程序阻塞handler 函数中的无限循环 for { ... } 会持续运行,即使客户端已断开(通过 ctx.Done() 检测),但如果没有数据或超时,循环不会退出,除非显式返回。
  3. 数据通道使用:通道 data_changetLiveDataStream 中启动 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

此修复确保了服务器在客户端连接期间持续发送数据,并在客户端断开时正确清理资源。

回到顶部