从服务器广播消息

3

我正在学习套接字编程,目前遇到了向所有已连接客户端广播消息的困难。

服务器只需使用以下代码启动:

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;
}

我在那里启动socket以监听是否有人发送消息。

正如您所看到的,我在上面使用了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);
        }
    }
}

我有一个不同的TCP/IP代码,用于从服务器端广播消息。它使用异步通信。 - user1773603
2个回答

2

虽然我不能为您编写全部代码,但我将教您一些模式,以帮助您有所进展。

我假设您正在使用TCP进行通信。一个socket代表一个连接。如果您想发送数据,则必须使用您用于接收的相同Socket实例。创建新的socket和Bind将用于打开另一个监听端口(在此不适用)。

可能,您希望为每个连接都拥有一个对象,以跟踪所有相关状态。例如:

class MyConnection {
 Socket socket;
 Player player;
}

将该类的实例追踪到List<MyConnection>中。然后可以遍历该列表并使用socket发送内容。


您可以大大简化接受循环。这是一个好的模式:

while (true) {
 var connectionSocket = listeningSocket.Accept();
 Task.Run(() => ProcessConnection(connectionSocket));
}

所有微软示例代码中的异步内容都没有意义。只有一个接受线程。异步用于节省线程。节省一个线程是没有帮助的。此外,完全阻止事件也会完全抵消异步的好处。
如果连接的客户端数量较少(<100),或者您关心简单的代码,您可以简单地摆脱所有异步IO并使用同步IO(没有Begin/End)。
或者,您可以使用基于任务的套接字包装器或使用具有基于任务的方法的NetworkStream。然后您可以使用await。这也可以消除使编写简单逻辑非常困难的回调函数。
如果这不足以帮助您,我们可以在评论中继续讨论。

2
奖项将在18小时内到来。无论如何,您在评论中提供的建议是无价的。微软关于套接字的教程过于复杂。我刚刚成功地从客户端到服务器建立了连接,在服务器上显示客户端已连接,返回给客户端连接成功的消息,并在输入后成功地向所有客户端广播了消息。我将继续进行更多的尝试,但仍然感谢您 :) - Aleksa Ristic

1

使用:

  • 使用 System.Net.Sockets;
  • 使用 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();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接