我创建了一个测试驱动程序来模拟我与另一个程序有关的问题:
EDIT2:这段代码可以在很大程度上被忽略,第二段代码可能更简洁
using System;
using System.Net;
using System.Text;
using System.Net.Sockets;
namespace TestAsyncSockets
{
class Program
{
/// <summary>
/// Object to send back and forth along with datagrams
/// </summary>
class State
{
/// <summary>
/// Socket that started async receive
/// </summary>
public Socket S;
/// <summary>
/// Endpoint that data was received from
/// </summary>
public EndPoint E;
/// <summary>
/// Buffer where received data is stored
/// </summary>
public byte[] Buffer;
}
private const int SERVER_PORT = 50000;
private const int SERVER_CMD_SEND_PORT = 50001;
private const int CLIENT_PORT = 50002;
private static readonly IPAddress s_LocalHost = new IPAddress(new byte[] {127, 0, 0, 1});
private static readonly StringBuilder s_Logger = new StringBuilder();
private static readonly object s_Locker = new object();
/// <summary>
/// Test async sockets
/// </summary>
static void Main()
{
// Server receives commands on SERVER_PORT from anyone and calls CommandReceived
CreateReceiver(s_LocalHost, SERVER_PORT, IPAddress.Any, 0, ServerCommandReceived);
// Client receives Acks on CLIENT_PORT from SERVER_PORT and calls ResponseReceived
CreateReceiver(s_LocalHost, CLIENT_PORT, s_LocalHost, SERVER_PORT, ClientResponseReceived);
// Client receives commands on CLIENT_PORT from SERVER_CMD_SEND_PORT and calls
CreateReceiver(s_LocalHost, CLIENT_PORT, s_LocalHost, SERVER_CMD_SEND_PORT, ClientCommandReceived);
// Client sends commands from CLIENT_PORT to SERVER_PORT
CreateSender(s_LocalHost, CLIENT_PORT, s_LocalHost, SERVER_PORT, Encoding.ASCII.GetBytes("ClientCmd"));
// Server sends commands from SERVER_CMD_SEND_PORT to CLIENT_PORT
CreateSender(s_LocalHost, SERVER_CMD_SEND_PORT, s_LocalHost, CLIENT_PORT, Encoding.ASCII.GetBytes("ServerCmd"));
// Wait for messages to be received (hack for test code only)
System.Threading.Thread.Sleep(5000);
System.IO.File.AppendAllText("C:\\log.txt", s_Logger.ToString());
Console.WriteLine(s_Logger.ToString());
Console.ReadLine();
}
/// <summary>
/// Creates a socket to send data
/// </summary>
private static void CreateSender(IPAddress localIp, int localPort, IPAddress remoteIp, int remotePort, byte[] dataToSend)
{
// Create socket for sending
Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// Allow other Sockets to bind to its local end point
sender.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
// Create local end point and bind to it
IPEndPoint localEndPoint = new IPEndPoint(localIp, localPort);
sender.Bind(localEndPoint);
// Create remote end point to send to
EndPoint remoteEndPoint = new IPEndPoint(remoteIp, remotePort);
// Send and log
sender.SendTo(dataToSend, remoteEndPoint);
string message = string.Join("", Encoding.ASCII.GetChars(dataToSend));
LogStatement("Sent " + message + " from " + localEndPoint + " to " + remoteEndPoint);
}
/// <summary>
/// Creates socket to receive a message and call async method
/// </summary>
private static void CreateReceiver(IPAddress localIp, int localPort, IPAddress remoteIp, int remotePort, AsyncCallback messageReceived)
{
// Create UDP socket
Socket receiver = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// Create local endpoint to bind to
IPEndPoint localEndPoint = new IPEndPoint(localIp, localPort);
// Allow other Sockets to bind to this local end point too
receiver.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
// Bind to local endpoint
receiver.Bind(localEndPoint);
// Create remote endpoint
EndPoint remoteEndPoint = new IPEndPoint(remoteIp, remotePort);
// Create state object for async callback
State receiveState = new State { S = receiver, E = remoteEndPoint, Buffer = new byte[100] };
// Begin receiving message from remote endpoint
// Call messageReceived callback when done
receiver.BeginReceiveFrom(receiveState.Buffer, 0, 100, SocketFlags.None, ref remoteEndPoint, messageReceived, receiveState);
LogStatement("Receiving on " + localEndPoint + " from " + remoteEndPoint + " and will call " + messageReceived.Method.Name);
}
/// <summary>
/// Method called when server receives command
/// </summary>
private static void ServerCommandReceived(IAsyncResult ar)
{
LogStatement("In ServerCommandReceived");
State state = (State)ar.AsyncState;
try
{
int bytes = state.S.EndReceiveFrom(ar, ref state.E); // End recieve and get data
if (bytes > 0)
{
byte[] actualMessage = new byte[bytes];
Buffer.BlockCopy(state.Buffer, 0, actualMessage, 0, bytes);
string message = string.Join("", Encoding.ASCII.GetChars(actualMessage));
LogStatement("Received " + message + " on " + state.S.LocalEndPoint + " from " + state.E);
}
}
catch (SocketException se)
{
LogStatement(se.ToString());
}
// We just received a command. Send a response back to the port that sent the command (state.E)
state.S.SendTo(Encoding.ASCII.GetBytes("Response"), state.E);
LogStatement("Sent " + "Response" + " from " + state.S.LocalEndPoint + " to " + state.E);
state.S.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.E, ServerCommandReceived, state);
}
/// <summary>
/// Method called when client receives a command response
/// </summary>
private static void ClientResponseReceived(IAsyncResult ar)
{
LogStatement("In ClientResponseReceived");
State state = (State)ar.AsyncState;
try
{
int bytes = state.S.EndReceiveFrom(ar, ref state.E);
if (bytes > 0)
{
byte[] actualMessage = new byte[bytes];
Buffer.BlockCopy(state.Buffer, 0, actualMessage, 0, bytes);
string message = string.Join("", Encoding.ASCII.GetChars(actualMessage));
LogStatement("Received " + message + " on " + state.S.LocalEndPoint + " from " + state.E);
}
}
catch (SocketException se)
{
Console.WriteLine(se.ToString());
}
state.S.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.E, ClientResponseReceived, state);
}
/// <summary>
/// Method called when client receives a server command
/// </summary>
private static void ClientCommandReceived(IAsyncResult ar)
{
LogStatement("In ClientCommandReceived");
State state = (State)ar.AsyncState;
try
{
int bytes = state.S.EndReceiveFrom(ar, ref state.E);
if (bytes > 0)
{
byte[] actualMessage = new byte[bytes];
Buffer.BlockCopy(state.Buffer, 0, actualMessage, 0, bytes);
string message = string.Join("", Encoding.ASCII.GetChars(actualMessage));
LogStatement("Received " + message + " on " + state.S.LocalEndPoint + " from " + state.E);
}
}
catch (SocketException se)
{
LogStatement(se.ToString());
}
state.S.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.E, ClientCommandReceived, state);
}
/// <summary>
/// Thread safe logging (hopefully)
/// </summary>
/// <param name="s"></param>
private static void LogStatement(string s)
{
lock (s_Locker)
{
s_Logger.AppendLine(s);
}
}
}
}
它有点冗长,但它主要是评论。如果人们愿意,我可以删除它们。
无论如何,直到生意。快速看一下,我希望每次调用ServerCommandReceived,ClientResponseReceived和ClientCommandReceived,但是当我运行它时,我看到ServerCommandReceived调用一次而ClientResponseReceived调用两次。似乎正在发生的是当从50001或50002收到消息时正在调用ClientResponseReceived。为什么会发生这种情况?是否不可能将两个UDP套接字绑定到同一端口,侦听来自两个远程UDP端口的消息?
编辑:
只是为了澄清,该计划的输出是:
从0.0.0.0:0接收127.0.0.1:50000并将调用ServerCommandReceived
从127.0.0.1:50000接收127.0.0.1:50002并将调用ClientResponseReceived
从127.0.0.1:50001接收127.0.0.1:50002并将调用ClientCommandReceived
从127.0.0.1:50002发送ClientCmd到127.0.0.1:50000
从127.0.0.1:50001发送ServerCmd到127.0.0.1:50002
在ServerCommandReceived中
从127.0.0.1:50002收到127.0.0.1:50000上的ClientCmd
发送响应从127.0.0.1:50000到127.0.0.1:50002
在ClientResponseReceived中
在127.0.0.1:50002上从127.0.0.1:50001收到ServerCmd
在ClientResponseReceived中
收到127.0.0.1:50002的回复,来自127.0.0.1:50000
粗体文字就是问题所在。应该在回调&#34; ClientCommandReceived&#34;中收到ServerCmd。
另外,我尝试使用Connect()和BeginReceive / Send而不是BeginReceiveFrom / SendTo,但是没有用。并且它不允许连接到IPAddress.Any或端口0,所以我不得不破解它甚至到目前为止。
EDIT2: 为了简洁,我将代码更新为以下内容:
using System;
using System.Net;
using System.Text;
using System.Net.Sockets;
using System.Collections.Generic;
namespace TestAsyncSockets {
static class CProgramSuccinct {
// Class for async receives
class State {
public Socket S;
public EndPoint E;
public byte[] Buffer;
}
private static readonly IPAddress s_LocalHost = new IPAddress(new byte[] {127, 0, 0, 1});
private static readonly StringBuilder s_Logger = new StringBuilder();
private static readonly object s_Locker = new object();
static void Main() {
System.IO.File.WriteAllText("C:\\log.txt", "");
SetupReceivers();
SendCommands();
// Wait for messages to be received (hack for test code only)
System.Threading.Thread.Sleep(1000);
System.IO.File.AppendAllText("C:\\log.txt", s_Logger.ToString());
Console.WriteLine(s_Logger.ToString());
Console.ReadLine();
}
private static void SendCommands() {
List<Tuple<IPEndPoint, IPEndPoint>> endpointPairs = new List<Tuple<IPEndPoint, IPEndPoint>> {
new Tuple<IPEndPoint, IPEndPoint>(
new IPEndPoint(s_LocalHost, 50002), new IPEndPoint(s_LocalHost, 50000)), // client sends command from 50002 to 50000
new Tuple<IPEndPoint, IPEndPoint>(
new IPEndPoint(s_LocalHost, 50001), new IPEndPoint(s_LocalHost, 50002)), // server sends command from 50001 to 50002
};
foreach (Tuple<IPEndPoint, IPEndPoint> endpoints in endpointPairs) {
Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
sender.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
sender.Bind(endpoints.Item1);
sender.SendTo(Encoding.ASCII.GetBytes("Command"), endpoints.Item2);
System.Threading.Thread.Sleep(500);
LogStatement("Sent Command from " + endpoints.Item1 + " to " + endpoints.Item2);
}
}
private static void SetupReceivers() {
List<Tuple<IPEndPoint, IPEndPoint>> endpointPairs = new List<Tuple<IPEndPoint, IPEndPoint>> {
new Tuple<IPEndPoint, IPEndPoint>(
new IPEndPoint(s_LocalHost, 50000), new IPEndPoint(IPAddress.Any, 0)), // server receives commands on 50000 from anywhere
new Tuple<IPEndPoint, IPEndPoint>(
new IPEndPoint(s_LocalHost, 50002), new IPEndPoint(s_LocalHost, 50000)), // client receives responses on 50002 from 50000
new Tuple<IPEndPoint, IPEndPoint>(
new IPEndPoint(s_LocalHost, 50002), new IPEndPoint(s_LocalHost, 50001)), // client receives commands on 50002 from 50001
};
// Callbacks for each async receive
List<AsyncCallback> receiverCallbacks = new List<AsyncCallback> { ServerCommandReceived, ClientResponseReceived, ClientCommandReceived };
int i = 0;
foreach (Tuple<IPEndPoint, IPEndPoint> endPoints in endpointPairs) {
Socket receiver = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
receiver.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
receiver.Bind(endPoints.Item1);
State receiveState = new State { S = receiver, E = endPoints.Item2, Buffer = new byte[100] };
receiver.BeginReceiveFrom(receiveState.Buffer, 0, 100, SocketFlags.None, ref receiveState.E, receiverCallbacks[i], receiveState);
LogStatement("Receiving on " + endPoints.Item1 + " from " + endPoints.Item2 + " and will call " + receiverCallbacks[i].Method.Name);
i++;
}
}
private static void ServerCommandReceived(IAsyncResult ar) {
State state = LogMessage(ar, "ServerCommandReceived");
// We just received a command. Send a response back to the port that sent the command (state.E)
state.S.SendTo(Encoding.ASCII.GetBytes("Response"), state.E);
LogStatement("ServerCommandReceived: Sent " + "Response" + " from " + state.S.LocalEndPoint + " to " + state.E);
state.S.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.E, ServerCommandReceived, state);
}
// The client doesn't send responses
private static void ClientResponseReceived(IAsyncResult ar) { LogMessage(ar, "ClientResponseReceived"); }
private static void ClientCommandReceived(IAsyncResult ar) { LogMessage(ar, "ClientCommandReceived"); }
// logs received message
private static State LogMessage(IAsyncResult ar, string methodName) {
System.Threading.Thread.Sleep(500);
State state = (State)ar.AsyncState;
int bytes = state.S.EndReceiveFrom(ar, ref state.E);
if (bytes > 0)
{
byte[] actualMessage = new byte[bytes];
Buffer.BlockCopy(state.Buffer, 0, actualMessage, 0, bytes);
string message = string.Join("", Encoding.ASCII.GetChars(actualMessage));
LogStatement(methodName + ": Received " + message + " on " + state.S.LocalEndPoint + " from " + state.E);
}
state.S.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.E, ClientCommandReceived, state);
return state;
}
private static void LogStatement(string s) {
lock (s_Locker) {
s_Logger.AppendLine(s);
}
}
}
}
只是为了让帖子更短,发生了一些奇怪的事情:
Receiving on 127.0.0.1:50000 from 0.0.0.0:0 and will call ServerCommandReceived
Receiving on 127.0.0.1:50002 from 127.0.0.1:50000 and will call ClientResponseReceived
Receiving on 127.0.0.1:50002 from 127.0.0.1:50001 and will call ClientCommandReceived
Sent Command from 127.0.0.1:50002 to 127.0.0.1:50000
ServerCommandReceived: Received Command on 127.0.0.1:50000 from 127.0.0.1:50002
ServerCommandReceived: Sent Response from 127.0.0.1:50000 to 127.0.0.1:50002
Sent Command from 127.0.0.1:50001 to 127.0.0.1:50002
ClientResponseReceived: Received Command on 127.0.0.1:50002 from 127.0.0.1:50001
ClientCommandReceived: Received Response on 127.0.0.1:50002 from 127.0.0.1:50000
现在我看到每个接收方法调用一次!不幸的是,我仍然感到困惑,因为当从50000收到消息时应该调用ClientResponseReceived,并且当从50001收到消息但是它们被切换时应该调用ClientCommandReceived。不确定这是引用问题还是Windows套接字问题。