我现在正在学习套接字;我正在努力向所有连接的客户端广播消息。
服务器只是用以下代码启动:
public static int Main(String[] args)
{
Thread t1 = new Thread(Test);
t1.Start();
#region Start listening to socket
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);
Socket listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
listener.Bind(localEndPoint);
listener.Listen(100);
Console.WriteLine("[{0}] Server started listening!", DateTime.Now);
while (true)
{
// Set the event to nonsignaled state.
allDone.Reset();
// Start an asynchronous socket to listen for connections.
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
// Wait until a connection is made before continuing.
allDone.WaitOne();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
#endregion
Console.WriteLine("\nPress ENTER to continue...");
Console.Read();
return 0;
}
在那里,我启动套接字以侦听是否有人发送消息。
如您所见,我在那里有thread
,并且我希望该线程不时向所有连接的用户发送一些消息(目前所有字节仅用于测试)。
这是线程中的方法
private static void Test()
{
while (true)
{
for (int i = 0; i < Players.Length; i++)
{
if (Players[i] == null)
continue;
Socket cs = new Socket(Players[i].IPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
cs.Bind(Players[i].IPEndPoint);
byte[] data = Encoding.ASCII.GetBytes("Random data");
cs.Send(data);
}
}
}
此方法仅是最后一次尝试,但未成功。问题是它使我发现以下错误:
socket是对null的引用。
在这种情况下,但无论如何我都无法向每个客户发送消息。
这是我跟踪客户端与服务器连接的方式。
case "0": // Client tries to connect to server
if (nClients >= MAX_PLAYERS)
{
Send(handler, "Too many players. Try again later.");
return;
}
for (int i = 0; i < MAX_PLAYERS; i++)
{
if(Players[i] == null)
{
Players[i] = new Client();
Players[i].Name = parts[1].ToString();
Players[i].IPEndPoint = handler.RemoteEndPoint as IPEndPoint;
Send(handler, String.Format("1|{0}", i));
Console.WriteLine("[{0}] Succesfully registered client ID: {1}, NAME: {2}!", DateTime.Now, i, parts[1].ToString());
i = MAX_PLAYERS;
nClients++;
}
}
break;
这只是位于我的方法中的专注于处理连接消息的代码的一部分:
private static void HandleMessages(string message, Socket handler)
我从这里叫它:
public static void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue.
allDone.Set();
// Get the socket that handles the client request.
Socket listener = (Socket)ar.AsyncState;
Socket handler = listener.EndAccept(ar);
// Create the state object.
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
}
public static void ReadCallback(IAsyncResult ar)
{
String content = String.Empty;
// Retrieve the state object and the handler socket
// from the asynchronous state object.
StateObject state = (StateObject)ar.AsyncState;
Socket handler = state.workSocket;
// Read data from the client socket.
int bytesRead = handler.EndReceive(ar);
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(
state.buffer, 0, bytesRead));
// Check for end-of-file tag. If it is not there, read
// more data.
content = state.sb.ToString();
if (content.IndexOf("<EOF>") > -1)
{
string message = content.Remove(content.Length - 5, 5);
HandleMessages(message, handler);
}
else
{
// Not all data received. Get more.
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
}
}
答案 0 :(得分:2)
虽然我无法为您编写所有代码,但我会教您一些应该使您步入正轨的模式。
我假设您正在通过TCP进行通信。套接字代表一个连接。如果要发送内容,则必须使用与接收相同的Socket
实例。创建新的套接字和Bind
将用于打开另一个监听端口(此处不适用)。
可能,您希望每个跟踪所有相关状态的连接都有一个对象。例如:
class MyConnection {
Socket socket;
Player player;
}
在List<MyConnection>
中跟踪该类的实例。然后,您可以遍历该列表并使用socket
发送一些内容。
您可以大大简化accept循环。这是一个很好的模式:
while (true) {
var connectionSocket = listeningSocket.Accept();
Task.Run(() => ProcessConnection(connectionSocket));
}
Microsoft示例代码中的所有异步内容均无用。只有一个接受线程。异步用于保存线程。保存一个线程没有帮助。而且,阻止事件完全抵消了异步带来的好处。
如果连接的客户端数量少(<100)和/或如果您关心简单的代码,则可以简单地摆脱所有异步IO并使用同步IO(无开始/结束)。
或者,您可以对套接字使用基于任务的包装器,也可以使用具有基于任务的方法的NetworkStream
。然后,您可以使用await
。这也摆脱了那些使编写简单逻辑变得非常困难的回调。
如果这不足以帮助您,我们可以在评论中进行处理。
答案 1 :(得分:0)
using System.Net.Sockets;
using System.Threading;
创建一个用于存储客户信息的类
public TcpClient tclient;
public byte[] Tx, Rx;
public string strId;
public string strName;
public ClientNode(TcpClient _tclient, byte[] _tx, byte[] _rx, string _str, string _name)
{
tclient = _tclient;
Tx = _tx;
Rx = _rx;
strId = _str;
strName = _name;
}
public string ToStrng()
{
return strName;
}
bool mIsListening = false;
TcpListener mTCPListener;
private List<ClientNode> mlClientSocks;
TcpClient tcpc;
IPAddress IP;
int Port;
Thread t;
public void StartServer(string _IP, int _Port)
{
IP = IPAddress.Parse(_IP);
Port = _Port;
t = new Thread(new ThreadStart(this.StartProcessing));
t.Start();
}
public void StartProcessing()
{
//Server is started
mlClientSocks = new List<ClientNode>();
try
{
mTCPListener = new TcpListener(IP, Port);
//Server is running now
mTCPListener.Start();
mIsListening = true;
mTCPListener.BeginAcceptTcpClient(onCompleteAcceptTcpClient, mTCPListener);
}
catch (Exception exx)
{
// Handle exception message hare
}
}
void onCompleteAcceptTcpClient(IAsyncResult iar)
{
TcpListener tcpl = (TcpListener)iar.AsyncState;
TcpClient tclient = null;
ClientNode cNode = null;
if (!mIsListening)
{
//Stopped listening for incoming connections
return;
}
try
{
tclient = tcpl.EndAcceptTcpClient(iar);
//Client Connected...
StreamReader sR = new StreamReader(tclient.GetStream());
// Read the username (waiting for the client to use WriteLine())
String username = (String)sR.ReadLine();
tcpl.BeginAcceptTcpClient(onCompleteAcceptTcpClient, tcpl);
lock (mlClientSocks)
{
// add newly connected client node in List
mlClientSocks.Add((cNode = new ClientNode(
tclient,
new byte[512],
new byte[512],
tclient.Client.RemoteEndPoint.ToString(),
username,
)));
}
// broadcasting newly connected client to all other clients
BroadcastClients("New client connected: " + username);
tclient.GetStream().BeginRead(cNode.Rx, 0, cNode.Rx.Length, onCompleteReadFromTCPClientStream, tclient);
}
catch (Exception exc)
{
// handle exception here
}
}
void onCompleteReadFromTCPClientStream(IAsyncResult iar)
{
int nCountReadBytes = 0;
string strRecv;
ClientNode cn = null;
try
{
lock (mlClientSocks)
{
tcpc = (TcpClient)iar.AsyncState;
// find client from list
cn = mlClientSocks.Find(x => x.strId == tcpc.Client.RemoteEndPoint.ToString());
// check if client is connected
if (IsConnected)
nCountReadBytes = tcpc.GetStream().EndRead(iar);
else
nCountReadBytes = 0;
//Disconnect Client if there is no byte
if (nCountReadBytes == 0)
{
mlClientSocks.Remove(cn);
return;
}
// read message recieved from client (node)
strRecv = Encoding.ASCII.GetString(cn.Rx, 0, nCountReadBytes).Trim();
/*
Handle messages from clients
*/
cn.Rx = new byte[512];
tcpc.GetStream().BeginRead(cn.Rx, 0, cn.Rx.Length, onCompleteReadFromTCPClientStream, tcpc);
}
}
catch (Exception)
{
lock (mlClientSocks)
{
//Client is Disconnected and removed from list
mlClientSocks.Remove(cn);
}
}
}
private void onCompleteWriteToClientStream(IAsyncResult iar)
{
try
{
TcpClient tcpc = (TcpClient)iar.AsyncState;
tcpc.GetStream().EndWrite(iar);
}
catch (Exception exc)
{
// handle exception
}
}
public void StopServer()
{
StopListing();
}
public void StopListing()
{
// stop server thread
t.Interrupt();
try
{
mIsListening = false;
mTCPListener.Stop();
}
catch (Exception eee)
{
// handle exception
}
}
public bool IsConnected
{
get
{
try
{
if (tcpc != null && tcpc.Client != null && tcpc.Client.Connected)
{
// Detect if client disconnected
if (tcpc.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (tcpc.Client.Receive(buff, SocketFlags.Peek) == 0)
{
// Client disconnected
return false;
}
else
{
return true;
}
}
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
}
在使用上述代码创建服务器类之后,现在在该服务器类中创建一个函数(BroadcastClients
)来广播客户端,例如:
void BroadcastClients(string BroadcastingMsg)
{
if (mlClientSocks.Count() <= 0)
return;
else
{
ClientNode cn = null;
mlClientSocks.ForEach(delegate (ClientNode clntN)
{
cn = clntN;
try
{
// broadcasting online clients list
cn.Tx = Encoding.ASCII.GetBytes(BroadcastingMsg);
cn.tclient.GetStream().BeginWrite(cn.Tx, 0, cn.Tx.Length, onCompleteWriteToClientStream, cn.tclient);
}
catch (Exception e)
{
// handle exception
}
});
}
}
创建上述方法后,您现在可以在要向客户端广播消息的任何地方使用它。
像这样使用上面的服务器类:
MyServer server = new MyServer();
server.StartServer("127.0.0.1",8000);
// For stopping server use this
// server.StopServer();