• На проекте открылась регистрация только для продавцов. Для обычных пользователей будет открыта позже. Подробнее.
    P.S. Не надо скидывать ссылки на форумы, где у вас ноль сообщений. Подобные заявки будут отклонятся.

Литература Сетевое программирование

ddd

(•̪̀●́)=o/̵͇̿̿/'̿̿ ̿ ̿̿
Команда форума
WebOwner
WebVoice
вЫкладЫваем статьи и собственнЫе наработки :)

ЯП: С++
Статья: Пишем PROXY-SERVER
Источник: www.uinc.ru
Введение
Было время, когда мне нужно было написать простейшей одноконнектовый прокси, даже без интерфейса, но состоящий из двух половинок, которые соединяются протоколом SPX, а не TCP. Я столкнулся с тем, что в том небольшом количестве примеров работы с WinSock, что у меня были, было столько ненужного мне мусора, что это затрудняло понимание самого принципа. А примеров организации многоконнектовости у меня вообще не было. Поэтому в данной статье я постараюсь как можно проще объяснить принцип работы прокси, но я не буду объяснять все с нуля. Если вы хотите понять принцип работы асинхронных неблокирующих сокетов в Windows и их отличия от стандартных синхронных, для начала прочтите документ "Синхронные и асинхронные сокеты в Windows". А если вы вообще не знакомы с сетевым программированием, отложите не надолго эту статью и постигните основы. Здесь же я расскажу только о том, что действительно может быть непонятным читателю. В качестве примера рассмотрим программу, организующую прослушивание сокета и осуществляющую перенаправление данных на указанный IP:PORT. Правильнее было бы назвать это чем-то вроде "port map" или "port redirect". Самая лучшая документация для программиста - это листинг программы.
Для начала определимся с константами.
Какой локальный порт будем прослушивать:
Код:
#define IN_PORT 3128
Удаленный IP адрес.
Код:
#define OUT_IP "192.168.0.1"
Порт к которому будем подключаться.
Код:
#define OUT_PORT 3128
Решим, какое максимальное количество соединений мы будем поддерживать.
Код:
#define MAXCONN 1000
Объявим глобальные переменные.

Это будет буфер для принятых данных.
Код:
char  buf[MAX_DATA];
Слушающий сокет, на который будут коннектиться клиенты.
Код:
SOCKET hListenSockTCP;
Массив дескрипторов сокетов, полученных при соединении нашей программой с удаленным сервисом в ответ на подключение со стороны клиента. Дескриптор сокета с клиентской стороны и будет индексом.
Например: к нашей программе подключается клиент. После выполнения строки "currentsock = accept(hListenSockTCP,NULL,NULL);" в переменной currentsock типа SOCKET будет возращен дескриптор сокета. Он, например, может быть числом 5,6 и т.д., поэтому, сам дескриптор можно использовать в качестве индекса в массиве. Теперь в ответ на "пятое" соединение (в случае, когда currentsock=5) соединяемся с прокси, на который мы делаем перенаправление, и полученный дескриптор сохраняем sockets[5]. Это равносильно строке "sockets[5]=connect (sockets[nofsock], ". Как вы должны понимать, это не самый лучший метод. Но зато он самый простой и нам пока подойдет.
Код:
SOCKET  sockets[MAXCONN];
Начнем.

* Инициализация среды перед использованием WinSock:
Код:
WSADATA stWSADataTCPIP;
if(WSAStartup(0x0101,  &stWSADataTCPIP)) MessageBox(hwndMain,
"WSAStartup error !","NET  ERROR!!!",0);
* Заполним массив дескрипторов сокетов нулями (на всякий случай).
Код:
ZeroMemory(sockets,sizeof(sockets));
* Зарегистрируем класс и создадим окно. Получим hwndMain - дескриптор окна.

* Создадим сокет.
Код:
hListenSockTCP = socket (AF_INET,SOCK_STREAM,0);
* Заполним структуру SOCKADDR_IN, указав тип протокола(family) и порт, к которому будем "биндиться", и "привязываем" сокет.
Код:
SOCKADDR_IN  myaddrTCP;
myaddrTCP.sin_family = AF_INET;
myaddrTCP.sin_addr.s_addr =  htonl (INADDR_ANY);
myaddrTCP.sin_port = htons (IN_PORT);
bind(  hListenSockTCP,(LPSOCKADDR)&myaddrTCP, sizeof(struct sockaddr) );
* Запускаем сокет "на прослушку".
Код:
listen (hListenSockTCP, SOMAXCONN));
* Привязываем события FD_ACCEPT, FD_READ, FD_CLOSE сокета к главному окну программы.
Код:
WSAAsyncSelect  (hListenSockTCP,hwndMain,WM_ASYNC_CLIENTEVENT,
FD_ACCEPT|FD_READ|FD_CLOSE);
Это значит, что при попытке клиента подключиться к прослушиваемому сокету окну с дескрипторо hwndMain будет передаваться сообщение WM_ASYNC_CLIENTEVENT. Напомню, что функция обработки сообщений окна выглядит так - "LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)". В переменной wParam будет передан дескриптор сокета, в котором произошло событие. А какое именно - узнаем из lParam. Но кроме кода события в lParam еще находится код ошибки. Для извлечения их этого 4-х байтного числа (DWORD) двух слов (WORD) существуют два макроопределения - WSAGETSELECTERROR(lParam) и WSAGETSELECTEVENT(lParam).

* Процедура обработки сообщений.
Код:
.............
case WM_ASYNC_CLIENTEVENT:
Сообщения о событиях подключенных к клиенту сокетов...
Код:
currentsock = wParam;
именно так узнаем, какое событие с сокетом произошло
Код:
WSAEvent = WSAGETSELECTEVENT  (lParam); 
switch (WSAEvent)
{
Это сообщение приходит тогда, когда к нам хотят подключиться.
Код:
case FD_ACCEPT:
Разрешаем подключение клиента, и пытаемся теперь подключиться к нашему удаленному прокси.
Код:
ConnectToProxy(accept(hListenSockTCP,NULL,NULL));
Если это не удалось, закрываем соединение, которое только что мы позволили установить с нами клиенту. Второй параметр - SD_SEND (у меня - просто единица). Этим мы позволяем соединению спокойно закрыться. После этой команды с сокетом произойдет событие
Код:
"FD_CLOSE".
shutdown(currentsock,1);
return  0;

case FD_CLOSE :
Клиент по какой-либо причине хочет прервать соединение. Глушим
соединение с уд. прокси, которое мы установили в ответ на это
соединение.
Код:
shutdown(sockets[currentsock],1);
и закрываем сокет.
Код:
closesocket(currentsock);
return 0;

case FD_READ:
На сокет пришли данные. Берем от клиента, посылаем на сервер.
Код:
i=recv(currentsock, buf, MAX_DATA, 0);
send(sockets[currentsock],  buf, i, 0);
и отправляем...
Код:
return 0;
}
break;
Так же поступаем и с обработкой событий на сокетах, когда сам наш прокси является клиентом другого прокси, на который мы делаем перенаправление. Проще говоря, у нас в программе будут две группы сокетов.
1. Со стороны клиентов. Есть главный сокет - hListenSockTCP. К нему могут подключаться клиенты, каждый раз создавая новые виртуальные каналы, каждому из которых назначается свой дескриптор.
2. Со стороны оконечного прокси сервера (или сервиса). Наша программа будет каждый раз при необходимости создавать сокет и коннектиться на заданный IP:PORT. Каждый открытый виртуальный канал будет ответом на подсоединение со стороны клиентов.


Код:
case WM_ASYNC_PROXYEVENT:
Найдем соответствующий дескриптор в массиве.
Код:
for (i=0;i<MAXCONN;i++)
if (sockets[i] == wParam) {  currentsock=i; break; }
Теперь в currentsock - наше соединение с клиентом, а в
sockets[currentsock] - соответствующее ему соединение с удаленным
прокси.
Код:
WSAEvent = WSAGETSELECTEVENT (lParam);
switch  (WSAEvent)
{
Произошло подключение к удаленному хосту.
Код:
case FD_CONNECT  :
i=WSAGETSELECTERROR(lParam);
if (i!=0)
Если соединение не удалось, закроем уже установленное соединение с клиентом и сокет, который мы создали, пытаясь установить соединение с удаленным прокси.
Код:
{
shutdown(currentsock,1);
closesocket(sockets[currentsock]);
sockets[currentsock]=INVALID_SOCKET;
}
return  0;
Сервер нас отрубает...
Код:
case FD_CLOSE  :
shutdown(currentsock,1);
closesocket(sockets[currentsock]);
sockets[currentsock]=INVALID_SOCKET;
return  0;
Перенаправление данных клиенту.
Код:
case  FD_READ:
i=recv(sockets[currentsock], buf, MAX_DATA,  0);
send(currentsock,buf, i, 0);
return 0;
}
break;
А теперь рассмотрим функцию соединения с прокси.


Код:
void ConnectToProxy(SOCKET  nofsock)
{
Заполняем структуру - IP, с которым мы будем связываться, порт, тип
протокола.
Код:
SOCKADDR_IN rmaddr;
rmaddr.sin_family =  AF_INET;
rmaddr.sin_addr.s_addr = inet_addr(OUT_IP);
rmaddr.sin_port =  htons (OUT_PORT);
Создание сокета TCP.
Код:
sockets[nofsock] = socket  (AF_INET,SOCK_STREAM,0);
Привязываем события FD_READ и FD_CLOSE с этим сокетом к главному окну приложения сообщением WM_ASYNC_PROXYEVENT. Тем самым мы переводим сокет в не блокирующий режим.
Код:
WSAAsyncSelect  (sockets[numofsock],hwndMain,WM_ASYNC_PROXYEVENT,
FD_CONNECT|FD_READ|FD_CLOSE);
Пытаемся соединиться.
Код:
connect (sockets[nofsock], (struct sockaddr  *)&rmaddr,sizeof(rmaddr));
Результат функции connect() мы не проверяем, так как она завершится до того, как соединение будет установлено.
Код:
return;
}

Warning!
Итак, я показал основные функции прокси. Я специально в первом варианте не добавлял код для проверки ошибок, дабы упростить ядро и позволить проще понять принципы. Теперь я расскажу о тех проблемах, которые есть у нашего прокси.

1. После каждой функции Winsock необходимо получать код возврата и адекватно реагировать.
2. Функция приема данных и передача их дальше по цепочке у нас выглядит так:
Код:
i=recv(sockets[currentsock], buf, MAX_DATA, 0);
send(currentsock,buf,  i, 0);
Но на самом деле функция send() не гарантирует то, что данные будут посланы, а так же то, что будет послано именно это число байт, а не меньше. Если бы сокет был блокирующим, мы за это могли бы не переживать, так как программа была бы в ожидании того, когда весь объем данных будет послан. Чтобы избежать этих проблем, можно ожидать в обработчике сообщения FD_WRITE, которое говорит о том, что сокет готов к передаче данных.
3. Массив для хранения дескрипторов сокетов с индексированием другими дескрипторами сокетов - не самый правильный вариант. Представим, например, что у нас такая запись - "SOCKET sockets[MAXCONN]", а MAXCONN=1000. Но при большой нагрузке на сеть значение дескриптора может быть и больше 1000 - тогда будет очень большая проблема, которую тяжело будет исправить, если не догадываться о ее присутствии. Самым простым (но не лучшим) решением данной проблемы будет заведение заранее большого массива и проверкой каждого созданного сокета - кодом типа:


Код:
hsocket=accept(hListenSockTCP,NULL,NULL);
if(hsocket>MAXCONN)
{  
shutdown(hsocket);
close(hsocket);
}
4. Еще раз внимательно посмотрите на код, и, возможно, вы найдете еще что-то, что я упустил.
Ну что, вы разобрались и готовы к реализации полноценной программы? Тогда заходите сервер UInC в раздел проектов. Там вы найдете полнофункциональную реализацию такого port-mapper-а. Вместе с ним будет исходный код. В нем вы увидите обработку всех ошибок, ведение лога, обработка командной строки и многое другое.
(c) Copyright 2001. Украина, Запорожье. KMiNT21 (mailto:kmint21@mail.ru).
uinC Member
[c]uinC
 
Сверху