当前位置: 首页 > news >正文

quic-go实现屏幕广播程序

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

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

功能(实现)说明:

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

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

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

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


服务端代码:

package mainimport ("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 MBMaxStreamReceiveWindow:         6 << 20,  // 6 MBInitialConnectionReceiveWindow: 2 << 20,  // 2 MBMaxConnectionReceiveWindow:     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 []bytefor {// 捕获桌面屏幕img, err := captureScreen()if err != nil {log.Fatal(err)}// 将图像编码为 PNG 格式var buf bytes.Buffererr = 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 mainimport ("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 streamstream, 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.Picturepic := 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环境…

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


http://www.mrgr.cn/news/46405.html

相关文章:

  • 使用 Multer 上传图片到阿里云 OSS
  • 【解决】okhttp的java.lang.IllegalStateException: closed错误
  • 【理论】测试框架体系TDD、BDD、ATDD、MBT、DDT介绍
  • JavaScript高级程序设计基础(十三)
  • 21天学通C++第八章——指针
  • 怎么实现Redis的高可用?
  • 如何编写测试用例
  • AVL树
  • 详谈7麦阵列
  • 力扣hot100--链表
  • 给网站加加速!下一代CDN(EdgeOne/边缘安全加速)使用与配置体验
  • gradle降级
  • QGridLayout Class
  • 趋势追踪:深度解析“单阳不破”形态
  • C#操作SqlServer数据库语句
  • 【RAG论文精读3】RAG论文综述1(2312.10997)-第1部分
  • fiddler手机抓包
  • MySQL(八)——索引
  • java内置的四种函数式接口
  • AD原理图编译出现Net XX has no driving source
  • 微信5大隐藏技巧,让你成为聊天高手
  • Code Review Item
  • RabbitMQ初识
  • Math Reference Notes: 常用求极限方法
  • 怎么样做好用户分层,精细化运营私域流量?
  • 入门篇-3 数据结构在编程语言中的应用