C#从服务器向多个客户端发送消息(套接字编程)

时间:2018-09-02 04:47:23

标签: c# sockets

因此,我对学习更复杂的编程技术和语言非常感兴趣,因此,我决定通过学习套接字和服务器编程来进一步研究C#。我偶然发现video是我打赌你们很多人已经看到或听说过的,而且我认为它非常擅长于解释大多数正在发生的事情。

因此,我的问题主要是从本质上采用此代码,并使其成为一个聊天系统,该系统从客户端获取信息,将其发送到服务器,然后从服务器发送到所有客户端。我查找了任何教程和其他堆栈溢出页面都没有用,至少对我来说没有用。我似乎无法弄清楚,当有人得到答案时,他们只是写了“找到解决方案”,或多或少地留了下来。

我知道SO上有this线程,因此我尝试了其中的发布,但无济于事。我还完成了相当一部分的搜索工作,即通过google和诸如此类的东西进行搜索,并且由于不熟悉套接字等所有功能,因此绝对不是最简单的事情。

实际问题

这是我的服务器代码:

using static AppNameHere.ChatCommands;


namespace AppNameHere
{
    public partial class Host : Form
    {
        public static Host host = null;

        private static string response;

        private static byte[] _buffer = new byte[1024];
        private static List<Socket> _ClientSk = new List<Socket>();
        private static Socket _Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        public Host()
        {
            InitializeComponent();
        }

        private void Host_Load(object sender, EventArgs e)
        {
            SetupServer();
        }

        private static void SetupServer()
        {
            _Socket.Bind(new IPEndPoint(IPAddress.Any, HostJoinSelect.portSelected));
            _Socket.Listen(HostJoinSelect.playerTotal + 2);
            _Socket.BeginAccept(new AsyncCallback(AccCallback), null);
        }

        private static void AccCallback(IAsyncResult iar)
        {
            Socket s = _Socket.EndAccept(iar);
            _ClientSk.Add(s);
            Console.WriteLine("Client Connected.");
            s.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(RecCallback), s);
            _Socket.BeginAccept(new AsyncCallback(AccCallback), null);
        }

        private static void RecCallback(IAsyncResult iar)
        {
            Socket s = (Socket)iar.AsyncState;
            int received = s.EndReceive(iar);
            byte[] dataBuf = new byte[received];

            Buffer.BlockCopy(_buffer, 0, dataBuf, 0, received);

            string t = Encoding.ASCII.GetString(dataBuf);

            foreach (Socket socket in _ClientSk)
            {
                response = ChatCommandParser(t);

                byte[] data = Encoding.ASCII.GetBytes(response);
                socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
                socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(RecCallback), socket);
            }
        }

        private static void SendCallback(IAsyncResult iar)
        {
            Socket s = (Socket)iar.AsyncState;
            s.EndSend(iar);
        }
    }
}

顶部的using只是我为将文本转换为其他内容而制作的一个类,因此没有障碍。真正使我感到困惑的是foreach循环,从理论上讲,它应该起作用,并且它应该遍历所有客户端并向其发送所有消息,但如果客户端发送一条消息,其他客户端没有任何反应。只有原始客户才能收到邮件。

因此,调试和故障排除都表明它可以通过BeginSend运行,但是它仍然只向我输入的那个奇怪的客户端发送一条消息。

请先谢谢您,如果您有答案,请向我解释,因为我仍在学习,因此进行修复和作出解释将是不错的选择。

P.S。是的,我知道我使用的是TCP,而不是UDP。

编辑:我用于客户端的代码位于以下pastebin链接中:https://pastebin.com/4WicEu2x

编辑2 :我觉得主要的问题是,在我的客户代码中,我让客户仅监听消息 AFTER 它发送一条消息。如果是这样,那么我比石头更笨,要感谢任何在这里发布内容并告诉我如何改进程序的人。

编辑3 :我在第二次编辑中写的是正确的。如果您遇到问题,并且已经按照我在该youtube视频中所做的相同指南进行操作,那么只需知道您的客户端程序和服务器中都应具有异步接收功能。我对客户端没有太多的想法,但是请务必检查您的代码!这是我编辑的客户端代码(是的,我知道它需要很多工作):

namespace AppNameHere
{
    public partial class Join : Form
    {
        public static Join join = null;

        private static Socket _ClientSk = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        private static byte[] recBuf = new byte[1024];

        public Join()
        {
            InitializeComponent();
        }

        private void Join_Load(object sender, EventArgs e)
        {
        }

        private void Join_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (HostJoinSelect.hjs != null)
            {
                HostJoinSelect.hjs.Show();
            }
            else
            {
                HostJoinSelect.hjs = new HostJoinSelect();
                HostJoinSelect.hjs.Show();
            }
        }

        private void Join_Shown(object sender, EventArgs e)
        {
            LoopConnect();
        }

        private static void Send()
        {
            string req = join.textBox1.Text;
            byte[] buffer = Encoding.ASCII.GetBytes(req);
            _ClientSk.Send(buffer);
        }

        private static void ConnectedCallback()
        {
            _ClientSk.BeginReceive(recBuf, 0, recBuf.Length, SocketFlags.None, new AsyncCallback(ReceivedCallback), _ClientSk);
        }

        private static void ReceivedCallback(IAsyncResult iar)
        {
            Socket s = (Socket)iar.AsyncState;
            int rec = s.EndReceive(iar);
            byte[] dataBuf = new byte[rec];

            Buffer.BlockCopy(recBuf, 0, dataBuf, 0, rec);

            string q = Encoding.ASCII.GetString(dataBuf);

            join.Invoke(new MethodInvoker(delegate () {
                join.listBox1.Items.Add(Form1.namesave + ": " + q);
                join.listBox1.TopIndex = join.listBox1.Items.Count - 1;
            }));

            s.BeginReceive(recBuf, 0, recBuf.Length, SocketFlags.None, new AsyncCallback(ReceivedCallback), s);
        }

        private static void LoopConnect()
        {
            int attempts = 0;

            while (!_ClientSk.Connected)
            {
                if (attempts < 4)
                {
                    try
                    {
                        attempts++;
                        if (attempts <= 4)
                        {
                            _ClientSk.Connect(HostJoinSelect.IPSelectedJoin, HostJoinSelect.portSelectedJoin);
                            Console.WriteLine("Connected");
                            ConnectedCallback();
                        }
                        else
                        {
                            attempts = 0;
                            break;
                        }
                    }
                    catch (SocketException s)
                    {
                        if (attempts <= 4)
                        {
                            Console.WriteLine(s.Message + " | Connection attempts: " + attempts.ToString());
                        }
                        else
                        {
                            attempts = 0;
                            break;
                        }
                    }
                }
                else
                {
                    MessageBox.Show("You failed to connect to " + HostJoinSelect.IPSelectedJoin + ":" + HostJoinSelect.portSelectedJoin + ", please ensure you have a means of connecting to this address.");
                    join.Close();
                    break;
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Send();
        }
    }
}

我将代码从仅在自身发送消息之后才接受消息更改为使用异步BeginReceive来接收服务器发送的数据。始终检查您的代码和逻辑!

1 个答案:

答案 0 :(得分:0)

除了您的代码需要一些修复的事实外,它仍然有效。我在测试项目中执行了它,并且按您的要求进行了工作(几乎没有并发问题)。

所需的并发修复程序:

  1. 正如Emir Curtovic所述,您不应使用公共缓冲区。请改用局部变量。如果需要,可以使用ThreadLocal或AsyncLocal类级字段在连接之间使用相同的缓冲区。它既是线程安全的,又可以减少堆碎片的危险。
  2. 您不能仅将List用于套接字。它不是线程安全的。至少有两个可能的解决方法:a)在logcat调用周围使用lock {}或b)使用concurrent collections

如果客户端连接同时出现,则最后一个错误可能会导致您描述的行为。但是,您的客户端代码也可能会出现一些问题。如果在引入修复程序后仍然遇到问题,请同时发布您的客户端代码。

通常,您的代码还有更多工作要做。例如,您应该处理客户端断开连接。当前,只要您的客户端决定关闭连接,它只会在_ClientSk.Add(s)调用中引发异常。除了处理异常之外,您还需要向协议添加某种方式来执行正常的连接关闭。例如,通过处理EOF符号。 Here您将从MSFT中找到一个不错的代码示例。