Как стать автором
Обновить

Комментарии 35

Вам интересно выкладывать один и тот же шлак с теми же грамматическими ошибками на оба ресурса? Почему?
Не один и тот же. На GT статья «Просто код простой задачи» смотрится гораздо шлаковее, чем на Хабре
Недостаток такой реализации — это однопоточность сервера. Если хоть какое-то соединение с клиентом «подвиснет» и будет переполнен его исходящий буфер, то сервер просто зависнет на вызове write.

Решения данной проблемы:
1. Создание отдельного потока для каждого клиента. Недостаток: необходимость понимания механизмов синхронизации, низкая эффективность при большом числе соединений. Новичкам будет тяжело разобраться.
2. Использование select также и для записи в сокеты. Думаю, в данном случае самое простое решение.
3. Использование более высокоуровневых языков с поддержкой асинхронного программирования.

Вообще есть разные функции отправки и приема, к примеру send и recv вместе с сообщением шлют еще и флаг подтверждения, а функции read и write не требуют подтверждения, то есть сообщение может потерять байты при отправке и это не будет зафиксировано.

Это не так: read() и write() эквивалентны recv() и send() с нулевыми флагами.

Ну и ещё один момент: код очень разрозненный, фрагментарный. Новичку была бы полезна ссылка на репозиторий.
Ну зачем другие языки? А чем вам boost::asio не подходит?
В C++ пока ещё нет continuations, из-за чего многие конструкции асинхронного программирования выглядят неочевидно и монструозно.
Если хоть какое-то соединение с клиентом «подвиснет» и будет переполнен его исходящий буфер, то сервер просто зависнет на вызове write
Если будет переполнен исходящий буфер, то можно:
— расширить буфер
— отбросить новые данные
— вообще дропнуть соединение

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

Я вообще не понимаю зачем тут хотя бы один поток — отправкой данных занимается ОС, перекинуть данные из буфера по нынешним временам не стоит вообще ничего.
А ещё можно подождать с отправкой данных до тех пор, пока буфер не освободится, что в подавляющем большинстве случаев обычно и делают. Если данные — это большой файл, то передачей данных придётся заниматься нам самим, а не лезть в системные буферы и уж тем более отказывать клиенту в обслуживании, потому что захотел слишком большой файл.

И основных решения тут два:
1. Каждому соединению — по потоку, а то и по два (чтение и запись), используя блокирующие операции чтения и записи данных.
2. Использование select, epoll, libevent, неблокирующих операций и прочих радостей жизни.

Второй способ сильно эффективнее, но первый сильно проще в реализации, поэтому с него и нужно начинать изучение.

Кстати, Apache под Windows пользовался первым способом — создавал 100 потоков и обрабатывал каждый запрос в своём потоке.
И мы прекрасно знаем что с этого вышло… Nginx наше все, а все потому что начинал не с того что проще, а сразу с правильных архитектурных решений.
Во времена, когда апач начинал развиваться, правильных архитектурных решений попросту ещё не существовало. Например, во времена релиза Apache 2.0 не было ни libevent, ни epoll, только select, который обладал очень плохой масштабируемостью.
Ну, справдливости ради, тогда уже был overlapped io, правда не поддерживался в win 9x.
До Nginx был еще 0W, а до него thttpd, и они прекрасно работали.
Чем же первый способ проще? Неблокирующие операции элементарны. Нет, я серьезно не понимаю как можно назвать работу с потоками более простой.
Первый способ проще тем, что кода значительно меньше и он линеен. Меньше кода — меньше ошибок и времени для разработки.

Вместо двух состояний (ошибка, успешное завершение) при использовании неблокирующих сокетов добавляется третье — «выполнение операции без блокирования невозможно», при котором нужно прервать действие и перейти в режим ожидания готовности. А это дополнительная проверка после каждой операции с сокетами.

Как следствие — при использовании неблокирующих операций придётся вместо одной функции писать целый класс — машину состояний, сохраняющей точку входа. Желающие помучиться с отладкой, но писать в первом стиле могут ещё использовать нити.
Я не совсем понимаю о чем вы говорите.
Обычно в подобных задачах крутится цикл в котором принимаются новые соединения, закрываются соединения с ошибками, принимаются данные (сколько есть или сколько лезет) и отправляются данные (сколько есть или сколько лезет). Как бы все. Проще некуда. Хотите ООП — оформляете все это в базовый класс и в потомках меняете read / send, наворачивая любой уровень абстракции. Хотите — можете все это поместить в один тред. Или в десять. Но тред на клиента мне встречался лишь там, где подразумевалась долгая работа с конкретным клиентом при околонулевом взаимодействии между клиентами, короче там, где треды пришли на замену форкам ради роста производительности.
Я говорю о том, что код с потоками проще и логичнее. А в современных языках программирования он легко становится асинхронным, поэтому проблемы с написанием эффективного кода не вижу.

Ну да, крутится цикл, всё просто и понятно, но ровно до тех пор, пока идёт обмен короткими сообщениями, как в обучающем примере. Усложнение протокола приведёт к значительному повышению сложности кода.

Например, как прочитать пакет с длиной, передаваемой в начале пакета?

Вариант 1:
1. Прочитать 4 байта одной функцией. *
2. Прочитать N байт.
* например обёрткой над recv — bool recvall(...)

Вариант 2:
1. Проверить состояние.
2. Если состояние == 1, тогда продолжать читать буфер длины, по завершении перейти в состояние 2.
3. Если состояние == 2, тогда продолжать читать буфер пакета, по завершении перейти в состояние 3.

Собственно, сложность тут только в грамотном написании машины состояния.
Вариант 1. Сразу нет. Категорически. Потому как TCP/IP — не пакетная, а потоковая передача данных. И то, что клиент вам отправил длину и данные не означает, что вы получите их в одном или двух пакетах — может быть получите и в трех, и в десяти. Так что по-любому это все падает в промежуточный буфер, от которого «откусываются» куски по мере обработки данных.

Это ведет нас к варианту 3, т.е. как бы сделал я:

После каждого чтения вызывать метод типа on_data, который в базовом классе — пустышка.
На основе базового класса сделать класс, в котором on_data разбирается с длиной и забирает ровно столько из буфера, сколько надо. При этом в новом классе не будет или почти не будет низкоуровневой работы с сокетами — всем этим заведует базовый класс.

Собственно данный подход не привязан к конкретному языку — на языках высокого уровня будет ровно то же самое.
Именно поэтому я и указал recvall — функцию, которая заполняет буфер определённым числом байт из потока вне зависимости от того, сколько пакетов фактически придёт.
Тогда, какая в итоге разница? Все равно нужно читать все, что дают. А разбирать прочитанное будет уже не базовый класс (я еще раз пушну тему ООП).

Потоки же создают массу проблем при взаимодействии данных друг с другом. Т.е. когда у вас общение с каждым клиентом четко изолировано от других клиентов, то почему бы и нет? А если у вас клиенты как-то обмениваются данными, то уже придется страдать. Поэтому я вполне понимаю, когда непосредственно чтение и отправку сырых данных реализуют в отдельном потоке (но всех для всех клиентов вообще) или когда используются дополнительные потоки для доп. обработки данных (например, у меня как-то было 16 потоков — по числу ядер — занимавшихся gzip-ом). Но решительно не понимаю зачем вы хотите замучить несчастный шедулер, впарив ему 10к потоков (например, при чате на 10к клиентов).
Да не хочу я никого мучить. Я же тоже согласен, что нет нужды плодить потоки, которые 99.99% времени будут находиться в состоянии ожидания и отъедать ресурсы системы.

Я просто считаю, что реализация через метод on_data менее удобна, чем линейная, особенно если нет взаимодействия между соединениями.
> работа на должность программиста с\с++
Надеюсь, вас не взяли.
>c++
От плюсов тут только cin/cout.
Спасибо конечно. Радуйтесь на работу меня не взяли и поэтому теперь я буду ходить у вашего двора и трести у вашей жены денег на пиво, потому что ваш коментарий меня расстроил. Нет чтобы подсказать так лучше засодить.
Подсказываю.
1) Не пишите статьи на темы в которых не разбираетесь. Ничего хорошего из этого не выйдет. Неужели это не было понятно после вашей предыдущей статьи?
2) Если хотите работу c++ сетевого разработчика — потрудитесь потратить время (несколько месяцев а не два дня) на то чтоб в деталях разобраться в сети на низком уровне, попробовать существующие фреймворки (boost asio / libevent / etc.); написать несколько простых сетевых приложений (чат, http сервер, etc.) и выложить их на гитхаб. После этого моментально работу найдёте.
Да-да, за пару месяцев опыта и тренировочные шаблоны чата/http-сервера — так прям и оторвут. Только таких и ищут, указывая «минимум 2 года опыта» и подразумевая, что возьмут и с 1 годом, но такого, который бесценен.
Из личных наблюдений.
На хабре за неидеальный код на C/C++ больно бьют, за упоминание делфи тоже,
Для волшебных пендалей есть Тостер.
Вы написали код уровня «hello world». Примеров клиент-серверного взаимодействия в сети полно. Их много и подробно разбирали в литературе. Например, у меня вот такая книжка дома валялась — обратите внимание на год.
А не могли бы вы подсказать, где в сети полно этих примеров?
Иначе когда ищешь, натыкаешься только на подобные статьи, которые все поливают грязью.
Второй день ищу что-то, и это единственный материал с кусками кода, все остальные ресурсы — общие рассуждения, что и как работает.
В вашем двухстрочном комментарии 8 пунктуационных и грамматических ошибок. Теперь промасштабируйте это число на размер статьи и вам, возможно, все станет понятно. После этого можно переходить к разбору кода.
Зануда заметит, что перегрузка функций (read() и write()), как будто, тоже С++… но С++, действительно, предполагает совсем иной стиль кодинга.

Лично мне все стало ясно после авторского «c\c++». Сразу видна степень (не-)знакомства и с «linux», и с программированием.

Автор, видимо, считает нормальным для соискателя на должность программиста изучать язык накануне собеседования. Его ждет много открытий.
По тематике она больше подходит на StackOverflow или sql.ru, в форуме на тему «Как мне написать клиент-сервер на С++»
Конкретно эта статья вообще никуда не подходит.
Скорее на «Вопросы Мэйл.ру»
Она там была:) Ее быстро прикрыли.
Если интересны сеть и многопоточность в C++, гляньте в сторону вот этого курса
Ссылка не прицепилась. Это «Многопоточное программирование на C++» от mail.ru на stepic.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории