2016-09-22 33 views
2

我編碼一個自定義的C#電報客戶從TLSharp開始,修改了它以支持層54電報客戶端更新和在同一會話API請求

我要處理來自服務器的電報都接收更新和使用API而無需打開單獨的會話來執行此操作。

問題基本上是多線程訪問連接到電報服務器的套接字。

這方案:

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接受鎖定,它將永遠不會釋放它,直到接收到更新爲止......這與以前基本相同。 我也嘗試玩取消任務或套接字超時,但我覺得我走錯了路。

有沒有一個優雅的解決方案/模式,以便以一種整潔的方式處理這個問題? 如上所述,我不想打開2個會話,因爲它在邏輯上是錯誤的(這就像有兩個客戶端來處理接收更新和發送消息)。

回答

0

有更簡單的方法來做到這一點。

這就是我在VB.net中所做的,但也應該在C#中爲你工作。

  • 設置您的插座,並連接到電報的服務器,因爲它是收到
  • 過程中接收到的數據監聽數據
  • 緩存數據 - 從收到的電報解碼TL類型,要麼存儲/反應我。E在響應消息發送到所收到的內容
  • 一邊聽,你也可以通過相同的插座發送消息

示例代碼

地區的「變量」

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