По этой теме с сети написано немало материалов, как простых, так и сложных, но даже среди такого обилия информации разобраться в предмете непросто. Если же нужно написать сервер, который может обслуживать множество одновременных подключений, работающий быстро и надёжно, задача сильно усложняется. Почему же вопрос так труден для освоения? Мне кажется, причины следующие.
1. Нужно хорошо ориентироваться в терминологии локальных сетей, многоуровневой модели OSI (которая и так достаточно запутана - многие технологии и протоколы относятся сразу к нескольким уровням) многообразии сетевых технологий, в методах адресации - чтобы подобрать оптимальные протоколы работы приложения. Однако, наиболее распространен, без всяких сомнений, TCP/IP.
2. На каждой платформе свои обёртки к сетевому IP. Например, в MFC (Visual Studio) это CSocket, в Delphi TSocket, TServerSocket, TClientSocket. Простые примеры из help или Интернета обычно работают нормально, но если надо что-то переделать - перехватить ошибки, организовать обработку множества одновременных подключений к серверу - обёртки только усложняют задачу, и хороший/понятный пример найти уже сложнее.
3. Качественное применение обёрток подразумевает Ваше хотя бы приблизительное знакомство с базовыми функциями и принципами работы библиотеки Windows Sockets - иначе будет трудно понять и применить многие свойства и методы обёрток (связанные, например, с блокирующими функциями Windows Sockets).
4. Если Вы решили отказаться от оберток в пользу прямого вызова функций Windows Sockets (хороший выбор!) - будьте готовы к тому, что потребуются хорошие знания по написанию многопоточных приложений, методам корректного выделения и освобождения памяти, функциям обратного вызова (callback), межпотоковым взаимодействиям и т. п. Всё это потому, что многие функции Windows Sockets блокируют ход выполнения программы.
5. Для написания Winsock-приложения можно использовать пять моделей управления вводом-выводом (select, WSAAsyncSelect, WSAEventSelect, перекрытый ввод-вывод и порты завершения). Разобраться в этих технологиях и выбрать для себя одну (или несколько) может оказаться непростым делом.
Из всего вышесказанного ясно, что у “новичка в чистом виде” практически никаких шансов написать что-то путное, которое будет работать хотя бы с самыми щадящими требованиями к надежности и быстродействию. Если Вы - стреляный воробей, любите C (не купились сомнительной лёгкостью Visual Basic и Delphi), и успели слегка попробовать и то, и это (с переменным успехом), но понимания в сетевом программировании ещё не достигли, хотите чего-то большего, неплохо бы прочитать хотя бы разок хорошую книжку по предмету. Лучшая из тех, что мне попадалась - “Программирование в сетях Windows” Э. Джонс, Д. Оланд, издательство Microsoft Press 2002 года - ни капли лишнего, максимально широко рассмотрена тема (в пределах разумного), много хороших работоспособных примеров (на CD-ROM). Самое оно. Прочитаете, и сразу полегчает, останется только написать программу. Вам пока только кажется, что все понятно, не обольщайтесь - придётся ещё не раз перечитывать в книге многие места.
Напоследок приведу по шагам алгоритм работы сервера, который может одновременно обслуживать множество клиентских подключений. Исходный код можно скачать по ссылкам [1, 2] (проекты Win32 Console Visual C++). Первая программа может соединять сессии telnet с COM-портом. Вторая - консольный сервер telnet.
Писать многопоточный сервер предлагаю в такой последовательности:
1. Нужно загрузить правильную версию библиотеки Winsock. Я пользовался версией 2.0, хотя уже существует версия 2.2. Делается это так:
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 0 );
err = WSAStartup( wVersionRequested, &wsaData );
Если err не равно 0, то произошла ошибка (err содержит код ошибки, который можно расшифровать подпрограммой WSA_Err_Decode). В случае ошибки завершаем программу.
2 [можно пропустить]. Проверяем версию загруженной библиотеки. Она должна равняться запрашиваемой.
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 0 )
{
WSACleanup( );
WLog ("Couldn't find a usable WinSock DLL");
//далее идёт код, который обеспечивает завершение программы по ошибке
}
3 [можно пропустить]. Получение информации о том, сколько нужно памяти для опроса протоколов.
4 [можно пропустить]. WSAGetLastError() должна вернуть WSAENOBUFS.
5 [можно пропустить]. Выделение памяти для опроса протоколов.
6 [можно пропустить]. Загрузка в память информации о протоколах.
7 [можно пропустить]. Опрос всех доступных протоколов и поиск среди них нужного (в моей программе используется TCP/IP, и условие будет
(wsapi->iAddressFamily == AF_INET) && (wsapi->iProtocol == IPPROTO_TCP)).
8. Создание сокета, который будет принимать соединения вызовом WSASocket. Если WSASocket вернул INVALID_SOCKET, то получаем код ошибки по WSAGetLastError и завершаем программу. Код ошибки можно расшифровать подпрограммой WSA_Err_Decode.
9. Привязка сокета к адресу (bind). Для того, чтобы сервер был привязан ко всем адресам, используется константа INADDR_ANY. Если bind вернул не 0, то произошла ошибка. Получаем код ошибки по WSAGetLastError и завершаем программу. Код ошибки расшифровывается подпрограммой WSA_Err_Decode.
10. Перевод сокета в режим приёма входящих соединений или режим прослушивания (listen). На этом этапе задаётся максимальное число допустимых подключений. Если listen вернул не 0, то произошла ошибка. Получаем код ошибки по WSAGetLastError и завершаем программу. Код ошибки можно расшифровать подпрограммой WSA_Err_Decode.
11. Далее запускается бесконечный цикл ожидания клиентов. В теле этого цикла присутствует блокирующий метод accept - прокрутка цикла останавливается, пока не приконнектится любой клиент. Этот метод accept возвращает дескриптор сокета, через который можно работать с соединившимся клиентом. Через этот сокет и происходит дальнейший обмен данными с клиентом. Если наш сервер должен обслуживать несколько клиентов (а мы так и хотели), то в этом месте нужно запустить 2 потока, которые будут работать с сокетом клиента (один на приём, другой на передачу). 2 потока, а не один, нужны потому, что метод recv, который читает данные из сокета, блокирующий (есть и другие варианты, но я их рассматривать не буду, для этого есть спецлитература).
Вот и всё (?) что нужно знать, чтобы написать “всамделишный” сервер.
[Ссылки]
1. telnetc09.rar. 2. servcons01.rar. |