golang实现mediasoup的tcp服务及channel通道

tcp模块

定义相关类

  • Client:表示客户端连接,包含网络连接conn、指向服务器的指针Server和Channel指针c。
  • server:表示TCP服务器,包含服务器地址address、TLS配置config以及三个回调函数:
    • onNewClientCallback:新客户端连接时调用。
    • onClientConnectionClosed:客户端连接关闭时调用。
    • onNewMessage:客户端接收新消息时调用。

客户端相关接口

  • Client.listen():客户端监听方法,读取连接数据,调用onNewMessage回调。
  • Client.Send(message string):发送文本消息给客户端。
  • Client.SendBytes(b []byte):发送字节数据给客户端。
  • Client.Conn():获取客户端的网络连接。
  • Client.Close():关闭客户端的网络连接。

服务器相关接口

  • server.OnNewClient(callback func(c *Client)):设置新客户端连接的回调。
  • server.OnClientConnectionClosed(callback func(c *Client, err error)):设置客户端连接关闭的回调。
  • server.OnNewMessage(callback func(c *Client, message []byte, size int)):设置客户端接收新消息的回调。
  • server.Listen():启动网络服务器,监听连接。

服务器初始化

  • NewTcpServer(address string) *server:创建新的TCP服务器实例,不使用TLS。
  • NewTcpServerWithTLS(address, certFile, keyFile string) *server:创建带有TLS功能的TCP服务器实例。

服务器启动流程

  1. 使用NewTcpServer或NewTcpServerWithTLS创建服务器实例。
  2. 设置回调函数,响应新客户端连接、客户端连接关闭和接收新消息事件。
  3. 调用server.Listen()开始监听连接。

TLS支持

  • 如果需要TLS,使用NewTcpServerWithTLS函数,提供证书和密钥文件路径。

参考demo

package MediasoupLib

import (
	"bufio"
	"time"
	"crypto/tls"
	"log"
	"net"
)

// Client holds info about connection

type Client struct {
	conn net.Conn
	Server *server
	c *Channel
}

// TCP server
type server struct {
	address string // Address to open connection: localhost:9999
	config *tls.Config
	onNewClientCallback func(c *Client)
	onClientConnectionClosed func(c *Client, err error)
	onNewMessage func(c *Client, message []byte, size int)
}

// Read client data from channel
func (c *Client) listen() {
	fmt.Printf("tcp client listen() ")
	c.Server.onNewClientCallback(c)

	reader := bufio.NewReader(c.conn)
	for {
		recv := make([]byte, 1500) //MTU 1500
		size, err := reader.Read(recv)

		if err != nil {
			c.conn.Close()
			c.Server.onClientConnectionClosed(c, err)
			fmt.Printf("tcp client close! %s", err.Error())
			return
		}
		
		if size == 0 {
			time.Sleep(time.Millisecond * 250)
			fmt.Printf("tcp client recv size=0")
			continue
		}
		
		recv = recv[0:size]
		c.Server.onNewMessage(c, recv, size)
	}
}

// Send text message to client
func (c *Client) Send(message string) error {
	return c.SendBytes([]byte(message))
}

// Send bytes to client
func (c *Client) SendBytes(b []byte) error {
	_, err := c.conn.Write(b)
	if err != nil {
		c.conn.Close()
		c.Server.onClientConnectionClosed(c, err)
	}
	return err
}

func (c *Client) Conn() net.Conn {
	return c.conn
}

func (c *Client) Close() error {
	return c.conn.Close()
}

// Called right after server starts listening new client
func (s *server) OnNewClient(callback func(c *Client)) {
	s.onNewClientCallback = callback
}

// Called right after connection closed
func (s *server) OnClientConnectionClosed(callback func(c *Client, err error)) {
	s.onClientConnectionClosed = callback
}

// Called when Client receives new message
func (s *server) OnNewMessage(callback func(c *Client, message []byte, size int)) {
	s.onNewMessage = callback
}

// Listen starts network server
func (s *server) Listen() {
	var listener net.Listener
	var err error
	if s.config == nil {
		listener, err = net.Listen("tcp", s.address)
	} else {
		listener, err = tls.Listen("tcp", s.address, s.config)
	}

	if err != nil {
		fmt.Printf("Error starting TCP server.\r\n", err)
	}

	defer listener.Close()

	for {

		conn, err := listener.Accept()
		if err != nil {
			fmt.Printf("tcpserver listner Accept error:%s", err.Error())
		}

		client := &Client{
			conn: conn,
			Server: s,
		}

		go client.listen()
	}
}

// Creates new tcp server instance
func NewTcpServer(address string) *server {
	fmt.Printf("Creating server with address %s", address)
	server := &server{
		address: address,
	}

	server.OnNewClient(func(c *Client) {
		c.c = NewChannel(c)
	})

	server.OnNewMessage(func(c *Client, message []byte, size int) {
		c.c.onData(c, message, size)
	})

	server.OnClientConnectionClosed(func(c *Client, err error) {
		c.c.Close()
		fmt.Printf("OnClientConnectionClosed   err = %s", err.Error())
	})

	return server
}

func NewTcpServerWithTLS(address, certFile, keyFile string) *server {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {

		fmt.Printf("Error loading certificate files. Unable to create TCP server with TLS functionality.\r\n", err)

	}

	config := &tls.Config{

		Certificates: []tls.Certificate{cert},
	}

	server := NewTcpServer(address)
	server.config = config
	return server

}

channel模块

ChannelListener接口定义:

  • ChannelListener:定义了一个接口,包含两个方法OnChannelEvent和OnChannelStringEvent,用于监听通道事件。

Channel结构体:

  • 包含字段如MediasoupClient(指向Client的指针)、PendingSent(一个同步映射,用于存储待发送的数据)、LastBinaryNotification、ID、Pid、udpAddress、udpPort、queue和messageQueue(两个循环队列)、Num和Listeners(一个映射,存储监听器)。
  • 包含一个互斥锁mutex,用于并发控制。

Channel的接口:

  • NewChannel:构造函数,创建并返回一个新的Channel实例。
  • AddListener和RemoveListener:用于添加和移除监听器。
  • processMessage:处理接收到的消息。
  • onData:处理接收到的数据。
  • handle:一个循环,从队列中取出项目并处理。
  • handleMessage:处理消息队列中的消息。
  • process:根据消息类型进行不同的处理。
  • Close:关闭通道,清理资源。
  • Request:发送请求并返回一个通道用于接收响应。
  • SetUdp:设置UDP地址和端口。

并发处理:

  • 使用sync.Map和sync.RWMutex来处理并发,确保数据的一致性和线程安全。

循环队列:

  • 使用MeetGo.CycleQueue作为循环队列,用于存储消息和数据。

参考demo

import (
	"encoding/json"
	"fmt"
	"strconv"
	"sync"
	"time"
    "strings"
)

//###########SendRequest begin############/
var REQUEST_TIMEOUT = 30000

type SendRequest struct {
	ID       string
	Method   string
	Internal map[string]interface{}
	Data     map[string]interface{}
}

//async chan SendReponse
type SendReponse struct {
	ID       int
	TargetId int
	Event    string
	Accepted bool
	Rejected bool
	Internal map[string]interface{}
	Data     map[string]interface{}
	Reason   string
	Binary   bool
}

type AsyncSingal struct {
	Async chan SendReponse
}
###########SendRequest End############//

///###########CycleQueue begin############//
type CycleQueue struct {
	data  []interface{} //存储空间
	front int           //前指针,前指针负责弹出数据移动
	rear  int           //尾指针,后指针负责添加数据移动
	cap   int           //设置切片最大容量
}

func NewCycleQueue(cap int) *CycleQueue {
	return &CycleQueue{
		data:  make([]interface{}, cap),
		cap:   cap,
		front: 0,
		rear:  0,
	}
}

//入队操作
//判断队列是否队满,队满则不允许添加数据
func (q *CycleQueue) Push(data interface{}) bool {
	//check queue is full
	if (q.rear+1)%q.cap == q.front { //队列已满时,不执行入队操作
		return false
	}
	q.data[q.rear] = data         //将元素放入队列尾部
	q.rear = (q.rear + 1) % q.cap //尾部元素指向下一个空间位置,取模运算保证了索引不越界(余数一定小于除数)
	return true
}

//出队操作
//需要考虑: 队队为空没有数据返回了
func (q *CycleQueue) Pop() interface{} {
	if q.rear == q.front {
		return nil
	}
	data := q.data[q.front]
	q.data[q.front] = nil
	q.front = (q.front + 1) % q.cap
	return data
}

//因为是循环队列, 后指针减去前指针 加上最大值, 然后与最大值 取余
func (q *CycleQueue) QueueLength() int {
	return (q.rear - q.front + q.cap) % q.cap
}

func (q *CycleQueue) FindDataByRequestId(requestId string) string {
	for i := 0; i < q.QueueLength(); i++ {
		if strings.Count(q.data[i].(string), requestId) == 1 {
			emitData := q.data[i].(string)
			q.data = append(q.data[:i], q.data[i+1:]...)
			return emitData
		}
	}
	return ""
}
///###########CycleQueue############


import (
	"encoding/json"
	"fmt"
	"strconv"
	"sync"
	"time"

	MeetGo "vrv.meeting.server/MeetGo"
)

const NS_MAX_SIZE int = 655350

var messageBuffer = make([]byte, NS_MAX_SIZE)
var messageIndex = 0

type ChannelListener interface {
	OnChannelEvent(string, map[string]interface{})
	OnChannelStringEvent(string, string)
}

type Channel struct {
	MediasoupClient        *Client
	PendingSent            sync.Map
	LastBinaryNotification interface{}
	ID                     int
	Pid                    int
	udpAddress             string
	udpPort                int
	queue                  CycleQueue
	messageQueue          CycleQueue
	Num                    int
	Listeners              map[string]ChannelListener
	mutex                  sync.RWMutex
}

func NewChannel(tcpClient *Client) *Channel {
	channel := new(Channel)
	channel.MediasoupClient = tcpClient
	channel.queue = MeetGo.NewCycleQueue(1000)
	channel.messageQueue = MeetGo.NewCycleQueue(10000)
	channel.Num = 0
	channel.Listeners = make(map[string]ChannelListener, 100)
	go channel.handle()
	go channel.handleMessage()
	return channel
}

func (channel *Channel) AddListener(id string, listener ChannelListener) {
	channel.mutex.Lock()
	channel.Listeners[id] = listener
	channel.mutex.Unlock()
}

func (channel *Channel) RemoveListener(id string) {
	channel.mutex.Lock()
	delete(channel.Listeners, id)
	channel.mutex.Unlock()
}

func (channel *Channel) processMessage(message string) {
	jsonMessage := make(map[string]interface{})
	err := json.Unmarshal([]byte(message), &jsonMessage)
	if err != nil {
		MeetGo.Log.Error("Channel processMessage error:%s", err.Error())
		return
	}
	if jsonMessage["registId"] != nil {
		MeetGo.Log.Debug("client registId succeeded [id:%s]", jsonMessage["registId"].(string))
		channel.ID, _ = strconv.Atoi(jsonMessage["registId"].(string))
		channel.Pid = int(jsonMessage["pid"].(float64))
		Global_Worker.OnMediasoupWorkerOnline(channel.ID, channel, jsonMessage["registId"].(string))
	} else if jsonMessage["id"] != nil {
		idd := int(jsonMessage["id"].(float64))
		value, ret := channel.PendingSent.Load(idd)
		if !ret {
			fmt.Printf("received Response does not match any sent Request")
			return
		}
		channel.PendingSent.Delete(idd)
		asyncReponse := value.(*MeetGo.AsyncSingal)
		if jsonMessage["accepted"] != nil && jsonMessage["accepted"].(bool) {
			MeetGo.Log.Debug("request succeeded [id:%d]", int(jsonMessage["id"].(float64)))
			sendReponse := MeetGo.SendReponse{
				ID:       idd,
				Accepted: jsonMessage["accepted"].(bool),
				Data:     jsonMessage["data"].(interface{}).(map[string]interface{}),
			}
			asyncReponse.Async <- sendReponse
		} else {
			MeetGo.Log.Debug("request failed [id:%d, reason: %s]", int(jsonMessage["id"].(float64)), jsonMessage["reason"].(string))
			sendReponse := MeetGo.SendReponse{
				ID:     int(jsonMessage["id"].(float64)),
				Reason: jsonMessage["reason"].(string),
			}
			asyncReponse.Async <- sendReponse
		}

	} else if jsonMessage["targetId"] != nil && jsonMessage["event"] != nil {
		if jsonMessage["binary"] != nil {
			channel.LastBinaryNotification = jsonMessage
			return
		} else if jsonMessage["data"] != nil {
			listenerKey := fmt.Sprintf("%d", int(jsonMessage["targetId"].(float64)))
			channel.mutex.RLock()
			listener := channel.Listeners[listenerKey]
			channel.mutex.RUnlock()
			if listener != nil {
				listener.OnChannelEvent(jsonMessage["event"].(string), jsonMessage["data"].(map[string]interface{}))
			}

		} else {
			data := make(map[string]interface{})
			listenerKey := fmt.Sprintf("%d", int(jsonMessage["targetId"].(float64)))
			channel.mutex.RLock()
			listener := channel.Listeners[listenerKey]
			channel.mutex.RUnlock()
			if listener != nil {
				listener.OnChannelEvent(jsonMessage["event"].(string), data)
			}
		}

	} else {
		fmt.Printf("received message is not a Response nor a Notification")
		return
	}
}

func (channel *Channel) onData(client *Client, message []byte, size int) {
	for {
		ret := channel.messageQueue.Push(message)
		if ret {
			break
		} else {
			time.Sleep(40 * time.Millisecond)
		}
	}
}

func (channel *Channel) handle() {
	for {
		item := channel.queue.Pop()
		if item == nil {
			time.Sleep(40 * time.Millisecond)
			continue
		}
		channel.process(item)
		time.Sleep(1 * time.Millisecond)
	}
}

func (channel *Channel) handleMessage() {
	ns := NetString{bufLen: 0, length: 0, state: 0}
	for {
		item := channel.messageQueue.Pop()
		if item == nil {
			time.Sleep(40 * time.Millisecond)
			continue
		}
		message := item.([]byte)
		var nsPayloads [][]byte
		err := ns.NsUnmarshal(message, &nsPayloads)
		if err != nil {
			fmt.Printf("Channel handleMessage nsPayload error %s", err.Error())
			return
		}
		for _, nsPayload := range nsPayloads {
			channel.queue.Push(nsPayload)
		}
		time.Sleep(1 * time.Millisecond)
	}
}

func (channel *Channel) process(data interface{}) {
	nsPayload := data.([]byte)
	if channel.LastBinaryNotification == nil {
		switch nsPayload[0] {
		// 123 = '{' (a Channel JSON messsage).
		case 123:
			channel.processMessage(string(nsPayload))
			break
			// 68 = 'D' (a debug log).
		case 68:
			fmt.Printf(string(nsPayload))
			break

		// 87 = 'W' (a warning log).
		case 87:
			fmt.Printf(string(nsPayload))
			break

		// 69 = 'E' (an error log).
		case 69:
			fmt.Printf(string(nsPayload))
			break

		default:
			fmt.Printf(
				"unexpected data: %s", string(nsPayload))

		}
	} else {
		msg := channel.LastBinaryNotification
		channel.LastBinaryNotification = nil
		jsonMsg := make(map[string]interface{})
		err := json.Unmarshal([]byte(msg.(string)), &jsonMsg)
		if err != nil {
			panic(err)
		}
		listenerKey := fmt.Sprintf("%d", int(jsonMsg["targetId"].(float64)))
		channel.mutex.RLock()
		listener := channel.Listeners[listenerKey]
		channel.mutex.RUnlock()
		if listener != nil {
			listener.OnChannelStringEvent(jsonMsg["event"].(string), jsonMsg["data"].(string))
		}

	}
}

func (channel *Channel) Close() {
	channel.PendingSent.Range(func(k, v interface{}) bool {
		channel.PendingSent.Delete(k)
		return true
	})
	registId := strconv.Itoa(channel.ID)
	Global_Worker.OnMediasoupWorkerOffline(registId)
	time.Sleep(time.Millisecond * 250) //?
	fmt.Printf("channel.MediasoupClient.Close() ")
	channel.MediasoupClient.Close()
}

func (c *Channel) Request(method string, internal,
	data map[string]interface{}) (chan MeetGo.SendReponse, int64) {

	id := RandomNumberGenerator(10000000, 99999999)
	fmt.Printf("MediasoupLib Channel [method:%s, id:%d]", method, id)

	request := MeetGo.RequestJson{
		ID:       id,
		Method:   method,
		Internal: internal,
		Data:     data,
	}
	requestJson := request.Encode()
	requestSend := nsEncode(requestJson)
	fmt.Printf("___requestSend : %s", requestSend)
	sendReponse := new(MeetGo.AsyncSingal)
	sendReponse.Async = make(chan MeetGo.SendReponse)
	if sendReponse != nil {
		c.PendingSent.Store(int(id), sendReponse)
	}

	defer c.MediasoupClient.Send(requestSend)
	return sendReponse.Async, id
}

func (channel *Channel) SetUdp(udpAddress string, udpPort int) {
	channel.udpAddress = udpAddress
	channel.udpPort = udpPort
}


  • 58
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

littleboy_webrtc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值