我刚刚开始通过各种谷歌搜索来学习套接字,但是我在解决如何在C#中正确使用套接字时遇到了一些问题,而且我需要一些帮助。
我有一个测试应用程序(Windows窗体)和一个不同的类(实际上是它自己的.dll,但这是无关紧要的)我有我的套接字代码的所有服务器/客户端代码。
问题1)
在我的测试应用程序中,在服务器部分,用户可以单击“开始侦听”按钮,我的套接字应用程序的服务器部分应该开始侦听指定地址和端口上的连接,到目前为止一切顺利。
但是,应用程序将被阻止,在有人连接到服务器之前我无法执行任何操作。如果没有人连接怎么办?我该怎么处理?我可以指定接收超时但是呢?抛出异常,我该怎么办?我想要的是在主应用程序上进行某种活动,以便用户知道应用程序没有冻结并等待连接。但是如果没有连接,它应该超时并关闭所有内容。
也许我应该使用异步调用来发送/接收方法,但它们似乎令人困惑,我无法使其工作,只能进行同步工作(我将在下面发布我当前的代码)。
问题2)
当某些发送/接收呼叫超时时,是否需要关闭任何内容。正如你在我当前的代码中看到的那样,我在套接字上有一堆关闭,但这种方式感觉不对。但是当操作超时且我没有关闭套接字时,它也感觉不对。
总结我的两个问题....我想要一个不阻止的应用程序,以便用户知道服务器正在等待连接(例如,使用一个小的选框动画)。如果在一段时间后从未建立连接,我想关闭应该关闭的所有内容。建立连接或在一段时间后没有发生连接时,我想告知主要应用结果。
这是我的一些代码,其余的类似。 Packet
类是一个代表我的自定义数据单元的自定义类,它现在只是基于enums
的一堆属性,其方法是将它们转换为字节并返回属性。
开始侦听连接的函数是这样的:
public void StartListening(string address, int port) {
try {
byte[] bufferBytes = new byte[32];
if(address.Equals("0.0.0.0")) {
udpSocket.Bind(new IPEndPoint(IPAddress.Any, port));
} else {
udpSocket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
}
remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
int numBytesReceived = udpSocket.ReceiveFrom(bufferBytes, ref remoteEndPoint);
if(numBytesReceived == 0) {
udpSocket.Close();
return;
}
Packet syncPacket = new Packet(bufferBytes);
if(syncPacket.PacketType != PacketType.Control) {
udpSocket.Close();
return;
}
} catch {
if(udpSocket != null) {
udpSocket.Close();
}
}
}
我确信我有一堆不必要的代码,但我是新手,我不知道该做什么,修复我的代码以及如何解决上述问题的任何帮助都非常感谢。
修改
我应该说我的要求是使用UDP并在应用层自己实现这些。您可以将此视为家庭作业,但我没有这样标记,因为代码无关紧要,不会成为我的成绩的一部分而我的问题(我的问题)是“如何编码”,因为我的套接字体验很少而且不是教导。
但是我必须说我现在解决了我的问题我认为......我在演示应用程序上使用了线程,它给了我一些问题,现在我在协议连接中使用它,更有意义我可以轻松更改我的自定义协议类属性,并从演示应用程序中读取它们。
我已指定超时并在达到超时时抛出SocketException。只要捕获到这样的异常,就会关闭套接字连接。我只是谈论连接握手,仅此而已。如果没有捕获异常,代码可能会顺利进行并建立连接。
请相应调整您的答案。现在,将我们中的任何一个标记为已接受的答案是没有意义的,希望你理解。
答案 0 :(得分:9)
你的东西有点不对劲。
首先,UDP是无连接的。您没有连接或断开连接。您所做的就是发送和接收(每次都必须指定目的地)。您还应该知道UDP承诺的唯一事情就是每次读取都会收到完整的消息。 UDP不保证您的邮件以正确的顺序到达或者它们到达的时间。
另一方面,TCP是基于连接的。您连接,发送/接收,最后断开连接。 TCP是基于流的(而UDP是基于消息的),这意味着您可以在第一次读取时获得一半消息,在第二次读取时获得另一半消息。 TCP承诺,一切都会以正确的顺序到达(或者会死于尝试;)。因此,使用TCP意味着您应该拥有某种逻辑来了解完整消息何时到达以及用于构建完整消息的缓冲区。下一个重要问题是关于阻止。由于您是新手,我建议您使用Threads来处理套接字。将侦听器套接字放在一个线程中,并将每个连接套接字放在一个单独的线程中(5个连接的客户端= 5个线程)。
我还建议您使用TCP,因为它比构建消息和构建事务系统更容易构建完整的消息(如果您想确保所有消息到达/来自客户端,则需要这样做。)
您仍然遇到UDP错误。除了清理系统资源之外,Close不会执行任何操作。你应该做这样的事情:
public void MySimpleServer(string address, int port)
{
try
{
byte[] bufferBytes = new byte[32];
if(address.Equals("0.0.0.0")) {
udpSocket.Bind(new IPEndPoint(IPAddress.Any, port));
} else {
udpSocket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
}
remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
while (serverCanRun)
{
int numBytesReceived = udpSocket.ReceiveFrom(bufferBytes, ref remoteEndPoint);
// just means that one of the clients closed the connection using Shutdown.
// doesnt mean that we cant continue to receive.
if(numBytesReceived == 0)
continue;
// same here, loop to receive from another client.
Packet syncPacket = new Packet(bufferBytes);
if (syncPacket.PacketType != PacketType.Control)
continue;
HandlePacket(packet, endPoint);
}
} catch {
if(udpSocket != null) {
udpSocket.Close();
}
}
}
请参阅?因为没有连接,所以只是浪费时间来关闭UDP套接字以开始从另一个套接字收听。同一个套接字可以从知道正确端口和地址的所有udp客户端接收。这就是remoteEndPoint
的用途。它告诉发送消息的客户端。
小更新,以汇总我的所有评论。
UDP是无连接的。您永远无法检测连接是已建立还是已断开连接。 UDP套接字上的Close方法只释放系统资源。对client.Close()
的调用不会通知服务器套接字(与TCP一样)。
检查连接是否打开的最佳方法是创建ping / pong样式的数据包。即客户端发送PING消息,服务器以PONG响应。请记住,如果消息未到达,UDP将不会尝试重新发送消息。因此,在假定服务器已关闭之前(如果没有收到PONG),您需要重新发送PING几次。
对于关闭客户端,您需要将自己的消息发送到服务器,告知客户端将停止与服务器通信。为了可靠性,同样的事情在这里,继续重新发送BYE消息,直到你收到回复。
如果你想要可靠性,那么你必须为UDP实现一个事务系统。 SIP(google rfc3261)是使用UDP上的事务的协议示例。答案 1 :(得分:0)
从您的描述中我觉得您应该使用TCP套接字而不是UDP。差异是
TCP - 您等待特定IP上的连接:某些用户可以连接到它的端口,直到套接字关闭可以通过发送和接收信息进行通信。这就像打电话给某人。
UDP - 您在某个IP上等待消息:端口。想要通信的用户只是通过UDP发送消息。您将通过UDP接收消息。交货顺序无法保证。这更像是向某人发送蜗牛邮件。没有建立专门的沟通渠道。
现在出现问题
服务器
客户端