电报客户端更新和在同一会话API请求
问题描述:
我编码一个自定义的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个会话,因为它在逻辑上是错误的(这就像有两个客户端来处理接收更新和发送消息)。
答
有更简单的方法来做到这一点。
这就是我在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