C # Ошибка отправки сообщения с сервера нескольким клиентам (Socket Programming) - PullRequest
0 голосов
/ 02 сентября 2018

Так что я очень заинтересован в изучении более сложных техник и языков программирования, и я решил продолжить свое изучение C #, изучая сокеты и серверное программирование. Я наткнулся на видео , которое, держу пари, многие из вас уже видели или слышали, и, на мой взгляд, довольно хорошо объясняет большую часть происходящего.

Так что моя проблема, в частности, заключается в том, чтобы по сути взять этот код и сделать его системой чата, которая получает информацию от клиента, отправляет ее на сервер, а затем с сервера на ВСЕ клиенты. Я просмотрел все учебники и другие страницы переполнения стека, но безрезультатно, по крайней мере, для себя. Я не могу понять это, и когда у кого-то есть ответ, он просто пишет «Нашел решение» и более или менее оставляет это при этом.

Мне известно о этой ветке на SO, и я пробовал то, что было там опубликовано, но безрезультатно. Я также сделал свою честную долю поиска через 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.

РЕДАКТИРОВАТЬ : код, который я использовал для моего клиента, находится по этой ссылке: https://pastebin.com/4WicEu2x

РЕДАКТИРОВАТЬ 2 : У меня такое чувство, что главная проблема заключается в том, что в моем клиентском коде клиент прослушивает только сообщения ПОСЛЕ отправляет сообщение. Если это так, то я тупее, чем скала, и хотел бы поблагодарить всех, кто писал здесь, рассказывая мне, как улучшить мою программу.

РЕДАКТИРОВАТЬ 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 голосов
/ 02 сентября 2018

Помимо того, что ваш код требует некоторых исправлений, он все еще работает. Я выполнил его в своем тестовом проекте, и он работал, как вы хотите (почти без проблем с параллелизмом).

Обязательные исправления параллелизма:

  1. Как отметил Эмир Куртович, вы не должны использовать общий буфер. Вместо этого используйте локальную переменную. При желании вы можете использовать одни и те же буферы между соединениями, используя поля уровня класса ThreadLocal или AsyncLocal. Это будет как потокобезопасным, так и уменьшит опасность фрагментации кучи.
  2. Вы не можете просто использовать Список для сокетов. Это не потокобезопасно. Существует как минимум два возможных исправления: а) использовать lock {} вокруг _ClientSk.Add(s) вызова или б) использовать одновременных коллекций .

Если ваши клиентские подключения происходят одновременно, последняя ошибка может вызвать поведение, которое вы описали. Тем не менее, существует также вероятность того, что ваш клиентский код имеет некоторые проблемы. Если после внесения исправлений проблема по-прежнему сохраняется, отправьте также свой код клиента.

Как правило, с вашим кодом еще предстоит проделать большую работу. Например, вы должны обрабатывать отключения клиентов. В настоящее время он просто генерирует исключения при вызове int received = s.EndReceive(iar) каждый раз, когда ваш клиент решает закрыть соединение. Помимо обработки исключения, вам нужно добавить в свой протокол какой-нибудь способ для корректного закрытия соединения. Например, обработав символ EOF. Здесь вы найдете хороший пример кода из MSFT.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...