C# 套接字 BeginAccept 与 Select?
C# Socket BeginAccept vs Select?
我正在学习Socket编程来做一个聊天室。
我知道我可以使用异步套接字,例如
listenFd.BeginAccept(AcceptCallback, listenFd);
我也可以使用
Socket.Select(checkRead,null,null,1000);
我知道 async
和 select
的基本含义。
但是,我不知道在什么情况下一个应该比另一个更好。
编辑:
其实我是在看教程。它说使用 select 比异步更好,因为逻辑更清晰。
这里有两个例子:
一个使用 select:
namespace Server
{
class App
{
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static string ipAddr="127.0.0.1";
static int port=8888;
static void Main(string[] args)
{
Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse(ipAddr);
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, port);
listenFd.Bind(iPEndPoint);
listenFd.Listen(0);
Console.WriteLine("Server start!");
List<Socket>checkRead=new List<Socket>();
while(true)
{
checkRead.Clear();
checkRead.Add(listenFd);
foreach(var clientState in clients.Values)
{
checkRead.Add(clientState.socket);
}
Socket.Select(checkRead,null,null,1000);
foreach(var socket in checkRead)
{
if(socket==listenFd)
{
ReadListenfd(socket);
}
else
{
ReadClientfd(socket);
}
}
}
}
public static void ReadListenfd(Socket listenfd)
{
Console.WriteLine("Accept");
Socket clientfd=listenfd.Accept();
ClientState state=new ClientState();
state.socket=clientfd;
clients.Add(clientfd,state);
}
public static bool ReadClientfd(Socket clientfd)
{
ClientState state=clients[clientfd];
int count=0;
try
{
count=clientfd.Receive(state.readBuff);
}
catch(SocketException ex)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine($"Receive Socket Exception {ex.ToString()}");
return false;
}
if(count==0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket close");
return false;
}
string recvStr=System.Text.Encoding.Default.GetString(state.readBuff,0,count);
Console.WriteLine($"Rec {recvStr}");
string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+recvStr;
byte[]sendBytes=System.Text.Encoding.Default.GetBytes(strFromClientWithTime);
foreach(ClientState cs in clients.Values)
{
cs.socket.Send(sendBytes);
}
return true;
}
}
}
一个使用异步:
namespace Server
{
class App
{
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static void Main(string[] args)
{
Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 8888);
listenFd.Bind(iPEndPoint);
listenFd.Listen(0);
Console.WriteLine("Server start!");
listenFd.BeginAccept(AcceptCallback, listenFd);
while(true)
{
Thread.Sleep(1000);
}
}
static void AcceptCallback(IAsyncResult result)
{
var listenfd = result.AsyncState as Socket;
var connfd = listenfd.EndAccept(result);
var clientState = new ClientState { socket = connfd };
clients.Add(connfd, clientState);
connfd.BeginReceive(clientState.readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
Console.WriteLine($" Client connected!");
listenfd.BeginAccept(AcceptCallback, listenfd);
}
static void EndReceiveCallback(IAsyncResult result)
{
var connfd = result.AsyncState as Socket;
var count = connfd.EndReceive(result);
if (count <= 0)
{
Console.WriteLine("Client disconnected!");
connfd.Close();
return;
}
connfd.BeginReceive(clients[connfd].readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
string strFromClient=System.Text.Encoding.Default.GetString(clients[connfd].readBuff,0,count);
Console.WriteLine($"string from client:{strFromClient}");
string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+strFromClient;
byte[] sendBuff= System.Text.Encoding.Default.GetBytes(strFromClientWithTime,0,strFromClientWithTime.Length);
foreach(var conn in clients.Keys)
{
conn.BeginSend(sendBuff, 0, sendBuff.Length, 0, EndSendCallback, conn);
}
}
static void EndSendCallback(IAsyncResult result)
{
var connfd = result.AsyncState as Socket;
connfd.EndSend(result);
}
}
}
在这两个例子中,Class ClientState
是
class ClientState
{
public Socket socket;
public byte[] readBuff=new byte[1024];
}
这两个例子应该都能正常工作。但我认为异步应该更好,正如 所说。
不过第二版的教程作者更喜欢用select只是说逻辑更清晰
我已经做了几个小时的研究,但仍然感到困惑。这只是一种偏好还是我在这里缺少的东西。
使用 Select
几乎总是意味着您正在轮询 - 占用一个线程来重复进行此调用,处理结果(通过某种形式的 Accept
),可能会休眠一段时间一会儿然后再做一遍。
BeginAccept
让您告诉 "the system" 在发生有趣的事情时通知您。与此同时,您自己使用的处理资源为零,如果一切都经过适当设计,no thread at all 会进行任何类型的轮询或等待。
我只有 可能 使用 Select
如果我有 "good" 次和 "bad" 次的奇怪情况执行接受。通过这种方式,您可以确保在 "bad" 时间内没有未完成的接受调用。但这将是一个非常罕见的利基领域,希望您已经确定了这种情况。
Select method receives a list of sockets that are binding and listening for incomming requests. When the call returns that list will only have sockets that have incomming requests pending to be accepted as showned here。
我可以证明的主要区别之一是在使用 AcceptAsync instead of the Accept 时在 OnCompleted 事件处理程序上放置的独占线程,这是将用于处理的当前线程使用该接受结果创建的套接字。
您可以将聊天室中的 1.synchronous 和 2.asynchronous 行为与此进行比较:
假设您有一个拍卖会,一个有 1000 人的房间,潜在买家。
同步:拍卖师使用固定的 pattern/order [您的轮询循环] 遍历所有席位,并询问 [轮询] 每个席位 he/she 是否想要放置更高的bid [检查传入连接/传入数据]。如果是,拍卖师将其注册[接受连接或处理输入的聊天语句],然后走到下一个并询问,依此类推。完成所有 1000 次后,他重复。请注意,不允许说话,即。在被询问之前没有人可以注册出价,尽管拍卖师在收到新出价后会立即宣布新出价[从聊天服务器向所有客户发送任何新的出价]。
异步:允许通话。任何人都可以随时喊出 his/her 出价 [callback to your callback fn],甚至可以同时 [OS 创建多个同步并行线程] - 拍卖师非常机敏、快速且技术娴熟,所以他听到每个出价 [OS 管理传入] 并以他能做到的最好的方式宣布它 [所有线程将数据放在您的公共共享数据存储库中,按照线程碰巧提出的顺序,并且你尽快发送通用数据],他甚至能够听到 200 个同时出价 [多线程]。无需步行,投标无固定顺序
在情况 1 中,您可能会从随机用户 John 那里得到一个抱怨:"Why is Lisa always commenting before me? Unfair!" [您的固定轮询顺序,从 1 到最大,Lisa 坐在 John 之前]。并且来自任何名为 "X" 的用户(假设您从公共服务器提供所有聊天条目,即 X 的条目进行服务器往返):"Why does my chat entry sometimes appear immediately [the auctioneer asked the X-1 person, he'll ask X in a nanosec] but sometimes it takes 2 secs? [the auctioneer asked the X+1 person, takes time before he's here again, the OS stack keeps X on hold]"
在重负载条件下,同步替代方案将是缓慢且旋转不良的变速箱。有点……:-)
小附录重新。异步编码(不太科学)
异步更难编码、测试和调试。它需要一种完全不同的编码风格,尽管异步部分可能存在于(侧面)一个同步应用程序中(仍然由用户操作驱动的应用程序,如 Windows 中)。
异步部分可以被认为是代码中隔离良好的电机;它适用于您在其周围维护的全局和持久性管理数据,但在盒子内部它可以自由 "go wild",具体取决于来自外部世界的内容 = 网络连接和数据,它们会轰炸最里面的直接框,独立于您自己的操作。这是 OS 引发的事件,源自那里的客户。
有两个重要的特征需要理解:
该框仅包含一组代码(几个函数),但可以同时触发多次。每次它被触发时,它的一个新的、孤立的实例将 运行。唯一将它与其他类似线程隔离开来的是线程 ID,每个实例都是唯一的。这些实例彼此独立工作,速度与 CPU/OS 一样快。然而,每个人都会在某个时候完成工作,并且可能希望将结果(每个人依次以任何预先未知的顺序)传递到您的全局数据中(例如,客户列表,总聊天记录)所有客户等)。这意味着必须要有锁机制,因为同一时刻只有一个实例(线程)可能访问全局数据,否则就乱七八糟。你的编码语言有这方面的工具(保持,锁定)。
由于这一组代码是事件驱动的,所以无法提前知道会有多少工作量。因此它必须有能力 a) 开始做工作 b) 继续做工作和 c) 当 OS 说 "that was it" 时以干净的方式完成。但是万一失败,如果 OS 的某种原因从来没有说 "that was it",那么一定有一个超时来完成事情。
不用说,这一切都很难做到正确。如果有错误导致一个线程停止,其他线程会继续吗?你如何访问失败线程的内部?您如何向后走以查看 why/how 失败的线程首先产生?等等
编写异步代码时,应该非常循序渐进;从最简单的实现开始;并有一个适当的外部环境来提供丰富的测试数据,从逐步速率(例如按键)到非常高的自动化速率,甚至同时(例如 LAN 中的其他计算机,提供数据在开始 feeding/stop 喂食的基础上)。
我正在学习Socket编程来做一个聊天室。
我知道我可以使用异步套接字,例如
listenFd.BeginAccept(AcceptCallback, listenFd);
我也可以使用
Socket.Select(checkRead,null,null,1000);
我知道 async
和 select
的基本含义。
但是,我不知道在什么情况下一个应该比另一个更好。
编辑:
其实我是在看教程。它说使用 select 比异步更好,因为逻辑更清晰。
这里有两个例子:
一个使用 select:
namespace Server
{
class App
{
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static string ipAddr="127.0.0.1";
static int port=8888;
static void Main(string[] args)
{
Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse(ipAddr);
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, port);
listenFd.Bind(iPEndPoint);
listenFd.Listen(0);
Console.WriteLine("Server start!");
List<Socket>checkRead=new List<Socket>();
while(true)
{
checkRead.Clear();
checkRead.Add(listenFd);
foreach(var clientState in clients.Values)
{
checkRead.Add(clientState.socket);
}
Socket.Select(checkRead,null,null,1000);
foreach(var socket in checkRead)
{
if(socket==listenFd)
{
ReadListenfd(socket);
}
else
{
ReadClientfd(socket);
}
}
}
}
public static void ReadListenfd(Socket listenfd)
{
Console.WriteLine("Accept");
Socket clientfd=listenfd.Accept();
ClientState state=new ClientState();
state.socket=clientfd;
clients.Add(clientfd,state);
}
public static bool ReadClientfd(Socket clientfd)
{
ClientState state=clients[clientfd];
int count=0;
try
{
count=clientfd.Receive(state.readBuff);
}
catch(SocketException ex)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine($"Receive Socket Exception {ex.ToString()}");
return false;
}
if(count==0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket close");
return false;
}
string recvStr=System.Text.Encoding.Default.GetString(state.readBuff,0,count);
Console.WriteLine($"Rec {recvStr}");
string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+recvStr;
byte[]sendBytes=System.Text.Encoding.Default.GetBytes(strFromClientWithTime);
foreach(ClientState cs in clients.Values)
{
cs.socket.Send(sendBytes);
}
return true;
}
}
}
一个使用异步:
namespace Server
{
class App
{
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static void Main(string[] args)
{
Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 8888);
listenFd.Bind(iPEndPoint);
listenFd.Listen(0);
Console.WriteLine("Server start!");
listenFd.BeginAccept(AcceptCallback, listenFd);
while(true)
{
Thread.Sleep(1000);
}
}
static void AcceptCallback(IAsyncResult result)
{
var listenfd = result.AsyncState as Socket;
var connfd = listenfd.EndAccept(result);
var clientState = new ClientState { socket = connfd };
clients.Add(connfd, clientState);
connfd.BeginReceive(clientState.readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
Console.WriteLine($" Client connected!");
listenfd.BeginAccept(AcceptCallback, listenfd);
}
static void EndReceiveCallback(IAsyncResult result)
{
var connfd = result.AsyncState as Socket;
var count = connfd.EndReceive(result);
if (count <= 0)
{
Console.WriteLine("Client disconnected!");
connfd.Close();
return;
}
connfd.BeginReceive(clients[connfd].readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
string strFromClient=System.Text.Encoding.Default.GetString(clients[connfd].readBuff,0,count);
Console.WriteLine($"string from client:{strFromClient}");
string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+strFromClient;
byte[] sendBuff= System.Text.Encoding.Default.GetBytes(strFromClientWithTime,0,strFromClientWithTime.Length);
foreach(var conn in clients.Keys)
{
conn.BeginSend(sendBuff, 0, sendBuff.Length, 0, EndSendCallback, conn);
}
}
static void EndSendCallback(IAsyncResult result)
{
var connfd = result.AsyncState as Socket;
connfd.EndSend(result);
}
}
}
在这两个例子中,Class ClientState
是
class ClientState
{
public Socket socket;
public byte[] readBuff=new byte[1024];
}
这两个例子应该都能正常工作。但我认为异步应该更好,正如
不过第二版的教程作者更喜欢用select只是说逻辑更清晰
我已经做了几个小时的研究,但仍然感到困惑。这只是一种偏好还是我在这里缺少的东西。
使用 Select
几乎总是意味着您正在轮询 - 占用一个线程来重复进行此调用,处理结果(通过某种形式的 Accept
),可能会休眠一段时间一会儿然后再做一遍。
BeginAccept
让您告诉 "the system" 在发生有趣的事情时通知您。与此同时,您自己使用的处理资源为零,如果一切都经过适当设计,no thread at all 会进行任何类型的轮询或等待。
我只有 可能 使用 Select
如果我有 "good" 次和 "bad" 次的奇怪情况执行接受。通过这种方式,您可以确保在 "bad" 时间内没有未完成的接受调用。但这将是一个非常罕见的利基领域,希望您已经确定了这种情况。
Select method receives a list of sockets that are binding and listening for incomming requests. When the call returns that list will only have sockets that have incomming requests pending to be accepted as showned here。
我可以证明的主要区别之一是在使用 AcceptAsync instead of the Accept 时在 OnCompleted 事件处理程序上放置的独占线程,这是将用于处理的当前线程使用该接受结果创建的套接字。
您可以将聊天室中的 1.synchronous 和 2.asynchronous 行为与此进行比较:
假设您有一个拍卖会,一个有 1000 人的房间,潜在买家。
同步:拍卖师使用固定的 pattern/order [您的轮询循环] 遍历所有席位,并询问 [轮询] 每个席位 he/she 是否想要放置更高的bid [检查传入连接/传入数据]。如果是,拍卖师将其注册[接受连接或处理输入的聊天语句],然后走到下一个并询问,依此类推。完成所有 1000 次后,他重复。请注意,不允许说话,即。在被询问之前没有人可以注册出价,尽管拍卖师在收到新出价后会立即宣布新出价[从聊天服务器向所有客户发送任何新的出价]。
异步:允许通话。任何人都可以随时喊出 his/her 出价 [callback to your callback fn],甚至可以同时 [OS 创建多个同步并行线程] - 拍卖师非常机敏、快速且技术娴熟,所以他听到每个出价 [OS 管理传入] 并以他能做到的最好的方式宣布它 [所有线程将数据放在您的公共共享数据存储库中,按照线程碰巧提出的顺序,并且你尽快发送通用数据],他甚至能够听到 200 个同时出价 [多线程]。无需步行,投标无固定顺序
在情况 1 中,您可能会从随机用户 John 那里得到一个抱怨:"Why is Lisa always commenting before me? Unfair!" [您的固定轮询顺序,从 1 到最大,Lisa 坐在 John 之前]。并且来自任何名为 "X" 的用户(假设您从公共服务器提供所有聊天条目,即 X 的条目进行服务器往返):"Why does my chat entry sometimes appear immediately [the auctioneer asked the X-1 person, he'll ask X in a nanosec] but sometimes it takes 2 secs? [the auctioneer asked the X+1 person, takes time before he's here again, the OS stack keeps X on hold]"
在重负载条件下,同步替代方案将是缓慢且旋转不良的变速箱。有点……:-)
小附录重新。异步编码(不太科学)
异步更难编码、测试和调试。它需要一种完全不同的编码风格,尽管异步部分可能存在于(侧面)一个同步应用程序中(仍然由用户操作驱动的应用程序,如 Windows 中)。
异步部分可以被认为是代码中隔离良好的电机;它适用于您在其周围维护的全局和持久性管理数据,但在盒子内部它可以自由 "go wild",具体取决于来自外部世界的内容 = 网络连接和数据,它们会轰炸最里面的直接框,独立于您自己的操作。这是 OS 引发的事件,源自那里的客户。
有两个重要的特征需要理解:
该框仅包含一组代码(几个函数),但可以同时触发多次。每次它被触发时,它的一个新的、孤立的实例将 运行。唯一将它与其他类似线程隔离开来的是线程 ID,每个实例都是唯一的。这些实例彼此独立工作,速度与 CPU/OS 一样快。然而,每个人都会在某个时候完成工作,并且可能希望将结果(每个人依次以任何预先未知的顺序)传递到您的全局数据中(例如,客户列表,总聊天记录)所有客户等)。这意味着必须要有锁机制,因为同一时刻只有一个实例(线程)可能访问全局数据,否则就乱七八糟。你的编码语言有这方面的工具(保持,锁定)。
由于这一组代码是事件驱动的,所以无法提前知道会有多少工作量。因此它必须有能力 a) 开始做工作 b) 继续做工作和 c) 当 OS 说 "that was it" 时以干净的方式完成。但是万一失败,如果 OS 的某种原因从来没有说 "that was it",那么一定有一个超时来完成事情。
不用说,这一切都很难做到正确。如果有错误导致一个线程停止,其他线程会继续吗?你如何访问失败线程的内部?您如何向后走以查看 why/how 失败的线程首先产生?等等
编写异步代码时,应该非常循序渐进;从最简单的实现开始;并有一个适当的外部环境来提供丰富的测试数据,从逐步速率(例如按键)到非常高的自动化速率,甚至同时(例如 LAN 中的其他计算机,提供数据在开始 feeding/stop 喂食的基础上)。