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