@Senture

Клиент-серверное приложение UDP Socket?

Здравствуйте!
У меня не получается написать клиент-серверное приложение на протоколе UDP с использованием Socket.
Я пытаюсь построить UDP приложение на подобие приложение на протоколе TCP(P.S. Код TCP клиент серверного приложения частично состоит из примеров находящихся в открытом доступе), а работает оно у меня так:
СЕРВЕР:
1) Есть поток1(стоковый в котором работает метод Main).
Метод Main
class Program
    {
        static ServerObject server; // Сервер
        static Thread listenThread; // Поток для прослушивания

        static void Main(string[] args)
        {
            try
            {
                server = new ServerObject();
                listenThread = new Thread(new ThreadStart(server.Listen));
                listenThread.Start(); // Старт потока
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

2) Запускается поток2 в котором начинается ожидание подключений.
Метод Listen
protected internal void Listen()
        {
            try
            {
                tcpListener = new TcpListener(IPAddress.Any, 34447);
                tcpListener.Start();
                Console.WriteLine("Сервер запущен. Ожидайте подключений...");

                while(true)
                {
                    TcpClient tcpClient = tcpListener.AcceptTcpClient();

                    ClientObject clientObject = new ClientObject(tcpClient, this);
                    Thread clientThread = new Thread(new ThreadStart(clientObject.Process));
                    clientThread.Start();
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

3) После подключения клиента к серверу, сервер сохраняет клиента в классе и запускает поток3 для получения сообщений от клиента и рассылки его другим клиентам после в классе ServerObject в список List<> сохраняется ClientObject:
Конструктор класса ClientObject
protected internal string Id { get; private set; }
        protected internal NetworkStream Stream { get; private set; }
        string userName;
        TcpClient client;
        ServerObject server;

        public ClientObject(TcpClient tcpClient, ServerObject serverObject)
        {
            Id = Guid.NewGuid().ToString();
            client = tcpClient;
            server = serverObject;
            serverObject.AddConnection(this);
        }

Метод Process в классе ClientObject
public void Process()
        {
            try
            {
                Stream = client.GetStream();
                // Получаем имя пользователя
                string message = GetMessage();
                userName = message;
                while(true)
                {
                    try
                    {
                        message = GetMessage();
                        message = String.Format("{0}: {1}", userName, message);
                        Console.WriteLine(message);
                        server.BroadcastMessage(message, this.Id);
                    }
                    catch
                    {
                        break;
                    }
                }
            }
            catch(Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

Метод AddConnection в классе ServerObject
List<ClientObject> clients = new List<ClientObject>(); // Все подключения

        protected internal void AddConnection(ClientObject clientObject)
        {
            clients.Add(clientObject);
        }

4)При получении сообщения от пользователя вызывается метод BroadcastMessage из класса ServerObject, в котором полученное сообщение рассылается всем подключенным клиентам кроме самого отправителя, а информация кому отправлять содержится в списке List<> clients:
Метод BroadcastMessage в классе ServerObject
protected internal void BroadcastMessage(string message, string id)
        {
            byte[] data = Encoding.Unicode.GetBytes(message);
            for(int i = 0; i < clients.Count; i++)
            {
                if (clients[i].Id != id)
                    clients[i].Stream.Write(data, 0, data.Length);
            }
        }

КЛИЕНТ НА UNITY:
Client
string userName;
    private const string host = "127.0.0.1";
    private const int port = 34447;
    public TcpClient client;
    public NetworkStream stream;

    public Text messages;
    public Text outPut;

    void Start()
    {
        Connection();
    }

    public void Connection()
    {
        userName =  "login";
        client = new TcpClient();
        try
        {
            client.Connect(host, port); // Подключение клиента
            stream = client.GetStream(); // Получаем поток


            byte[] data = Encoding.Unicode.GetBytes(userName);
            stream.Write(data, 0, data.Length);

            // Запускаем новый поток для получения данных
            Thread receiveThread = new Thread(new ThreadStart(ReceiveMessage));
            receiveThread.Start();
            outPut.text += "Добро пожаловать, " + StaticHelperScripts.login + "\n";
        }
        catch(Exception ex)
        {
            Debug.Log(ex.Message);
        }
    }

    public void SendMessage()
    {
        outPut.GetComponent<Text>().text += userName + " : " + messages.text + "\n";
        byte[] data = Encoding.Unicode.GetBytes(messages.text);
        stream.Write(data, 0, data.Length);
    }

    // получение сообщений
    public void ReceiveMessage()
    {
        while (true)
        {
            try
            {
                byte[] data = new byte[512]; // буфер для получаемых данных
                StringBuilder builder = new StringBuilder();
                int bytes = 0;
                do
                {
                    bytes = stream.Read(data, 0, data.Length);
                    builder.Append(Encoding.Unicode.GetString(data, 0, bytes));
                }
                while (stream.DataAvailable == false && bytes == 0);

                string message = builder.ToString();
                outPut.text += message + "\n";
            }
            catch(Exception ex)
            {
                outPut.text += "Подключение прервано!\n"; //соединение было прервано
                Disconnect();
                return;
            }
        }
    }

    public void Disconnect()
    {
        if (stream != null)
            stream.Close(); // Отключение потока
        if (client != null)
            client.Close(); // отключение клиента
    }


Теперь собственно вопрос что необходимо сохранять чтобы сделать список из подключенных клиентов и делать рассылку сообщений от других клиентов? Я понимаю что протокол UDP без установления соединения поэтому я не могу понять как сделать подобное приложение только на UDP. А так же мультиплеерных action(шутеры) играх используется протокол UDP, т.к. он благополучно влияет на ping который очень важен. Вопрос: Что бы отправить сообщение по UDP должен быть открыт порт что не есть хорошо(к примеру 1к игроков и укаждого должен быть открыт порт 34447)?

Мне к сожалению трудно дается протокол UDP, так что прошу прощения за возможную банальщину.

P.S. Попытался расписать все максимально подробно, если что то не понятно задавайте вопросы, а я отвечу вам на них(если смогу). И всем огромное спасибо за всеее!!!
  • Вопрос задан
  • 2673 просмотра
Решения вопроса 1
@Sumor
UDP и TCP это разные протоколы над протоколом IP.
TCP устанавливает соединение и ведёт "разговор" между клиентом и сервером в рамках установленного соединения, пока кому-нибудь из них не надоест. В простейшем случае устанавливается соединение, клиент посылает запрос, сервер отправляет ответ. Размер полученных и отправляемых данных не ограничивается размером пакета. Пакетов может быть много, также может быть много запросов-ответов в рамках одного соединения. Доставку всех пакетов TCP гарантирует. В том смысле, если соединение есть, то отправленные вам данные будут доставлены.
UDP по сути просто отправка данных на какой-то адрес. Обработали его там или не обработали - для того, чтобы вам это узнать нужна дополнительная синхронизация. Размер передаваемых данных определяется размером пакета - максимум MTU, для Ethernet это 1500 байт. Может быть больше или меньше. Есть возможность отправить и больше данных, но они будут разбиты по частям и какие-то из них могут не дойти.
Аналогия примерно такая: TCP - телефон: соединение устанавливается, дальше идёт разговор между собеседниками. UDP - радиообмен: нажали PTT, говорите, но вы не знаете находится ли ваш абонент в зоне действия радио и услышит ли вас.

Для реализации и клиента и сервера UDP на C# можно использовать класс UDPClient. Он один и на сервер и на клиент. Для приёма сообщений сервер открывает порт на чтение. Размер принимаемых данных ограничен приёмным буфером. Если данные приходят, а вы их не успеваете обрабатывать - они затираются.
UDP используют для передачи данных, для которых критично время получения, но не критичен сам факт получения.
Для понимания того как ходят пакеты и устанавливается соединение используйте Wireshark или подобные программы.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы