Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Букатов, Заставной. Часть2

.pdf
Скачиваний:
28
Добавлен:
13.02.2015
Размер:
390.02 Кб
Скачать

отличия касаются реализации взаимодействия с клиентом после выполнения функции accept(). Указанное взаимодействие осуществляется функцией PerformConnection(). Этой функции необходимо передать два значения – сокет,

через который выполняется взаимодействие с клиентом, и адрес клиента.

Последнее значение для взаимодействия с клиентом по существу не нужно, и в этом примере используется для иллюстрации «оберточных» классов при передаче потоковой функции нескольких значений через один параметр.

Изменения кода серверной части, конечно, не влияет на код клиента,

остающийся неизменным.

1.class PARAMS

2.{

3.

public:

 

4.

SOCKET newsock;

 

5.

sockaddr_inaaddr;

 

6.

PARAMS( SOCKET

newsock, sockaddr_in aaddr )

7.

{

 

8.

this->newsock = newsock;

9.

this->aaddr = aaddr;

10.}

11.};

12.int _tmain(int argc, _TCHAR* argv[])

13.{

14.// Создание и инициализация сокета

15.// ……………………………………………

16.wprintf( L"Сетевая служба активна на %S:%d\n",

inet_ntoa( baddr.sin_addr ), htons( baddr.sin_port ) );

17.for( ; ; )

18.{

11

19.

//

………………………………………………………

20.

 

SOCKET newsock = accept( sock, (sockaddr*)&aaddr, &len );

21.

 

if( newsock == INVALID_SOCKET )

22.

 

{

23.

//

Обработка ошибки

24.

//

…………………………………………………………………

25.

 

}

26.

//

Однопотоковый вариант

27.

//

PerformConnection( new PARAMS( newsock, aaddr ) );

28.

//

Многопоковый вариант

29.

 

HANDLE hThread = CreateThread( NULL, 0,

 

 

PerformConnection,

 

 

(LPVOID)(new PARAMS(newsock, aaddr )),

 

 

CREATE_SUSPENDED, NULL );

30.

 

if( hThread == NULL )

31.

 

{

32.

//

Обработка ошибки

33.

//

………………………………………………

34.

 

}

35.

 

ResumeThread( hThread );

36.}

37.

//

Завершение

38.

//

…………………………………………………

39.}

40.DWORD WINAPI PerformConnection( LPVOID lpar )

41.{

42.PARAMS* par = (PARAMS*)lpar;

43.

SOCKET newsock = par->newsock;

44.sockaddr_inaaddr = par->aaddr;

12

45.wprintf( L"Соединение с %S:%d открыто\n",

inet_ntoa( aaddr.sin_addr ), htons( aaddr.sin_port ) );

46.// Обмен данными с клиентом

47.// ……………………………………………………

48.}

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

Строки 1-11. Определение простейшего оберточного класса.

Строка 27. Обработка запроса клиента в однопотоковом режиме

(закомментировано).

Строка 29. Создание нового потока для обработки запроса клиента. Параметры newsock и aaddr упаковываются в объект класса PARAMS, указатель на этот объект преобразуется к стандартному нетипизированному указателю LPVOID дл соответствия описания функции CreateThread(). Параметр CREATE_SUSPENDED определяет, что новый поток будет создан, но не будет исполняться до явной активизации.

Строка 35. Явная активизация потока при помощи вызова функции

ResumeThread().

Строки 42-44. Преобразование указателя LPVOID к PARAMS* и распаковка параметров.

И однопотоковый, и многопотоковый варианты сервера с функциональной точки зрения одинаковым образом взаимодействуют с каждым отдельно взятым клиентом. Однако каждый следующий клиент при взаимодействии с однопотоковым сервером должен ожидать окончания взаимодействия сервера с предыдущим клиентом, тогда как при взаимодействии с многопотоковым вариантом сеанс будет начинаться немедленно; это будет видно, например, по появлению сообщения строки 45. Таким образом, сервер сможет обрабатывать одновременно множество клиентских подключений.

13

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

поскольку будут возрастать расходы на выполнение переключения между потоками. Далее, неконтролируемое порождение потоков делает сервер потенциальным объектом для реализации сетевых атак типа «отказ в обслуживании». Поэтому более безопасным решением является ограничение максимального количества одновременных подключений, обрабатываемых сервером, и создание пула потоков, выделяемых очередному подключению,

количество которых должно соответствовать реальным вычислительным ресурсам системы.

Теперь рассмотрим еще один пример применения многопотокововой архитектуры, использующий совместный доступ двух потоков к одному сокету.

Такой вариант может быть полезен для создания программ типа чат на основе консольных приложений. Напомним, что при однопотоковой архитектуре при разработке клиента и сервера необходимо согласовать, какая сторона начинает первой пересылать данные. Вторая сторона, соответственно, должна находиться в ожидании приема; его выполнение блокируется при вызове recv(). Чтобы избавиться от подобной асимметрии, необходимо создать два потока, один из которых выполняет запись в сокет, а второй ожидает приема данных.

Ниже приведен фрагмент серверной части и две функции для потоков;

клиентская часть отличается от серверной, как и ранее, только способом созданием сокета. Потоковые функции одинаковы для обеих частей.

Приведенный пример так же иллюстрирует применение функций работы со строками Unicode, которые позволяют использовать кириллические символы в консольных приложениях.

14

1.int _tmain(int argc, _TCHAR* argv[])

2.{

3. setlocale( LC_ALL, "Russian_Russia.866" );

4.// Инициализация сокета

5. // ……………………………………………………

6. SOCKET clientsock = accept( sock, (sockaddr*)&addrclient, &size );

7. HANDLE handles[2];

8. handles[0]= CreateThread( NULL, 0, Reading, (LPVOID)&clientsock, 0, NULL );

9. handles[1] = CreateThread( NULL, 0, Writing, (LPVOID)&clientsock, 0, NULL );

10.WaitForMultipleObjects( 2, handles, FALSE, INFINITE );

11.CloseHandle( handles[0] );

12.CloseHandle( handles[1] );

13. // Завершение

14.// ……………………………………………………

15.}

16.DWORD WINAPI Reading( LPVOID pinfo )

17.{

18.wprintf( L"Чтение...\n" );

19.SOCKET sock = *(SOCKET*)pinfo;

20.for( ;; )

21.{

22.

wchar_t

Mess[MessSize];

23.

wmemset( Mess, 0, MessSize );

24.

if( recv( sock, (char*)Mess, MessSize*sizeof(wchar_t), 0 )

 

== - 1 )

25.

ExitThread( 1 );

26.

wprintf( L"Сообщение: %s\n", Mess );

15

27.

if( wcscmp( Mess, L"всего" ) == 0 )

28.

ExitThread( 1 );

29.}

30.}

31.DWORD WINAPI Writing( LPVOID pinfo )

32.{

33.wprintf( L"Запись...\n" );

34.SOCKET sock = *(SOCKET*)pinfo;

35.for( ;; )

36.{

37.

wchar_t

Mess[MessSize];

38.

wmemset( Mess, 0, MessSize );

39.

wprintf( L">" );

40.

wscanf( L"%100ls", Mess );

41.

send( sock, (char*)Mess, wcslen( Mess )*sizeof( wchar_t ), 0 );

42.

if( wcscmp( Mess, L"всего" ) == 0 )

43.

ExitThread( 1 );

44.}

45.}

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

Строка 3. Инициализация консоли для поддержки кириллических букв.

Строки 8, 9. Создание и запуск двух потоков для чтения и записи в сокет. Пятым параметром передается значение 0, что приводит к немедленному запуску созданного потока, в отличие от потоков, созданных с использованием флага

CREATE_SUSPENDED.

Строка 10. Функция WaitForMultipleObjects(), по аналогии с функций WaitForSingleObject(), останавливает выполнение потока программы, из которого она была вызвана (в данном случае – из основного потока), пока не

16

будет завершен хотя бы один из потоков, хендлы которых помещены в передаваемый массив.

Строки 19, 34. Доступ к значению, соответствующего сокету, через переданный потоку указатель.

Строки 22, 37. Создание массива wchar_t для хранения строки в многобайтовой или Unicode-кодировке.

Строки 23, 38. Функция wmemset(), являющаяся аналогом функции memset(),

используется для заполнение массива указанными символами.

Строки 26, 39. Функция wprintf() является аналогом функции printf(); обратите внимание на использование префикса “L” перед литеральной строкой.

Строки 24, 41. При пересылке и получении массива wchar_t функциями send() и recv() необходимо выполнять преобразование указателя к типу char*, и

правильно указывать длину массива в байтах.

Строки 25, 28, 43. Функция ExitThread() выполняет завершение выполнения текущего потока. Следует иметь в виду, что удаление потока происходит не при вызове этой функции, а при закрытии последнего хендла данного потока.

Строка 27, 41, 42. Функции wcscmp() и wcslen() используется соответственно для сравнения строк с символами многобайтовой кодировки и вычисления длины

(в количестве символов, а не байтов).

Строка 40. Функция wscanf() предназаначена для ввода (аналог функции scanf()). Следует обратить внимание на используемый форматер L"%100ls" для ввода строки.

Сеанс прекращается, когда одна из сторон «набирает» последовательность символов "всего" или получает эту последовательность из сокета. В этом случае один из потоков завершается, что приводит к возврату из функции WaitForMultipleObjects() и принудительному закрытию обоих хендлов и,

соответственно, завершения все еще активного второго потока. Аналогично обрабатывается ошибка при функции recv().

17

2. Передача через сокеты пакетов различных протоколов, работающих

над протоколом IP

Помимо рассмотренных к предыдущих разделах сокетов, использующих для пересылки данных протоколы TCP и UDP, можно создавать так же так называемые ROW-сокеты [5], которые позволяют пересылать через них пакеты других протоколов, использующих IP-протокол в качестве транспортного.

При создании Row-сокета вторым параметром функции socket() является значение SOCK_RAW, а третий параметр определяет тип протокола. Значение третьего параметра будет помещаться в заголовке IP-пакета всех отправляемых через этот сокет пакетов, и эти пакеты получателями будут восприниматься именно как пакеты этого типа. Аналогично, через созданный таким образом сокет можно получать и все входящие пакеты данного протокола.

Значение третьего параметра socket() должно соответствовать одному из поддерживаемых данным хостом сетевых протоколов; проверить, поддерживает ли данный хост некоторый протокол можно при помощи вызова функции

getprotobynumber().

Для оправления и приема данных через Row-сокет обычно используются функции sendto() и recvfrom(). Как и при работе с дейтаграмными соединениями,

при отправке необходимо указать IP-адрес получателя и буфер с пересылаемыми данными. Этот буфер перед выполнением операции sendto() должен содержать данные в формате, соответствующем формату пакета используемого протокола,

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

При получении данных при возврате из recvfrom() буфер содержит весь входящий IP-пакет вместе с его заголовком. Таким образом, в начале буфера располагается IP-заголовок, имеющий длину 20 байт, а непосредственно за ним – вложенный пакет формата, соответствующего используемому протоколу.

18

Ниже приведен пример, иллюстрирующей использование ROW-сокетов на примере программы – упрощенной версии утилиты ping [3,1]. Эта утилита,

напомним, используется для проверки, доступен ли хост в сети, используя средства ICMP-протокола [3,1,4].

Работа рассматриваемой программы, приводимой фрагментарно, состоит в создании Row-сокета, формировании и отправке опрашиваемому хосту ICMP-

пакета требуемой структуры, получении ответа и его последующем анализе.

1.typedef struct

2.{

3.

u_char vihl;

4.

u_char tos;

5.

short tlength;

6.

short id;

7.

short flagoff;

8.

u_char ttl;

9.

u_char protocol;

10.u_short checksum;

11.struct in_addr ipSrc;

12.struct in_addr ipDst;

13.} IP_Header;

14.typedef struct

15.{

16.unsigned char type;

17.unsigned char code;

18.unsigned short checksum;

19.unsigned short id;

20.unsigned short sequence;

21.unsigned long timestamp;

19

22.char body[32];

23.} ICMP_Header;

24.int _tmain(int argc, _TCHAR* argv[])

25.{

26.// ……………………………………………

27.SOCKET sock = socket(AF_INET,SOCK_RAW, IPPROTO_ICMP);

28.sockaddr_in addr;

29.addr.sin_family = AF_INET;

30.addr.sin_addr.s_addr = inet_addr( ipaddr );

31.ICMP_Header icmp_req;

32.memset( &icmp_req, 0, sizeof( icmp_req) );

33.icmp_req.type = 8;

34.icmp_req.code = 0;

35.icmp_req.id = 11111;

36.icmp_req.checksum = 0;

37.icmp_req.sequence = 0;

38.icmp_req.timestamp = GetTickCount();

39.icmp_req.checksum = checksum( icmp_req, sizeof(ICMP_Header));

40.int ttl;

41.int srrl = sizeof( ttl );

42.getsockopt( sock, IPPROTO_IP, IP_TTL, (char*)&ttl, &srrl);

43.setsockopt( sock, IPPROTO_IP, IP_TTL, (соnst char*)&ttl,

sizeof( ttl ));

44.rc = sendto(sock, (const char*)&icmp_req, sizeof(ICMP_Header), 0,

(sockaddr*)&addr, sizeof(addr));

45.char buf[1024];

46.rc = recvfrom(sock, buf, sizeof(icmp_rec), 0, NULL, NULL );

47.

IP_Header ipheader = *(IP_Header*)buf;

48.wprintf( L"Протокол: %d\n",ipheader.protocol );

20