我从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个会话,因为它在逻辑上是错误的(就像有两个客户端来处理接收更新和发送消息)。
答案 0 :(得分:0)
有更简单的方法可以做到这一点。
这是我在VB.net中所做的,但也应该在C#中为你工作。
示例代码
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