自学内容网 自学内容网

quic-go实现屏幕广播程序

最近在折腾quic-go, 突然想起屏广适合用udp实现,而http3基于quic-go,后者又基于udp, 所以玩一下。

先贴出本机运行效果图:
在这里插入图片描述

功能(实现)说明:

1.服务器先启动作为共享屏幕方,等待客户端连接上来
2.客户端连接
3.客户端和服务器建立连接后,服务器主动打开stream

在一个for 循环中:每秒操作30次下面操作:

4.服务器开始抓取本机屏幕内容,转换成Image
5.数据传输协议:Image字节长度 + Image内容

6.客户端按上述协议接收数据,解析成Image对象,放界面上展示


服务端代码:

package main

import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/binary"
"encoding/pem"
"fmt"
"github.com/quic-go/quic-go"
"image"
"image/png"
"log"
"math/big"
"os"
"time"

"crypto/tls"
"github.com/kbinani/screenshot"
)

const addr = "localhost:4000"

var currentDir, _ = os.Getwd()

var quicConf = &quic.Config{
Allow0RTT:                      true,
MaxIdleTimeout:                 40 * time.Second,
InitialStreamReceiveWindow:     1 << 20,  // 1 MB
MaxStreamReceiveWindow:         6 << 20,  // 6 MB
InitialConnectionReceiveWindow: 2 << 20,  // 2 MB
MaxConnectionReceiveWindow:     12 << 20, // 12 MB
}

func main() {
//listener, err := quic.ListenAddr(addr, generateTLSConfig(), quicConf)
listener, err := quic.ListenAddr(addr, generateTLSConfig2(), quicConf)
if err != nil {
log.Fatal(err)
}
fmt.Println("Server listening on", addr)

for {
// 接受客户端连接
sess, err := listener.Accept(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println("New client connected")
go handleConnection(sess)
}
}

func handleConnection(sess quic.Connection) {
stream, err := sess.OpenStream()
if err != nil {
log.Fatal(err)
}
fmt.Println("New stream opened:", stream.StreamID())
defer stream.Close()
var b []byte
for {
// 捕获桌面屏幕
img, err := captureScreen()
if err != nil {
log.Fatal(err)
}

// 将图像编码为 PNG 格式
var buf bytes.Buffer
err = png.Encode(&buf, img)
if err != nil {
log.Fatal(err)
}

// magic校验
//n, err := stream.Write([]byte{0x05, 0x19})
//if err != nil {
//log.Fatal(err)
//}
b = buf.Bytes()
//var headLenBuf = make([]byte, 4)
//binary.BigEndian.PutUint32(headLenBuf, uint32(len(b)))
//_, err = stream.Write(headLenBuf)
err = binary.Write(stream, binary.BigEndian, uint32(len(b)))
if err != nil {
log.Fatal(err)
}
// 将图像数据发送到客户端
_, err = stream.Write(b)
if err != nil {
log.Fatal(err)
}
// 每秒捕获并传输一帧
time.Sleep(1 * time.Second / 30)
}
}

func captureScreen() (image.Image, error) {
bounds := screenshot.GetDisplayBounds(0) // 捕获主屏幕
img, err := screenshot.CaptureRect(bounds)
if err != nil {
return nil, err
}
return img, nil
}

/*
*
openssl req -x509 -newkey rsa:4096 -keyout privkey.pem -out cert.pem -days 365 -nodes
*/
func generateTLSConfig() *tls.Config {
// 使用自签名证书
// goland运行使用它
cert, err := tls.LoadX509KeyPair(currentDir+"/screenbroadcast/cert.pem", currentDir+"/screenbroadcast/privkey.pem")
// 命令行运行使用它
//cert, err := tls.LoadX509KeyPair("cert.pem", "privkey.pem")
if err != nil {
log.Fatal(err)
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos:   []string{"h3-29"},
}
}

func generateTLSConfig2() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})

tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos:   []string{"h3-29"},
}
}

客户端代码:

package main

import (
"bytes"
"context"
"crypto/tls"
"encoding/binary"
"fmt"
"github.com/quic-go/quic-go"
"image"
"image/png"
"io"
"log"
"time"

"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
)

const addr = "localhost:4000"

var headLenBuf = make([]byte, 4)

func main() {
pixelgl.Run(run)
}

func run() {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos:         []string{"h3-29"},
}

quicConfig := &quic.Config{
MaxIdleTimeout:  40 * time.Second,
KeepAlivePeriod: 30 * time.Second, // 使用quic的心跳机制
}
// 创建 QUIC 连接到服务器
sess, err := quic.DialAddr(context.Background(), addr, tlsConf, quicConfig)
if err != nil {
log.Fatal(err)
}

// 接收一个 QUIC stream:没错,是server主动推送数据过来,先发起的open stream
stream, err := sess.AcceptStream(context.Background())
if err != nil {
log.Fatal(err)
}

// 创建窗口显示接收的屏幕图像
cfg := pixelgl.WindowConfig{
Title:     "Screen Broadcast",
Bounds:    pixel.R(0, 0, 1024, 680),
VSync:     true,
Resizable: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
log.Fatal(err)
}
for !win.Closed() {
// 接收图像数据
img, err := receiveImage(stream)
if err != nil {
if err == io.EOF {
break
}
log.Fatal(err)
}

// 将图像转换为 pixel.Picture
pic := pixel.PictureDataFromImage(img)

// 绘制图像
sprite := pixel.NewSprite(pic, pic.Bounds())
win.Clear(pixel.RGB(0, 0, 0))
sprite.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
win.Update()
}
}

func receiveImage(stream quic.Stream) (image.Image, error) {
//_, err := io.ReadFull(stream, headLenBuf[:2])
//if err != nil {
//return nil, err
//}
//if headLenBuf[0] != 0x05 && headLenBuf[1] != 0x19 {
//return nil, errors.New("invalid magic")
//}
_, err := io.ReadFull(stream, headLenBuf)
if err != nil {
fmt.Println("video Error reading:", err.Error())
return nil, err
}
headLen := binary.BigEndian.Uint32(headLenBuf)
var buf bytes.Buffer
// 从 QUIC stream 读取图像数据
_, err = io.CopyN(&buf, stream, int64(headLen))
if err != nil {
return nil, err
}

// 解码 PNG 图像
img, err := png.Decode(&buf)
if err != nil {
return nil, err
}

return img, nil
}

下面开始说其中涉及到的坑:

当我本机(mac m1) OS版本为 12.1 时,运行服务器程序失败:

../../../../go/pkg/mod/github.com/kbinani/screenshot@v0.0.0-20240820160931-a8a2c5d0e191/darwin.go:9:10: fatal error:
'ScreenCaptureKit/ScreenCaptureKit.h' file not found
#include <ScreenCaptureKit/ScreenCaptureKit.h>

网上说升级系统到12.3+,因为ScreenCaptureKit 是 macOS 12.3 及更高版本中引入的 API,用于捕获屏幕内容。但是我升级到12.7.6后仍然报错…
在这里插入图片描述

然后看github.com/kbinani/screenshot源码:我当前下载的screenshot版本需要14.4+ ?

#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > MAC_OS_VERSION_14_4

FYI:我不敢升级到15版本,,,不敢。。。只是小版本升级

最后解决办法:使用低版本的screenshot:
去官网:https://pkg.go.dev/github.com/kbinani/screenshot@v0.0.0-20240820160931-a8a2c5d0e191/example?tab=versions
在这里插入图片描述
使用低版本的2023试试:

jelex@jelexxudeMacBook-Pro screenbroadcast % go get github.com/kbinani/screenshot@v0.0.0-20230831090513-3e604f0f372a

最后果然没问题了!

坑二:client程序无法交叉编译打包

在这里插入图片描述
我没有在windows电脑上验证,如果有使用windows版本的golang使用者看到本篇后,是否可以帮忙打包验证?

坑三:打包服务端程序成exe,在另一台电脑上运行,本机mac 作为客户端连接后没反应,直到超时报错退出:
2024/10/09 15:29:43 timeout: no recent network activity

是否有道友愿意联调?FYI: 我周边没有golang开发者,他们电脑上没安装golang环境…

或者有大佬知道这个问题能直接赐教吗?


原文地址:https://blog.csdn.net/csdnfanguyinheng/article/details/142787806

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!