|
|
Регистрация Восстановить пароль |
Повторная активизация e-mail |
Регистрация | Задать вопрос |
Заплачу за решение |
Новые сообщения |
Сообщения за день |
Расширенный поиск |
Правила |
Всё прочитано |
|
|
Опции темы | Поиск в этой теме |
10.02.2011, 10:11 | #1 |
Старожил
Регистрация: 21.03.2009
Сообщений: 2,193
|
Общая структура/архитектура клиент-серверных приложений
Доброго времени суток!
Хотелось бы задать знающим людям несколько вопросов по поводу логической структуры и некоторых аспектов построения клиент-серверных приложений. История вопроса Решил я недавно изучить программирование сокетов Беркли, в частности, с применением Winsock. Теорию взял из статьи в разделе "Общие вопросы C/C++", а также из книги "C++ глазами хакера", плюс гугл и msdn. Начал с создания пары консольных приложений в MS VS 2008, благополучно добился взаимодействия по алгоритму "сервер начинает слушать порт" - "клиент подсоединяется к порту" - "клиент отсылает серверу сообщение" - "сервер принимает сообщение, пишет клиенту в ответ" - "клиент принимает ответ, отключается" - "сервер продолжает висеть пока не будет закрыт или пока клиент еще раз не постучится". Естественно, этого мне оказалось мало, я решил написать далее более-менее приличный чат. Поскольку отличительной чертой чата является возможность отправки любой стороной любого количества сообщений подряд не дожидаясь ответа, то вариант с консольным приложением оказывается слишком сложным, поскольку пока пользователь печатает сообщение, от собеседника может прийти еще парочка, и должны будут отобразиться, к тому же еще и перед недопечатанным сообщением. Реально, но слишком геморно для ситуации, когда этот аспект второстепенен. Так что в результате я переполз на оконные приложения. Поскольку MFC я недолюбливаю, а с WinAPI знаком не настолько тесно (да и опять же - слишком много кода, не относящегося к изучаемой теме), то выбрал C++ Builder 6 (старый, да и тоже не без греха, но тем не менее). Ну и тут полезли очередные проблемы и вопросы. Немного про структуру моего варианта клиент-сервера Первое, что я сделал, убедившись в работоспособности всех этих функций типа bind, listen, send, recv и т.д. - это написал функции-обертки для основных функций работы с сокетами, которые проверяли бы успешность выполнения стандартных функций и в случае ошибки генерировали бы исключения с соответствующими сообщениями (класс Sockets::SockErr - прямой наследник std::runtime_error, даже не привносящий ничего нового). Затем, уже для варианта оконного приложения, написал (точнее, все еще пишу) классы клиента и сервера, которые в конструкторах и функциях-членах типа ConnectToServer осуществляют захват и инициализацию ресурсов (типа загрузки библиотеки Winsock, создания сокетов, привязки к портам и т.п.), а в деструкторах и функциях типа Disconnect - освобождение ресурсов. Создание сервера, и соединение с ним происходит по нажатию соответствующих кнопочек, отсоединение - по повторному нажатию. Протокол передачи данных - самый примитивный. Сообщение представляет собой, как известно, цепочку байт (точнее, массив char, что в самом общем случае не есть одно и то же), первый из которых - это код команды. 1 - "клиент оповещает сервер о подключении к нему", 2 - "клиент оповещает сервер об отключении", 3 - "текстовое сообщение". Байты 2-5 - это по сути число типа int (по-хорошему, строго четырехбитовое), означающее длину текстового сообщения (имеет значение только для команды с кодом 3). Последующие необязательные байты - текстовое сообщение указанной длины (в случае команд 1 и 2 вообще ни одного дополнительного байта). И наконец, собственно, вопросы: 1. Наиболее насущный - как правильно организовать возможность непрерывного получения сообщений с возможностью отправки в любой момент? Как возможный вариант - создавать для приема сообщений отдельный поток (thread), который будет внутри себя в цикле (вплоть до получения команды отключения) с помощью блокирующего сокета принимать сообщение, разбирает его по указанному протоколу, добавляет новую строку в поле чата; отправка сообщений происходит по событию нажатия на кнопку. Альтернативный вариант - использовать неблокирующие сокеты, и тогда, как я понял, нужно будет использовать событие TApplication::OnMessage. Собственно, ни один из этих вариантов еще не пробовал, потому подскажите, будут ли они работать, и: а) Будет ли блокирующий сокет адекватно работать в своем потоке, пока работает основная программа (сильно подозреваю, что будет)? б) Не начнется ли драка за разделяемые ресурсы при использовании потоков (а именно - порт, окно вывода сообщений, еще какие-нибудь)? в) разное.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта Тема на форуме, посвященная ему же |
10.02.2011, 10:13 | #2 |
Старожил
Регистрация: 21.03.2009
Сообщений: 2,193
|
2. Как правильно освобождать ресурсы? В частности, как проконтролировать корректное освобождение в случае ошибки только тех ресурсов, которые были захвачены? Мой вариант:
Код:
Не вполне уверен в корректности этого варианта, потому прошу меня просветить на этот счет. 3. На данный момент все это безобразие я могу тестировать только на локальном хосте, дальнейшей же экспансии мешает роутер, который гнусно сжирает предоставляемый провайдером белый айпишник и не дает моему компу самолично слушать порт. Насколько решабельна такая проблема? Что-то я краем уха слышал в интернете про проброску портов, но пока слабо себе ее представляю. Пока вроде бы все, что вспомнил из интересующего меня. P.S. И, да, первой теме в разделе - быть!
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта Тема на форуме, посвященная ему же |
10.02.2011, 10:39 | #3 |
Старожил
Регистрация: 03.01.2011
Сообщений: 2,508
|
> создавать для приема сообщений отдельный поток (thread), который будет внутри себя в цикле (вплоть до получения команды отключения) с помощью блокирующего сокета принимать сообщение
вариант. Только перед recv() и прочими блокирующими вызовами желательно делать select(), иначе ваш поток прийдётся вырубать через TerminateThread() (ну или посылать самому себе 1 байт, чтобы разблокировать вызов). Обычно используют пул потоков, чтобы не создавать новый поток каждый раз при подключении нового клиента. > Альтернативный вариант - использовать неблокирующие сокеты, и тогда, как я понял, нужно будет использовать событие TApplication::OnMessage. Для высокопроизводительных серверов по-хорошему делают привязку OVERLAPPED вызовов WinSock2 с IOCP. Такая связка отлично масштабируется на любое количество ядер, установленных на машине (вариант выше мастабируется не так легко). > а) Будет ли блокирующий сокет адекватно работать в своем потоке, пока работает основная программа (сильно подозреваю, что будет)? а че ему не работать > б) Не начнется ли драка за разделяемые ресурсы при использовании потоков (а именно - порт, окно вывода сообщений, еще какие-нибудь)? зависит от рук программиста. > и не дает моему компу самолично слушать порт роутер никак не может помешать слушать порт. Только другая прога, открывшая этот порт до вас может этот сделать. > Насколько решабельна такая проблема? Решается элементарно, смотрите настройки роутера в разделе NAT/Firewall/SUA. Все, что ему нужно указать — это номер (или диапазон номеров) порта и IP адрес локальной машины, на которую отправлять все запросы, приходящие на этот порт.
"Когда приходит положенное время, человек перестаёт играть в пинбол. Только и всего."
Последний раз редактировалось veniside; 10.02.2011 в 14:11. |
10.02.2011, 14:13 | #4 | |||
Старожил
Регистрация: 28.01.2009
Сообщений: 21,000
|
Цитата:
Цитата:
Цитата:
а WSACleanup делаем тогда когда больше не нужен WS, в принципе в самом конце приложения этого достаточно. Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
Программа делает то что написал программист, а не то что он хотел. Функции/утилиты ждут в параметрах то что им надо, а не то что вы хотите. |
|||
10.02.2011, 14:17 | #5 |
Старожил
Регистрация: 03.01.2011
Сообщений: 2,508
|
> легко может, если он не пропускает подключения извне
не, ну понятно. Имелось в виду, что порт на машине будет открыт. А кто к нему будет иметь доступ (только локалка, или локалка и внешние соединения), это уже другой вопрос.
"Когда приходит положенное время, человек перестаёт играть в пинбол. Только и всего."
|
11.02.2011, 13:07 | #6 | ||||||
Старожил
Регистрация: 21.03.2009
Сообщений: 2,193
|
Цитата:
Цитата:
Цитата:
Цитата:
Ну а по поводу того же RichEdit - к примеру, пришло сообщение от сервера, клиент начинает его выводить в окошко RichEdit'а, и в этот же момент пользователь ввел в другое окно клиента сообщение и нажал кнопку "Отправить". Соответственно, в окошке должно одновременно появиться и это сообщение. Чувствую, что у меня паранойя, но все-таки хочу уточнить. Цитата:
Меня же больше интересует аварийное поведение программы. К примеру, если Winsock не была нормально загружена, в конструкторе будет сгенерировано исключение, объект клиента не будет создан, деструктор не будет вызван, WSACleanup не вызовется (и не надо). Собственно, не вполне уверен насчет того, нужно ли вызывать и shutdown, и closesocket равноправно в случаях, когда сокет был только создан при помощи socket(), и когда он был, к примеру, привязан к порту, или подсоединен к серверу. Цитата:
Кстати, заметил тут, что я скопипастил код Disconnect в середину кода деструктора. Правильно оно выглядит так: Код:
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта Тема на форуме, посвященная ему же |
||||||
11.02.2011, 17:18 | #7 |
Старожил
Регистрация: 03.01.2011
Сообщений: 2,508
|
Вобще, для небольшого числа клиентов и блокирующих сокетов с пулами потоков понаписано, я думаю, необозримое число примеров и библиотек, вот первый попавшийся. Тысячи их. Если аккуратно писать, за пару дней получается кусок кода, на котором удобно оттачивать свои знания по синхронизации потоков и построения различных механизмов нотификации событий. Тут главное вовремя остановиться, и не начать изобретать свой протокол )
> К примеру, возможны ли одновременно прием и отправка данных через один и тот же клиентский сокет конечно, так общение с веб-сервером работает, например. Мы ему GET, а он нам файл )
"Когда приходит положенное время, человек перестаёт играть в пинбол. Только и всего."
|
01.03.2011, 16:00 | #8 |
Старожил
Регистрация: 21.03.2009
Сообщений: 2,193
|
Вот потихоньку вроде бы почти разобрался со всем этим делом, однако, есть одна небольшая проблема.
Вообще, общая концепция в итоге такая: сервер по нажатию кнопки "Создать подключение" создает серверный сокет, делает ему bind-listen и запускает поток, в котором сокет будет принимать (accept) подключения от клиентов (на настоящий момент - от одного-единственного). Поток в цикле проверяет, есть ли желающий подключиться клиент (через select с тайм-аутом), если есть - вызывает accept, создает поток, в котором будут приниматься данные от клиента. В этом потоке в свою очередь в цикле select'ом с тайм-аутом проверяется, пришли ли от клиента данные, и если да, то принимаются и соответствующим данным выводятся. Цикл с accept'ом длится, пока не будет передана информация о том, что соединение надо закрыть (нажатие кнопки -> вызов функции CloseConnection -> установка флага типа MTBool -> цикл, проверяющий значение флага, завершается); цикл с recv - до того же условия, или пока клиент не пришлет сообщение, что он отключается. По завершению циклов функции потоков завершаются. MTBool - это флаг для безопасной работы через потоки. Что называется, проще показать, чем объяснять: Код:
Фрагменты кода: Код:
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта Тема на форуме, посвященная ему же |
01.03.2011, 16:54 | #9 |
Старожил
Регистрация: 03.01.2011
Сообщений: 2,508
|
> прямо перед этим появляется MessageBox, объявляющий о создании потока приема сообщений от клиента
это который внутри RecvInThread()? Покажите тогда уже заодно и код RecvInThread(). Да, и при создании Код:
"Когда приходит положенное время, человек перестаёт играть в пинбол. Только и всего."
|
01.03.2011, 17:28 | #10 | |
Старожил
Регистрация: 21.03.2009
Сообщений: 2,193
|
Код RecvInThread:
Код:
это функция, получающая через сокет массив байт и разделяющая впоследствии их на код команды, длину сообщения и собственно сообщение (как было написано ранее). Хотя по идее этот код не должен влиять, т.к. проблему вызывает даже простое нажатие кнопок "Создать подключение"/"Закрыть подключение" (которые вообще-то есть всего одна кнопка с меняющейся надписью, проверяющая наличие подключения на данный момент). Цитата:
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта Тема на форуме, посвященная ему же |
|
|
Опции темы | Поиск в этой теме |
Похожие темы | ||||
Тема | Автор | Раздел | Ответов | Последнее сообщение |
Создание клиент/серверных БД | ti_sweta | Помощь студентам | 23 | 09.11.2010 15:00 |
Есть общая структура программы,как написать функции к ней? | Aleksandr_Yanov | Общие вопросы C/C++ | 0 | 13.06.2010 16:53 |
Ищу книгу Андрей Шкрыль "Разработка клиент-серверных приложений в Delphi" | virus_t | Свободное общение | 9 | 11.08.2009 21:42 |
Вопрос по нагрузке клиент/серверных программ. | stonix | Работа с сетью в Delphi | 2 | 23.12.2007 23:15 |