通过同一会话发送客户端更新和API请求

时间:2016-09-22 11:12:30

标签: c# multithreading api sockets telegram

我从TLSharp开始编写自定义C#电报客户端并对其进行修改以支持第54层。

我希望处理从电报服务器接收更新并使用API​​而无需打开单独的会话来执行此操作。

问题基本上是对连接到Telegram Server的套接字的多线程访问。

这是方案:

TelegramClient< ------- socket_1 [(session_1)] --------> TelegramServer

问题是,为了从电报服务器不断接收更新,我使用了一段时间(真实)循环 基本上被模式化为:

while(true){
    data_cipher = await socket_1.receive() // listen for new stuff from the socket_1
    data_plain  = decrypt(data_cipher)  // decrypt 
    processUpdate(data_plain)  // process the update
}

现在,如果我想要,例如,查询电报服务器以查找我注册的所有聊天列表,我有 访问socket_1以发送此请求,并等待答案,但socket_1正在监听,我显然无法访问它。

一种解决方案可能是使用将在收到更新后处理的请求向量, 这个想法是这样的:

List<Request> pending_requests = new List<Request>() // list of requests added by another thread 

    while(true){

        data_cipher = await socket_1.receive() // listen for new stuff from the socket_1
        data_plain  = decrypt(data_cipher)  // decrypt 
            processUpdate(data_plain)  // process the update

        if(pending_requests.Count != 0){
            foreach(Request r in pending_requests ){
                     processRequest(r)
                }
            }
    }

此解决方案非常糟糕,因为我们仅在更新后处理请求,因此没有更新=没有处理请求...

另一种可能性是按照这样的方案使用某种锁机制:

//Thread_updater
//--------------------------------------------
while(true){

        lock(socket_1){
    data_cipher = await socket_1.receive() // listen for new stuff from the socket_1
    }

    data_plain  = decrypt(data_cipher)  // decrypt 
        handleUpdate(data_plain)  // process the update

}
--------------------------------------------


//Thread_requests
//--------------------------------------------
Request r = new Request(<stuff>);

lock(socket_1){
   await sendRequest(r,socket_1)
}

--------------------------------------------

关于这一点的一个大问题是,一旦Thread_updater获取锁定,它将永远不会释放它,直到收到更新...这基本上是和以前一样的问题。 我也尝试过使用CancellationTasks或Socket Timeout,但我觉得我走错了路。

是否有一个优雅的解决方案/模式,以便以一个整洁的方式处理这个? 如上所述,我不想打开2个会话,因为它在逻辑上是错误的(就像有两个客户端来处理接收更新和发送消息)。

1 个答案:

答案 0 :(得分:0)

有更简单的方法可以做到这一点。

这是我在VB.net中所做的,但也应该在C#中为你工作。

  • 设置套接字并连接到Telegram服务器,监听数据
  • 收到缓冲区数据
  • 处理收到的数据 - 解码从电报收到的TL类型,并存储/响应,即发送消息以响应您收到的内容
  • 在收听时,您也可以通过同一个套接字发送消息

示例代码

地区&#34;变量&#34;

    Const BUFFER_SIZE = 1024 * 64

    Private soc As Socket
    Private ep As EndPoint
    Private connected As Boolean
    Private efSent As Boolean
    Private are As New AutoResetEvent(False)
#End Region

#Region "Network"
    Public Sub Connect(Optional ip As String = "149.154.167.40", Optional port As Integer = 443)
        soc = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) With {.SendBufferSize = BUFFER_SIZE, .SendTimeout = 3000}

        Try
            ep = GetIPEndPointFromHostName(ip, port)

            Dim arg = New SocketAsyncEventArgs With {.RemoteEndPoint = ep, .UserToken = "Connect_ARGS"}
            AddHandler arg.Completed, AddressOf IO_Handler

            While Not connected
                Try
                    If Not soc.ConnectAsync(arg) Then
                        IO_Handler(soc, arg)
                    End If

                    are.WaitOne(4000)
                Catch ex As Exception
                    Thread.Sleep(1000)
                End Try
            End While

        Catch ex As Exception
            Log("Connect: " & ex.ToString, ConsoleColor.Red, True)
        End Try

        ReadData()
    End Sub

    Public Sub Disconnect()
        connected = False
        loggedin = False

        soc.Disconnect(False)
        soc = Nothing

        Log("Disconnect", ConsoleColor.DarkYellow, True, True, True)
    End Sub

    Private Sub Send(m As PlainMessage)
        SendData(m.data, True)
    End Sub

    Private Sub Send(m As EncryptedMessage)
        SendData(m.data, True)
    End Sub

    Private Sub SendData(b() As Byte, Optional read As Boolean = False)
        b = TCPPack(b)

        Dim arg = New SocketAsyncEventArgs With {.RemoteEndPoint = ep, .UserToken = "Send_ARGS"}
        AddHandler arg.Completed, AddressOf IO_Handler
        arg.SetBuffer(b, 0, b.Length)

        Try
            If Not soc.SendAsync(arg) Then
                IO_Handler(soc, arg)
            End If
        Catch ex As Exception
            Log("SendData: " & ex.ToString, ConsoleColor.Red)
        End Try
    End Sub

    Private Sub IO_Handler(sender As Object, e As SocketAsyncEventArgs)
        Select Case e.SocketError
            Case SocketError.Success
                Select Case e.LastOperation
                    Case SocketAsyncOperation.Connect 'A socket Connect operation.
                        connected = True
                        Log("Connected to " & e.ConnectSocket.RemoteEndPoint.ToString, ConsoleColor.Green)
                        are.Set()
                    Case SocketAsyncOperation.Disconnect
                        connected = False
                        RaiseEvent Disconneted()
                    Case SocketAsyncOperation.Receive 'A socket Receive operation.
                        If e.BytesTransferred = 0 Then 'no pending data
                            Log("The remote end has closed the connection.")
                            If connected Then
                                ReadData()
                            End If

                            connected = False
                            loggedin = False

                            Exit Sub
                        End If
                        HandleData(e)
                End Select
            Case SocketError.ConnectionAborted
                RaiseEvent Disconneted()
        End Select
    End Sub

    Private Function GetIPEndPointFromHostName(hostName As String, port As Integer) As IPEndPoint
        Dim addresses = System.Net.Dns.GetHostAddresses(hostName)
        If addresses.Length = 0 Then
            Log("Unable to retrieve address from specified host name:  " & hostName, ConsoleColor.Red)
            Return Nothing
        End If
        Return New IPEndPoint(addresses(0), port)
    End Function

    Private Function TCPPack(b As Byte()) As Byte()
        Dim a = New List(Of Byte)
        Dim len = CByte(b.Length / 4)

        If efSent = False Then 'TCP abridged version
            efSent = True
            a.Add(&HEF)
        End If

        If len >= &H7F Then
            a.Add(&H7F)
            a.AddRange(BitConverter.GetBytes(len)) '
        Else
            a.Add(len)
        End If

        a.AddRange(b) 'data, no sequence number, no CRC32

        Return a.ToArray
    End Function

    Private Sub ReadData()
        Dim arg = New SocketAsyncEventArgs With {.RemoteEndPoint = ep, .UserToken = "Read_ARGS"}
        AddHandler arg.Completed, AddressOf IO_Handler

        Dim b(BUFFER_SIZE - 1) As Byte
        arg.SetBuffer(b, 0, BUFFER_SIZE)

        Try
            If Not soc.ReceiveAsync(arg) Then
                IO_Handler(soc, arg)
            End If
        Catch ex As Exception
            Log("ReadMessages: " & ex.ToString, ConsoleColor.Red)
        End Try
    End Sub

    Private Sub HandleData(e As SocketAsyncEventArgs)
        Log("<< " & B2H(e.Buffer, 0, e.BytesTransferred), ConsoleColor.DarkGray, True, logTime:=False)
        Try
            Dim len As Integer = e.Buffer(0)
            Dim start = 1

            If len = &H7F Then
                len = e.Buffer(1)
                len += e.Buffer(2) << 8
                len += e.Buffer(3) << 16
                start = 4
            End If

            len = 4 * len

            Dim d(len - 1) As Byte
            Array.Copy(e.Buffer, start, d, 0, len)

            ProcessResponse(d)
        Catch ex As Exception

        End Try

        ReadData()
    End Sub

    Private Sub ProcessResponse(data As Byte())
        'process the data received - identify the TL types returned from Telegram, then store / handle each as required
    End Sub
#End Region