Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.67/3: Рейтинг темы: голосов - 3, средняя оценка - 4.67
5 / 2 / 3
Регистрация: 20.10.2017
Сообщений: 16

Не могу получить сообщение, пока не отправлю своё. Или не могу отправить своё пока не получу чужое

19.01.2025, 16:15. Показов 767. Ответов 5
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Выглядит это вот так:

Сервер:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
private Socket listener = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private IPEndPoint IPEndPoint = new(IPAddress.Any, port);
private ConcurrentDictionary<string, Socket> _sockets = [];
private byte[]? data;
 
public async Task Start()
{
    listener.Bind(IPEndPoint);
    listener.Listen();
    await Console.Out.WriteLineAsync("Сервер запущен. Ожидание подключений. . .");
}
 
public async Task GetConnectionsAsync()
{
    while(true)
    {
        var tcpClient = await listener.AcceptAsync();
 
        await Console.Out.WriteLineAsync($"Установлено соединение с {tcpClient.RemoteEndPoint}");
 
        await ReceiveRequestAsync(tcpClient);
    }
}
 
public async Task ReceiveRequestAsync(Socket socket)
{
    data = new byte[512];
    int bytes = await socket.ReceiveAsync(data);
    if(bytes > 0)
    {
        string request = Encoding.UTF8.GetString(data, 0, bytes);
        var received = JsonSerializer.Deserialize<ClientObject>(request);
        if(_sockets.TryAdd(received.Id, socket))
        {
            if(string.IsNullOrEmpty(received.Message))
            {
                var response = $"Привет, {received.Name}";
                await SendMessageAsync(socket, response);
            }
        }
    }
}
 
private async Task SendMessageAsync(Socket socket, string message)
{
    data = Encoding.UTF8.GetBytes(message);
    using var stream = new NetworkStream(socket);
    await stream.WriteAsync(data.AsMemory(0, data.Length));
}
 
 
public async Task StartChatAsync()
{
    while(true)
    {
        foreach(var socket in _sockets.Values)
        {
            using(var stream = new NetworkStream(socket))
            {
                var data = new byte[512];
                int bytes = await socket.ReceiveAsync(data);
 
                if(data[0] != 0)
                {
                    var co = JsonSerializer.Deserialize<ClientObject>(Encoding.UTF8.GetString(data, 0, bytes));
                    
                    await ChattingAsync(co);
                }
                else
                {
                    break;
                }
            };
 
        }
    }
}
 
private async Task ChattingAsync(ClientObject client)
{
    byte[] data = new byte[512];
    var msg = string.Empty;
    msg = $"{client.Name}: {client.Message}";
 
    data = Encoding.UTF8.GetBytes(msg);
 
    if(_sockets.Count > 1)
    {
        foreach(var socket in _sockets.Values)
        {
            if(socket.Connected && _sockets[client.Id] != socket)
            {
                using var stream = new NetworkStream(socket);
                await stream.WriteAsync(data.AsMemory(0, data.Length));
            }
        }
    }
    else
    {
        data = Encoding.UTF8.GetBytes("Сообщение доставлено!");
        using var stream = new NetworkStream(_sockets[client.Id]);
        await stream.WriteAsync(data.AsMemory(0, data.Length));
    }
}
Клиент:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
        public string Id { get; } = Guid.NewGuid().ToString();
        public string Name { get; } = name;
        public string? Message { get; set; }
 
        [NonSerialized]
        private IPAddress _serverAddress = IPAddress.Parse("127.0.0.1");
        [NonSerialized]
        private int _serverPort = 8888;
        [NonSerialized]
        private static Socket _client = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        [NonSerialized]
        private byte[]? data;
        [NonSerialized]
        private (int, int) _lines = (0,3);
 
        public async Task Connect()
        {
            await _client.ConnectAsync(_serverAddress, _serverPort);
 
 
            data = new byte[512];
 
            data = Encoding.UTF8.GetBytes(ToText());
 
            await _client.SendAsync(data);
        }
 
        public async Task ReceiveResponseAsync()
        {
            data = new byte[512];
            int bytes = await _client.ReceiveAsync(data);
            if(bytes > 0)
            {
                string response = Encoding.UTF8.GetString(data, 0, bytes);
                var pos = Console.GetCursorPosition();
                Console.SetCursorPosition(_lines.Item1, _lines.Item2);
                await Console.Out.WriteLineAsync(response);
                _lines.Item2++;
                Console.SetCursorPosition(pos.Left, pos.Top);
            }
        }
 
        public async Task SendMessageAsync()
        {
            Console.Write("Введите сообщение: ");
            var pos = Console.GetCursorPosition();
            while(true)
            {
                byte[] data = new byte[512];
                string? msg = Console.ReadLine();
                Console.SetCursorPosition(pos.Left, pos.Top);
                Console.Write(MakeWhiteSpace(msg));
                Console.SetCursorPosition(pos.Left, pos.Top);
                if(!string.IsNullOrEmpty(msg))
                {
                    Message = msg;
 
                    data = Encoding.UTF8.GetBytes(ToText());
                    await _client.SendAsync(data);
                }
 
                int bytes = await _client.ReceiveAsync(data);
                if(bytes > 0)
                {
                    string response = Encoding.UTF8.GetString(data, 0, bytes);
                    Console.SetCursorPosition(_lines.Item1, _lines.Item2);
                    await Console.Out.WriteLineAsync(response);
                    _lines.Item2++;
                    Console.SetCursorPosition(pos.Left, pos.Top);
                }
            }
        }
 
        private string MakeWhiteSpace(string text)
        {
            string whiteSpaces = string.Empty;
            for(int i = 0; i < text.Length; i++)
            {
                whiteSpaces += " ";
            }
            return whiteSpaces;
        }
 
        private string ToText() => JsonSerializer.Serialize(this, typeof(Client));
В общем, подскажите, пожалуйста, как в этой ситуации можно убрать эту зависимость между отправкой и получением.

Сервер запускается так:
C#
1
2
3
4
5
6
7
8
9
Server server = new(8888);
 
await server.Start();
 
Thread thread = new(async () => await server.GetConnectionsAsync());
 
thread.Start();
 
await server.StartChatAsync();
Клиент так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
string name = string.Empty;
 
Console.WriteLine("Введите своё имя:");
 
name = Console.ReadLine();
if(!string.IsNullOrEmpty(name))
{
 
    Client client = new(name);
 
    await Task.Run(async () => await client.Connect());
 
    Thread t = new(async () => await client.ReceiveResponseAsync());
    t.Start();
    await client.SendMessageAsync();
}
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
19.01.2025, 16:15
Ответы с готовыми решениями:

Счетчик скачивания софта или есть идея, а реализовать в коде не могу пока
Как сделать так, что бы, нажав на гипер ссылку, открывалось два окна, одно в этом окне, а другое в новом... Просто я хочу чтобы при...

Не могу применить свое разрешение
сидел за компом знакомый . после чего не могу применить свое разрешение на вин 7. вроде показывает рекомендуемый 1360 х 768 а показывает...

Не могу установить свое название таблицы
Подскажите плз почему или что я делаю неверно! Смысл в следующем.Естественно я абсолютный новичок. Мне нужно создать таблицу для проекта...

5
 Аватар для IamRain
4694 / 2702 / 734
Регистрация: 02.08.2011
Сообщений: 7,233
19.01.2025, 16:41
Цитата Сообщение от modomaximarerum Посмотреть сообщение
Не могу получить сообщение, пока не отправлю своё.
Ну так код работает, сначала получает сообщение клиента, потом отвечает:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public async Task ReceiveRequestAsync(Socket socket)
{
    data = new byte[512];
   // сначала получить от сокета
    int bytes = await socket.ReceiveAsync(data);
    if(bytes > 0)
    {
        string request = Encoding.UTF8.GetString(data, 0, bytes);
        var received = JsonSerializer.Deserialize<ClientObject>(request);
        if(_sockets.TryAdd(received.Id, socket))
        {
            if(string.IsNullOrEmpty(received.Message))
            {
                // затем ответить собеседнику
                var response = $"Привет, {received.Name}";
                await SendMessageAsync(socket, response);
            }
        }
    }
}
Код крайне ненадежный - где гарантия, что все сообщение читается за одну ReceiveAsync операцию?
Нету message framing-а у вас.

Добавлено через 3 минуты
И смысл в json-е отправлять? С таким же успехом проще подключить SignalR и намного быстрее напишите ваш чатик.
Тот же gRPC приблизительно в 6 раз быстрее json (сам не проверял, это все от ByteByteGo инфа).
Проще вам взять protobuf и кодировать именно в нем, если уж в сокеты полезли, тогда их использование будет оправдано.
0
5 / 2 / 3
Регистрация: 20.10.2017
Сообщений: 16
19.01.2025, 17:09  [ТС]
Честно говоря, о таком даже не думал. С этим методом - то понятно, что он ждёт отправки сообщения при подключении, просто метод StartChatAsync, мне казалось, будет сразу забирать отправленное одним сообщение и раздавать его всем остальным, ведь сообщение "сообщение доставлено" он сразу отправляет, может, там можно что-то исправить или надо вообще по-другому это делать? Может у меня сообщения слишком короткие, всё вроде читается целиком. Или это связано не с длиной? И второе: что такое message framing?
0
5 / 2 / 3
Регистрация: 20.10.2017
Сообщений: 16
19.01.2025, 17:09  [ТС]
Честно говоря, о таком даже не думал. С этим методом - то понятно, что он ждёт отправки сообщения при подключении, просто метод StartChatAsync, мне казалось, будет сразу забирать отправленное одним сообщение и раздавать его всем остальным, ведь сообщение "сообщение доставлено" он сразу отправляет, может, там можно что-то исправить или надо вообще по-другому это делать? Может у меня сообщения слишком короткие, всё вроде читается целиком. Или это связано не с длиной? И второе: что такое message framing?
0
 Аватар для IamRain
4694 / 2702 / 734
Регистрация: 02.08.2011
Сообщений: 7,233
19.01.2025, 18:26
Цитата Сообщение от modomaximarerum Посмотреть сообщение
что такое message framing?
Это логика по логическому разделению сообщений друг от друга. Бывает двух видов:
- Length prefix-based (на основе длины сообщения, когда длина каждого сообщения - известная величина)
Перед каждый сообщением отправляется его длина(в байтах), и только затем само сообщение
- Delimiter-based - на основе разделителя между сообщениями (когда размеры сообщений заранее не известны, в http так работает, насколько я знаю)

Цитата Сообщение от modomaximarerum Посмотреть сообщение
просто метод StartChatAsync, мне казалось, будет сразу забирать отправленное одним сообщение и раздавать его всем остальным,
Тут аналогично:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public async Task StartChatAsync()
{
    while(true)
    {
        foreach(var socket in _sockets.Values)
        {
            using(var stream = new NetworkStream(socket))
            {
                var data = new byte[512];
 
                // получить данные с этого хоста, зачем - непонятно
                int bytes = await socket.ReceiveAsync(data);
 
                if(data[0] != 0)
                {
                    var co = JsonSerializer.Deserialize<ClientObject>(Encoding.UTF8.GetString(data, 0, bytes));                    
                    // и только потом продолжить общение
                    await ChattingAsync(co);
                }
                else
                {
                    break;
                }
            };
 
        }
    }
}
Вам нужно четко определить порядок действий на каждой стороне - это называется протоколом.
Тогда будет уже проще понимать, что надо писать и зачем.

Добавлено через 14 минут
Цитата Сообщение от modomaximarerum Посмотреть сообщение
Может у меня сообщения слишком короткие, всё вроде читается целиком
Возможно. Еще зависит от таймингов и от сети, Вот вы кодируете текстовые сообщения в UTF-8:
C#
1
2
3
4
5
6
public async Task ReceiveRequestAsync(Socket socket)
{
    data = new byte[512];
    int bytes = await socket.ReceiveAsync(data);
   ....
}
Однако, если имитировать какой-нибудь телеграм чат, где собеседника посетила муза и он начал расписывать, то в буфер 512 байт
Поместится только 256 UTF8 (два байта на один кириллический символ) символов.
И вот собеседник расписал на 300 символов, то тут нужно:
1. Как минимум прочитать дважды. (дважды вызвать socket.ReceiveAsync)
2. Положить это в массив бОльшего размера, так как в 512-байтовый массив уже такое сообщение не влезет.
1
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
19.01.2025, 23:20
.flag

Добавлено через 2 часа 38 минут
Окей, с чего бы начать...

1. Судя по всему вы пишете игру в Unity. Вам не нужно для сетевой логики наследоваться от базовых классов для скриптов и писать скорее как для консоли. Т.е. ваш сетевой код должен жить отдельной жизнью и через определенный контракта (делегаты, ивенты или внедрения зависимостей) взаимодействовать с предметами на сцене. Попытка засунуть это в базовый скрипт ничем хорошим не закончится.

2. Если у вас игра подразумевает реал-тайм взаимодействие (например бег персонажа), то сразу копаем в сторону UDP. Проблема в том что TCP работает как неразрывный поток, поэтому лаги/задержки в сети тормозят весь трафик. Из-за этого TCP хорошо применим для каких-нибудь шахмат, но абсолютно недопустим например для шутера.

3. Работа с TCP для много клиентского случая подразумевает выделение подключения в сессию. Грубо говоря ваш код, где объявлен listener должен заниматься только тремя вещами:
- ожидать нового входящего подключения и выделять его в сессию на основе TcpClient
- собирать сессии в единый список, чтобы можно было взаимодействовать с ними
- убирать отключившихся клиентов из этого списка

ВАЖНО! Всё это будет происходить в многопоточной среде, поэтому обычный List<T> тут не применим. Либо придется всё делать хотя бы в lock сегментах, иначе вам грозит падение сервака.

4. Сессия подразумевает под собой четыре вещи:
- какую-то базовую инкапсулированную инфу о подключенном клиенте. Например после авторизации записываем его логин.
- поток/задачу, которая просто читает беспрерывно сообщения и отсылает их на обработку
- очередь(!) для отправки сообщений. Именно очередь, и причем потоко-безопасную т.к. отправить что-то клиенту может понадобиться из разных модулей
- уведомление о отключении. Т.е. Сессия должна информировать внешний код, что она "всё". Нужно для того же listener выше.

5. Как правильно заметил выше IamRain -- вам нужен протокол. Грубо говоря набор "сообщений", как они кодируются в байты и считываются обратно. В идеале -- использовать бинарную (де)сериализацию, т.к. она найболее быстрая и на выходе получаете минимальный объем трафика. Если говорить про тот же TCP, то там вообще вся работа сводиться к Stream, не разобравшись с ним будете страдать.


Ну и ошибки по вашему коду....
C#
1
while(true)
Это уровень goto. Нет даже break, код вечный по определению. Смотрим далее этот же цикл

C#
1
2
3
4
5
6
7
8
9
10
    while(true)
    {
// ждем новое подключение(!)
        var tcpClient = await listener.AcceptAsync();
// пишем лог  
        await Console.Out.WriteLineAsync($"Установлено соединение с {tcpClient.RemoteEndPoint}");
// выполняем один раз чтение и отправку ответа 
        await ReceiveRequestAsync(tcpClient);
// возращаемся в начало цикла
    }
Т.е. у вам дождалось подключения, считало, отправило и повисло на ожидании нового подключения. Новый подключенный клиент не выделен в отдельную задачу.

C#
1
var received = JsonSerializer.Deserialize<ClientObject>(request);
Выглядит как лютый трындец. У вас должен быть отдельный class MessageType { public string Prp1 {get; set;} ... } который содержит сообщение, а не весь клиент пихать.

Также вам никто не гарантирует что сообщение прийдет единым куском. В теории может прилететь хоть по одному(!) байту. Т.е. отправили "{ text: "привет!" }", а пришло "{ text: "при". В итоге ваше десериализация падает. Вспоминаем что выше вам писали про протокол.

C#
1
if(_sockets.TryAdd(received.Id, socket))
Добавление происходит только после успешного получения сообщения. А должно происходить сразу после Accept.

C#
1
2
3
4
5
6
private async Task SendMessageAsync(Socket socket, string message)
{
    data = Encoding.UTF8.GetBytes(message);
    using var stream = new NetworkStream(socket);
    await stream.WriteAsync(data.AsMemory(0, data.Length));
}
Технически код не смертельный, но логически -- мрак. Мне нужно знать что конструктор NetworkStream не привязывается к сокету, и по закрытию потока не грохнется и сам сокет. Ну и пересоздавать поток каждый раз -- плохо.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
public async Task StartChatAsync()
{
//..
        foreach(var socket in _sockets.Values)
        {
            using(var stream = new NetworkStream(socket))
            {
                var data = new byte[512];
                int bytes = await socket.ReceiveAsync(data);
//..
            };
 
        }
Тоже какой-то трындец. Во-первых каждый раз пересоздаете поток и перевыделяете буфер. Во-вторых (и главное) пока у вас первый в списке не пришлёт сообщение, все остальные не будут считаны. Этого кода StartChatAsync вообще не должно существовать (в таком виде точно), т.к. чтение сообщения от каждого клиента -- отдельная независимая задача.

C#
1
2
3
4
5
        public async Task Connect()
        {
            await _client.ConnectAsync(_serverAddress, _serverPort);
            data = new byte[512];
            data = Encoding.UTF8.GetBytes(ToText());
создали массив и тут же потеряли на него сылку, т.к. GetBytes создает НОВЫЙ массив. Можно просто data = GetBytes(), без new перед этим.

C#
1
2
3
4
        public async Task ReceiveResponseAsync()
        {
            data = new byte[512];
            int bytes = await _client.ReceiveAsync(data);
Используете тот же буфер, куда записываете сообщение для отправки. А если в момент отправки у вас будет прием? Делайте разные независимые буферы.

C#
1
2
int bytes = await _client.ReceiveAsync(data);
            if(bytes > 0)
Ноль реакции на закрытие сокета со стороны сервера.



------------------------------------------------------------------
В общем, тут переписывать приблизительно 90% кода. Если вы пишете по какомму-то гайду весь этот ужас -- гайд плохой.

Добавлено через 7 минут
Цитата Сообщение от IamRain Посмотреть сообщение
Это логика по логическому разделению сообщений друг от друга. Бывает двух видов:
Есть подход через (десериализацию) -- просто пытаемся считать очередной блок/сообщение из Stream. В большинстве методах (де)сериализации сообщений позволяет не заморачиваться с указанием начала и конца. Также там можно спокойно пихать разные типы сообщений, и автоматически определять их на приёме (что вообще бомба).

Но есть проблема с тем что обычно при попытки считать очередное сообщение из закрытого потока приводит к ошибке. Можно игнорировать по catch, но в идеале нужно писать wrapper над Stream, который будет определять что в потоке появились новые данные и только тогда пинать чтение данных.

Это если говорить о практической реализации, понятное дело что (де)сериализация подразумевает что внутри как-то указывается размер данных, просто нам уже про это не нужно заморачиваться.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
19.01.2025, 23:20
Помогаю со студенческими работами здесь

Не могу найти свое приложение на wp в магазине
Приложение полностью прошло проверку и на сайте Microsoft developer center пишет что приложение загружено в магазин, однако при поиске не...

Не могу установить своё приложение из google play
Здравствуйте, такая проблема. Залил приложение на Google Play, хочу установить, но мне говорится, что приложение не совместимо с моим...

Могу ли я на свое железо поставить винчестер 4 тб? Мама - ASUS 5 ke
Здравствуйте. Подскажет ли мне чайнику могу ли я на свое железо поставить винчестер 4 тб? Мама - ASUS 5 ke . Чипсет Р35, сокет LGA 775?...

Рекурсия: читать числа в массив, пока не будет обнаружена единица или пока размер массива не станет 20 элементов
Написать рекурсивную функцию, которая считывает вводимые с клавиатуры числа в массив до тех пор, пока не будет обнаружена единица или...

На одно свое устройство не могу установить MYSQL Command Line 8.0
Полагаю, суть в этом. Windows 7, 32 bit. Мне нужно установить Visual studio? откуда и какие версии(ю)?


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
6
Ответ Создать тему
Новые блоги и статьи
Переходник USB-CAN-GPIO
Eddy_Em 20.03.2026
Достаточно давно на работе возникла необходимость в переходнике CAN-USB с гальваноразвязкой, оный и был разработан. Однако, все меня терзала совесть, что аж 48-ногий МК используется так тупо: просто. . .
Оттенки серого
Argus19 18.03.2026
Оттенки серого Нашёл в интернете 3 прекрасных модуля: Модуль класса открытия диалога открытия/ сохранения файла на Win32 API; Модуль класса быстрого перекодирования цветного изображения в оттенки. . .
SDL3 для Desktop (MinGW): Рисуем цветные прямоугольники с помощью рисовальщика SDL3 на Си и C++
8Observer8 17.03.2026
Содержание блога Финальные проекты на Си и на C++: finish-rectangles-sdl3-c. zip finish-rectangles-sdl3-cpp. zip
Символические и жёсткие ссылки в Linux.
algri14 15.03.2026
Существует два типа ссылок — символические и жёсткие. Ссылка в Linux — это запись в каталоге, которая может указывать либо на inode «файла-ИСТОЧНИКА», тогда это будет «жёсткая ссылка» (hard link),. . .
[Owen Logic] Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора
ФедосеевПавел 14.03.2026
Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора ВВЕДЕНИЕ Выполняя задание на управление насосной группой заполнения резервуара,. . .
делаю науч статью по влиянию грибов на сукцессию
anaschu 13.03.2026
прикрепляю статью
SDL3 для Desktop (MinGW): Создаём пустое окно с нуля для 2D-графики на SDL3, Си и C++
8Observer8 10.03.2026
Содержание блога Финальные проекты на Си и на C++: hello-sdl3-c. zip hello-sdl3-cpp. zip Результат:
Установка CMake и MinGW 13.1 для сборки С и C++ приложений из консоли и из Qt Creator в EXE
8Observer8 10.03.2026
Содержание блога MinGW - это коллекция инструментов для сборки приложений в EXE. CMake - это система сборки приложений. Здесь описаны базовые шаги для старта программирования с помощью CMake и. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru