Comments 35
Вам интересно выкладывать один и тот же шлак с теми же грамматическими ошибками на оба ресурса? Почему?
+6
Недостаток такой реализации — это однопоточность сервера. Если хоть какое-то соединение с клиентом «подвиснет» и будет переполнен его исходящий буфер, то сервер просто зависнет на вызове write.
Решения данной проблемы:
1. Создание отдельного потока для каждого клиента. Недостаток: необходимость понимания механизмов синхронизации, низкая эффективность при большом числе соединений. Новичкам будет тяжело разобраться.
2. Использование select также и для записи в сокеты. Думаю, в данном случае самое простое решение.
3. Использование более высокоуровневых языков с поддержкой асинхронного программирования.
Это не так: read() и write() эквивалентны recv() и send() с нулевыми флагами.
Ну и ещё один момент: код очень разрозненный, фрагментарный. Новичку была бы полезна ссылка на репозиторий.
Решения данной проблемы:
1. Создание отдельного потока для каждого клиента. Недостаток: необходимость понимания механизмов синхронизации, низкая эффективность при большом числе соединений. Новичкам будет тяжело разобраться.
2. Использование select также и для записи в сокеты. Думаю, в данном случае самое простое решение.
3. Использование более высокоуровневых языков с поддержкой асинхронного программирования.
Вообще есть разные функции отправки и приема, к примеру send и recv вместе с сообщением шлют еще и флаг подтверждения, а функции read и write не требуют подтверждения, то есть сообщение может потерять байты при отправке и это не будет зафиксировано.
Это не так: read() и write() эквивалентны recv() и send() с нулевыми флагами.
Ну и ещё один момент: код очень разрозненный, фрагментарный. Новичку была бы полезна ссылка на репозиторий.
0
Ну зачем другие языки? А чем вам boost::asio не подходит?
+2
Если хоть какое-то соединение с клиентом «подвиснет» и будет переполнен его исходящий буфер, то сервер просто зависнет на вызове writeЕсли будет переполнен исходящий буфер, то можно:
— расширить буфер
— отбросить новые данные
— вообще дропнуть соединение
Но городить по потоку для отправки данных каждому клиенту — это какой-то новый уровень неэффективности. Спасибо, что хоть не форки.
Я вообще не понимаю зачем тут хотя бы один поток — отправкой данных занимается ОС, перекинуть данные из буфера по нынешним временам не стоит вообще ничего.
0
А ещё можно подождать с отправкой данных до тех пор, пока буфер не освободится, что в подавляющем большинстве случаев обычно и делают. Если данные — это большой файл, то передачей данных придётся заниматься нам самим, а не лезть в системные буферы и уж тем более отказывать клиенту в обслуживании, потому что захотел слишком большой файл.
И основных решения тут два:
1. Каждому соединению — по потоку, а то и по два (чтение и запись), используя блокирующие операции чтения и записи данных.
2. Использование select, epoll, libevent, неблокирующих операций и прочих радостей жизни.
Второй способ сильно эффективнее, но первый сильно проще в реализации, поэтому с него и нужно начинать изучение.
Кстати, Apache под Windows пользовался первым способом — создавал 100 потоков и обрабатывал каждый запрос в своём потоке.
И основных решения тут два:
1. Каждому соединению — по потоку, а то и по два (чтение и запись), используя блокирующие операции чтения и записи данных.
2. Использование select, epoll, libevent, неблокирующих операций и прочих радостей жизни.
Второй способ сильно эффективнее, но первый сильно проще в реализации, поэтому с него и нужно начинать изучение.
Кстати, Apache под Windows пользовался первым способом — создавал 100 потоков и обрабатывал каждый запрос в своём потоке.
0
И мы прекрасно знаем что с этого вышло… Nginx наше все, а все потому что начинал не с того что проще, а сразу с правильных архитектурных решений.
0
Во времена, когда апач начинал развиваться, правильных архитектурных решений попросту ещё не существовало. Например, во времена релиза Apache 2.0 не было ни libevent, ни epoll, только select, который обладал очень плохой масштабируемостью.
+2
До Nginx был еще 0W, а до него thttpd, и они прекрасно работали.
+1
Чем же первый способ проще? Неблокирующие операции элементарны. Нет, я серьезно не понимаю как можно назвать работу с потоками более простой.
+1
Первый способ проще тем, что кода значительно меньше и он линеен. Меньше кода — меньше ошибок и времени для разработки.
Вместо двух состояний (ошибка, успешное завершение) при использовании неблокирующих сокетов добавляется третье — «выполнение операции без блокирования невозможно», при котором нужно прервать действие и перейти в режим ожидания готовности. А это дополнительная проверка после каждой операции с сокетами.
Как следствие — при использовании неблокирующих операций придётся вместо одной функции писать целый класс — машину состояний, сохраняющей точку входа. Желающие помучиться с отладкой, но писать в первом стиле могут ещё использовать нити.
Вместо двух состояний (ошибка, успешное завершение) при использовании неблокирующих сокетов добавляется третье — «выполнение операции без блокирования невозможно», при котором нужно прервать действие и перейти в режим ожидания готовности. А это дополнительная проверка после каждой операции с сокетами.
Как следствие — при использовании неблокирующих операций придётся вместо одной функции писать целый класс — машину состояний, сохраняющей точку входа. Желающие помучиться с отладкой, но писать в первом стиле могут ещё использовать нити.
0
Я не совсем понимаю о чем вы говорите.
Обычно в подобных задачах крутится цикл в котором принимаются новые соединения, закрываются соединения с ошибками, принимаются данные (сколько есть или сколько лезет) и отправляются данные (сколько есть или сколько лезет). Как бы все. Проще некуда. Хотите ООП — оформляете все это в базовый класс и в потомках меняете read / send, наворачивая любой уровень абстракции. Хотите — можете все это поместить в один тред. Или в десять. Но тред на клиента мне встречался лишь там, где подразумевалась долгая работа с конкретным клиентом при околонулевом взаимодействии между клиентами, короче там, где треды пришли на замену форкам ради роста производительности.
Обычно в подобных задачах крутится цикл в котором принимаются новые соединения, закрываются соединения с ошибками, принимаются данные (сколько есть или сколько лезет) и отправляются данные (сколько есть или сколько лезет). Как бы все. Проще некуда. Хотите ООП — оформляете все это в базовый класс и в потомках меняете read / send, наворачивая любой уровень абстракции. Хотите — можете все это поместить в один тред. Или в десять. Но тред на клиента мне встречался лишь там, где подразумевалась долгая работа с конкретным клиентом при околонулевом взаимодействии между клиентами, короче там, где треды пришли на замену форкам ради роста производительности.
0
Я говорю о том, что код с потоками проще и логичнее. А в современных языках программирования он легко становится асинхронным, поэтому проблемы с написанием эффективного кода не вижу.
Ну да, крутится цикл, всё просто и понятно, но ровно до тех пор, пока идёт обмен короткими сообщениями, как в обучающем примере. Усложнение протокола приведёт к значительному повышению сложности кода.
Например, как прочитать пакет с длиной, передаваемой в начале пакета?
Вариант 1:
1. Прочитать 4 байта одной функцией. *
2. Прочитать N байт.
* например обёрткой над recv — bool recvall(...)
Вариант 2:
1. Проверить состояние.
2. Если состояние == 1, тогда продолжать читать буфер длины, по завершении перейти в состояние 2.
3. Если состояние == 2, тогда продолжать читать буфер пакета, по завершении перейти в состояние 3.
Собственно, сложность тут только в грамотном написании машины состояния.
Ну да, крутится цикл, всё просто и понятно, но ровно до тех пор, пока идёт обмен короткими сообщениями, как в обучающем примере. Усложнение протокола приведёт к значительному повышению сложности кода.
Например, как прочитать пакет с длиной, передаваемой в начале пакета?
Вариант 1:
1. Прочитать 4 байта одной функцией. *
2. Прочитать N байт.
* например обёрткой над recv — bool recvall(...)
Вариант 2:
1. Проверить состояние.
2. Если состояние == 1, тогда продолжать читать буфер длины, по завершении перейти в состояние 2.
3. Если состояние == 2, тогда продолжать читать буфер пакета, по завершении перейти в состояние 3.
Собственно, сложность тут только в грамотном написании машины состояния.
0
Вариант 1. Сразу нет. Категорически. Потому как TCP/IP — не пакетная, а потоковая передача данных. И то, что клиент вам отправил длину и данные не означает, что вы получите их в одном или двух пакетах — может быть получите и в трех, и в десяти. Так что по-любому это все падает в промежуточный буфер, от которого «откусываются» куски по мере обработки данных.
Это ведет нас к варианту 3, т.е. как бы сделал я:
После каждого чтения вызывать метод типа on_data, который в базовом классе — пустышка.
На основе базового класса сделать класс, в котором on_data разбирается с длиной и забирает ровно столько из буфера, сколько надо. При этом в новом классе не будет или почти не будет низкоуровневой работы с сокетами — всем этим заведует базовый класс.
Собственно данный подход не привязан к конкретному языку — на языках высокого уровня будет ровно то же самое.
Это ведет нас к варианту 3, т.е. как бы сделал я:
После каждого чтения вызывать метод типа on_data, который в базовом классе — пустышка.
На основе базового класса сделать класс, в котором on_data разбирается с длиной и забирает ровно столько из буфера, сколько надо. При этом в новом классе не будет или почти не будет низкоуровневой работы с сокетами — всем этим заведует базовый класс.
Собственно данный подход не привязан к конкретному языку — на языках высокого уровня будет ровно то же самое.
0
Именно поэтому я и указал recvall — функцию, которая заполняет буфер определённым числом байт из потока вне зависимости от того, сколько пакетов фактически придёт.
0
Тогда, какая в итоге разница? Все равно нужно читать все, что дают. А разбирать прочитанное будет уже не базовый класс (я еще раз пушну тему ООП).
Потоки же создают массу проблем при взаимодействии данных друг с другом. Т.е. когда у вас общение с каждым клиентом четко изолировано от других клиентов, то почему бы и нет? А если у вас клиенты как-то обмениваются данными, то уже придется страдать. Поэтому я вполне понимаю, когда непосредственно чтение и отправку сырых данных реализуют в отдельном потоке (но всех для всех клиентов вообще) или когда используются дополнительные потоки для доп. обработки данных (например, у меня как-то было 16 потоков — по числу ядер — занимавшихся gzip-ом). Но решительно не понимаю зачем вы хотите замучить несчастный шедулер, впарив ему 10к потоков (например, при чате на 10к клиентов).
Потоки же создают массу проблем при взаимодействии данных друг с другом. Т.е. когда у вас общение с каждым клиентом четко изолировано от других клиентов, то почему бы и нет? А если у вас клиенты как-то обмениваются данными, то уже придется страдать. Поэтому я вполне понимаю, когда непосредственно чтение и отправку сырых данных реализуют в отдельном потоке (но всех для всех клиентов вообще) или когда используются дополнительные потоки для доп. обработки данных (например, у меня как-то было 16 потоков — по числу ядер — занимавшихся gzip-ом). Но решительно не понимаю зачем вы хотите замучить несчастный шедулер, впарив ему 10к потоков (например, при чате на 10к клиентов).
0
Да не хочу я никого мучить. Я же тоже согласен, что нет нужды плодить потоки, которые 99.99% времени будут находиться в состоянии ожидания и отъедать ресурсы системы.
Я просто считаю, что реализация через метод on_data менее удобна, чем линейная, особенно если нет взаимодействия между соединениями.
Я просто считаю, что реализация через метод on_data менее удобна, чем линейная, особенно если нет взаимодействия между соединениями.
0
> работа на должность программиста с\с++
Надеюсь, вас не взяли.
>c++
От плюсов тут только cin/cout.
Надеюсь, вас не взяли.
>c++
От плюсов тут только cin/cout.
+5
Спасибо конечно. Радуйтесь на работу меня не взяли и поэтому теперь я буду ходить у вашего двора и трести у вашей жены денег на пиво, потому что ваш коментарий меня расстроил. Нет чтобы подсказать так лучше засодить.
-12
Подсказываю.
1) Не пишите статьи на темы в которых не разбираетесь. Ничего хорошего из этого не выйдет. Неужели это не было понятно после вашей предыдущей статьи?
2) Если хотите работу c++ сетевого разработчика — потрудитесь потратить время (несколько месяцев а не два дня) на то чтоб в деталях разобраться в сети на низком уровне, попробовать существующие фреймворки (boost asio / libevent / etc.); написать несколько простых сетевых приложений (чат, http сервер, etc.) и выложить их на гитхаб. После этого моментально работу найдёте.
1) Не пишите статьи на темы в которых не разбираетесь. Ничего хорошего из этого не выйдет. Неужели это не было понятно после вашей предыдущей статьи?
2) Если хотите работу c++ сетевого разработчика — потрудитесь потратить время (несколько месяцев а не два дня) на то чтоб в деталях разобраться в сети на низком уровне, попробовать существующие фреймворки (boost asio / libevent / etc.); написать несколько простых сетевых приложений (чат, http сервер, etc.) и выложить их на гитхаб. После этого моментально работу найдёте.
+2
Из личных наблюдений.
На хабре за неидеальный код на C/C++ больно бьют, за упоминание делфи тоже,
Для волшебных пендалей есть Тостер.
На хабре за неидеальный код на C/C++ больно бьют, за упоминание делфи тоже,
Для волшебных пендалей есть Тостер.
+2
В вашем двухстрочном комментарии 8 пунктуационных и грамматических ошибок. Теперь промасштабируйте это число на размер статьи и вам, возможно, все станет понятно. После этого можно переходить к разбору кода.
+3
Зануда заметит, что перегрузка функций (read() и write()), как будто, тоже С++… но С++, действительно, предполагает совсем иной стиль кодинга.
Лично мне все стало ясно после авторского «c\c++». Сразу видна степень (не-)знакомства и с «linux», и с программированием.
Автор, видимо, считает нормальным для соискателя на должность программиста изучать язык накануне собеседования. Его ждет много открытий.
Лично мне все стало ясно после авторского «c\c++». Сразу видна степень (не-)знакомства и с «linux», и с программированием.
Автор, видимо, считает нормальным для соискателя на должность программиста изучать язык накануне собеседования. Его ждет много открытий.
0
Всё-таки эта статья по тематике подходит на хабр, а не на гиктаймс.
0
Если интересны сеть и многопоточность в C++, гляньте в сторону вот этого курса
0
Sign up to leave a comment.
Клиент-сервер под linux на c++ общение клиентов «все со всеми» с использованием потоков