Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sockets/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C#发布从服务器向多个客户端发送消息(套接字编程)_C#_Sockets - Fatal编程技术网

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

C#发布从服务器向多个客户端发送消息(套接字编程),c#,sockets,C#,Sockets,所以我对学习更复杂的编程技术和语言非常感兴趣,我决定通过学习sockets和服务器编程进一步学习C。我偶然发现了一个我敢打赌你们很多人已经看到或听说过的故事,在我看来,它非常善于解释大部分正在发生的事情 所以我的问题是,从本质上讲,这段代码是一个聊天系统,它从客户端获取信息,发送到服务器,然后从服务器发送到所有客户端。我查过任何教程和其他堆栈溢出页面都没有用,至少对我来说是这样。我似乎不明白,当有人有答案时,他们只是简单地写下“找到了解决方案”,然后或多或少地把它放在一边 我知道这是一个很好的帖

所以我对学习更复杂的编程技术和语言非常感兴趣,我决定通过学习sockets和服务器编程进一步学习C。我偶然发现了一个我敢打赌你们很多人已经看到或听说过的故事,在我看来,它非常善于解释大部分正在发生的事情

所以我的问题是,从本质上讲,这段代码是一个聊天系统,它从客户端获取信息,发送到服务器,然后从服务器发送到所有客户端。我查过任何教程和其他堆栈溢出页面都没有用,至少对我来说是这样。我似乎不明白,当有人有答案时,他们只是简单地写下“找到了解决方案”,然后或多或少地把它放在一边

我知道这是一个很好的帖子,我试过贴在上面的东西,但都没用。我也通过谷歌和诸如此类的网站完成了我的搜索任务,由于我对sockets和所有这些都不熟悉,这绝对不是一件容易的事情

实际问题 以下是我的服务器代码:

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

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

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

所需的并发修复程序:

  • 正如埃米尔·科托维奇(Emir Curtovic)提到的,您不应该使用公共缓冲区。改用局部变量。如果需要,可以使用ThreadLocal或AsyncLocal类级别字段在连接之间使用相同的缓冲区。它将是线程安全的,并减少堆碎片危险
  • 不能仅对套接字使用列表。它不是线程安全的。至少有两种可能的修复方法:a)在
    \u ClientSk.Add(s)
    调用周围使用
    锁{}
    ,或b)使用
  • 如果客户端连接同时出现,最后一个bug可能会导致您描述的行为。但是,您的客户机代码也可能存在一些问题。如果在引入修复程序后,您仍然遇到此问题,请同时发布您的客户端代码


    一般来说,代码还有很多工作要做。例如,您应该处理客户端断开连接。目前,只要您的客户端决定关闭连接,它就会在
    int received=s.EndReceive(iar)
    调用时抛出异常。除了处理异常之外,您还需要向协议中添加某种方式来执行优雅的连接关闭。例如,通过处理EOF符号。您将从MSFT中找到一个不错的代码示例。

    您是否尝试过调试并查看列表中的实际内容?什么被送到哪里?另外,您可能不想在循环中
    BeginReceive
    ,也不想对每个客户端使用相同的缓冲区。@SamiKuhmonen我已经调试过,在列表中看到,如果连接了x个客户端,列表的长度将是x,for循环将运行x次,这是应该的。至于
    BeginReceive
    你能解释一下为什么它不应该在循环中吗?谢谢你的回复!我知道我在某些方面有所欠缺,比如关闭连接,但我正试图一次解决一个问题。我不太明白你们在这里得到了什么,但从我能拼凑的东西来看,这可能是完全错误的,这个程序不应该是异步的?我也不知道这对你有什么作用,但对我没有作用。你的客户长什么样?这是我的:重新发布评论,因为我以前编辑它花了太长时间。所以我在客户端找到了解决方案,显然我太笨了,看不到客户端代码。尽管如此,还是非常感谢您的帮助,让我能够深入研究我的代码。另外,我将很快应用你提出的许多建议继续学习。非常感谢!我将在根帖子中对我的解决方案进行编辑。@EmirCurtovic,祝你的项目好运!关于客户端:我刚刚使用了
    -d blahblah
    开关,它有点笨拙,但可以完成以下任务:)。此外,您的程序应该是异步的,因为它服务于多个客户端,但是对类成员的访问必须同步。例如,当两个线程同时向同一个列表添加元素时,可能会导致非常糟糕的情况。
    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();
            }
        }
    }