socket
多年前(以基于事件的异步模式(EAP)方式)进行了一些(或多或少)“低级”异步编程之后,最近又将其“升级”到了TcpListener
(异步编程模型(APM)),然后尝试转到async/await
(基于任务的异步模式(TAP))时,我不得不忍受所有这些“低级管道”的困扰。所以我在想;为什么不试一试RX
(Reactive Extensions),因为它可能更贴近我的问题领域。
我编写的代码很多,许多客户端通过Tcp连接到我的应用程序,然后启动双向(异步)通信。客户端或服务器可以随时决定是否需要发送消息,所以这不是您的经典request/response
设置,而是更多的实时,双向,“线路”开放给双方以发送他们想要的任何内容,只要他们想要。(如果有人用一个得体的名字来形容这一点,我将很高兴听到它!)。
每个应用程序的“协议”有所不同(并且与我的问题并不相关)。我确实有,但是有一个最初的问题:
- 假设只有一个“服务器”正在运行,但是它必须跟踪许多(通常是数千个)连接(例如客户端),每个连接都有(由于缺乏更好的描述)自己的“状态机”来跟踪其内部状态等,您更喜欢哪种方法?EAP / TAP / APM?RX甚至可以考虑吗?如果没有,为什么?
因此,我需要使用Async,因为a)它不是请求/响应协议,所以我不能在“等待消息”阻止调用或“发送消息”阻止调用中使用线程/客户端(但是,如果发送是仅阻止该客户端,我可以忍受它)b)我需要处理许多并发连接。我看不到使用阻塞调用(可靠地)执行此操作的方法。
我的大多数应用程序都与VoiP相关;无论是来自SIP 科学家的 SIP消息,还是来自FreeSwitch / OpenSIPS等应用程序的PBX(相关)消息,但是您可以以最简单的形式尝试想象一个“聊天”服务器尝试处理许多“聊天”客户端。大多数协议都是基于文本的(ASCII)。
因此,在实现了上述技术的许多不同排列之后,我想通过创建一个对象来简化我的工作,该对象可以简单地实例化,告诉它在哪IPEndpoint
听,并在发生任何感兴趣的事情时告诉我(通常我通常会这样做)使用事件,因此通常将某些EAP与其他两种技术混合使用)。该类不应费力尝试“理解”协议。它应该只处理传入/传出的字符串。因此,我着眼于RX希望(最终)可以简化工作,我从头开始创建了一个新的“小提琴”:
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Linq;
using System.Text;
class Program
{
static void Main(string[] args)
{
var f = new FiddleServer(new IPEndPoint(IPAddress.Any, 8084));
f.Start();
Console.ReadKey();
f.Stop();
Console.ReadKey();
}
}
public class FiddleServer
{
private TcpListener _listener;
private ConcurrentDictionary<ulong, FiddleClient> _clients;
private ulong _currentid = 0;
public IPEndPoint LocalEP { get; private set; }
public FiddleServer(IPEndPoint localEP)
{
this.LocalEP = localEP;
_clients = new ConcurrentDictionary<ulong, FiddleClient>();
}
public void Start()
{
_listener = new TcpListener(this.LocalEP);
_listener.Start();
Observable.While(() => true, Observable.FromAsync(_listener.AcceptTcpClientAsync)).Subscribe(
//OnNext
tcpclient =>
{
//Create new FSClient with unique ID
var fsclient = new FiddleClient(_currentid++, tcpclient);
//Keep track of clients
_clients.TryAdd(fsclient.ClientId, fsclient);
//Initialize connection
fsclient.Send("connect\n\n");
Console.WriteLine("Client {0} accepted", fsclient.ClientId);
},
//OnError
ex =>
{
},
//OnComplete
() =>
{
Console.WriteLine("Client connection initialized");
//Accept new connections
_listener.AcceptTcpClientAsync();
}
);
Console.WriteLine("Started");
}
public void Stop()
{
_listener.Stop();
Console.WriteLine("Stopped");
}
public void Send(ulong clientid, string rawmessage)
{
FiddleClient fsclient;
if (_clients.TryGetValue(clientid, out fsclient))
{
fsclient.Send(rawmessage);
}
}
}
public class FiddleClient
{
private TcpClient _tcpclient;
public ulong ClientId { get; private set; }
public FiddleClient(ulong id, TcpClient tcpclient)
{
this.ClientId = id;
_tcpclient = tcpclient;
}
public void Send(string rawmessage)
{
Console.WriteLine("Sending {0}", rawmessage);
var data = Encoding.ASCII.GetBytes(rawmessage);
_tcpclient.GetStream().WriteAsync(data, 0, data.Length); //Write vs WriteAsync?
}
}
我知道,在这个“小提琴”中,有一些具体的实现细节;在这种情况下,我正在使用FreeSwitch ESL,因此"connect\n\n"
在重构为更通用的方法时,应该删除小提琴。
我还知道,我需要将匿名方法重构为Server类上的私有实例方法。我只是不确定OnSomething
为其方法名称使用哪种约定(例如“ ”)?
这是我的基础/起点/基础(需要一些“调整”)。我对此有一些疑问:
- 参见上面的问题“ 1”
- 我在正确的轨道上吗?还是我的“设计”决定不公正?
- 并发方面:这将应付成千上万的客户端(解析/处理实际消息)
- 关于异常:我不确定如何将客户端中引发的异常“上”到服务器(“ RX方式”);有什么好办法吗?
- 现在
ClientId
,假设我以一种或另一种方式公开客户端,并直接在其上调用方法,则可以从服务器类中获取任何连接的客户端(使用)。我还可以通过Server类来调用方法(例如,该Send(clientId, rawmessage)
方法(而后一种方法将是一种“便捷”方法,用于快速将消息发送到另一侧)。 - 我不太确定从这里到哪里(以及如何去):
- a)我需要处理传入的消息;我该如何设置?我可以得到课程流,但是在哪里可以检索接收到的字节?我想我需要某种“ ObservableStream”-我可以订阅吗?我会把这在
FiddleClient
还是FiddleServer
? - b)假设我想避免使用事件,直到更具体地实现这些
FiddleClient
/FiddleServer
类以定制其特定于应用程序的协议处理等为止。使用更具体的FooClient
/FooServer
类:从获取底层“小提琴”类中的数据到他们的方法如何?更具体的对应?
- a)我需要处理传入的消息;我该如何设置?我可以得到课程流,但是在哪里可以检索接收到的字节?我想我需要某种“ ObservableStream”-我可以订阅吗?我会把这在
我已经阅读/略读/用于参考的文章/链接: