Когда пишут про сокетное программирование, конечно же, подразумевается TCP/IP. Вот тут мы и отступим от правил, поговорим про IPX/SPX.
Многие забыли, а то и не знали, про существование данных протоколов, но надо отдать ему должное, так как еще многие программные модули и системы используют его. Подробнее ознакомиться и прочитать о протоколе можно здесь http://www.sources.ru/protocols/bsp08/index.html. В данном материале дана низкоуровневая реализация протокола с примерами под ДОС.
Заранее хочу извиниться за допущенные мной аберрации и ошибки в статъе, т.к. эта статья - моя первая, надеюсь, не последняя.
А все начинается как всегда, а именно, с инициализации WINSOCK библиотеки, обработка ошибок упускается для упрощения кода:
Код:
#include <winsock.h> // прототипы функции библиотеки
#include <wsipx.h> // IPX/SPX структуры
#include <wsnwlink.h> // IPX/SPX структуры и константы для NT платформ
void main ()
{
WSADATA wsaData;
// версия winsock.dll библиотеки
WORD wVersionRequested = MAKEWORD( 1, 1 );
// инициализация winsock.dll
WSAStartup( wVersionRequested, &wsaData );
// работа с сокетами
WSACleanup( ); // не забываем сообщить системе, что мы закончили работу с winsock.dll
}
Ну и собственно сокет, тут я дам только кусок, отличный от нормальных сокетов:
WORD SPX_SOCKET = 0x5647
char localNetNum[IPX_NET_SIZE];
char localNodeNum[IPX_NODE_SIZE];
// открытие сокета
SOCKET spx_skt = socket (PF_IPX, SOCK_STREAM, NSPROTO_SPX));
// проверим если открылся
if (spx ==INVALID_SOCKET) MessageBox (NULL, Ошибка открытия сокета., "Error", MB_OK);
// структура для адресса нашего сокета
sockaddr_ipx addr_ipx;
/* вот так выглядит структура sockaddr_ipx в WSIPX.H
typedef struct sockaddr_ipx {
u_short sa_family;
u_char sa_netnum[4];
u_char sa_nodenum[6];
unsigned short sa_socket;
} SOCKADDR_IPX, *PSOCKADDR_IPX, FAR *LPSOCKADDR_IPX
*/
int sz = sizeof (addr_ipx);
// обнулим её
memset (&addr_ipx, 0, sz);
addr_ipx.sa_family = AF_IPX; // тип протокола
addr_ipx.sa_socket = htons(SPX_SOCKET); // номер сокета
// биндим сокет (привязываем его к номеру сокета)
bind(spx_skt, (sockaddr*) &addr_ipx, sz);
// узнаем наш адресс
getsockname (spx_skt, (sockaddr*) &addr_ipx, &sz);
// наш номер сети
memcpy (localNetNum, addr_ipx.sa_netnum, IPX_NET_SIZE);
// нашномер узла
memcpy (localNodeNum, addr_ipx.sa_nodenum, IPX_NODE_SIZE);
В остальном, работа с SPX идентична работе TCP сокетов, все выше написанное справедливо и для IPX сокетов, только не забудьте, что последние нельзя законнектить. Открываются они следующим образом:
Код:
SOCKET ipx_skt = socket (PF_IPX, SOCK_DGRAM, NSPROTO_IPX);
Передача данных происходит следующим образом:
Код:
// структура для хранения удалённого адреса
sockaddr_ipx addr_ipx;
// обнуляем её, хотя это не всегда нужно, но и никогда не мешает
memset (&addr_ipx, 0, sizeof (addr_ipx));
// тип протокола
addr_ipx.sa_family = AF_IPX;
// номер сокета
addr_ipx.sa_socket = htons(SOCKET_NR);
// удалённый номер сети
memcpy (addr_ipx.sa_netnum, remoteNetNum, IPX_NET_SIZE);
// удалённый номер узла
memcpy (addr_ipx.sa_nodenum, remoteNodeNum, IPX_NODE_SIZE);
char* buff = Test string;
// вот и, собственно, передача данных
sendto (ipx_skt, buff, strlen (buff), 0, (sockaddr*)&addr_ipx, sizeof (addr_ipx));
Дальше я дам несколько, на мой взгляд, полезных вещей при работе с данными протоколами.
Приём заголовка пакета данных
В некоторых случаях нам нужен больший контроль над IPX/SPX пакетами, и для того, чтоб наше приложение могло управлять, изменять заголовок IPX/SPX, нужно вызвать следующий код:
Код:
// первый вариант нужен wsnwlink.h
int rcv_header = 1;
setsockopt (spx_skt, NSPROTO_IPX, IPX_RECVHDR, (char*)&rcv_header, sizeof (rcv_header));
// второй вариант только для SPX протокола
int rcv_header = 1;
setsockopt (spx_skt, NSPROTO_SPX, SPX_RAWSPX, (char*)&rcv_header, sizeof (rcv_header));
А вот вам и структура заголовка SPX пакета, взято из WSIPX.H
Код:
/* WSSpxHeader -- SPX Header structure when in SPXL_SPXRAW mode. */
typedef struct WSSpxHeaderStruc
{
WSIpxHeader IpxHdr; // 0x00
u_char ConnCtrl; // 0x1E
u_char DataStreamType; // 0x1F
u_short SrcConnId; // 0x20
u_short DstConnId; // 0x22
u_short SendSeq; // 0x24
u_short AckSeq; // 0x26
u_short AllocNum; // 0x28
} WSSpxHeader, *PWSSpxHeader,FAR *LPWSSpxHeader; // 0x2A (42)
В данном режиме Windows Sockets не будут сегментировать пакеты, ограничивая их размер до максимально допустимого протоколом.
Широковещательные пакеты
Широковещательные пакеты могут быть использованы, например, в качестве средства "принюхивания" клиента к серверу, это в случае, когда мы знаем порт нужного нам сервера, но не знаем его сетевого адресса.
Код:
sockaddr_ipx addr_ipx;
char* broadcast_msg = Some broadcast stuff;
SOCKET ipx_skt = socket (PF_IPX, SOCK_DGRAM, NSPROTO_IPX);
addr_ipx.sa_family = AF_IPX;
// номер сокета, данный номер используется посылки SAP пакетов в нетваре сетях
addr_ipx.sa_socket = htons (0x0452); // = IPXSKT_SAP
// для широковещательных пакетов
memset (addr_ipx.sa_netnum, 0, IPX_NET_SIZE);
memset (addr_ipx.sa_nodenum, 0xff, IPX_NODE_SIZE);
// устанавливаем флаг для посылки широковещательных пакетов
int set_broadcast = 1;
setsockopt (ipx_skt, SOL_SOCKET, SO_BROADCAST, (char*)& set_broadcast, sizeof (set_broadcast));
// ну и, собствено, само вещание
sendto (ipx_skt, broadcast_msg, strlen (broadcast_msg), MSG_DONTROUTE, (sockaddr*)&addr_ipx, sizeof (addr_ipx));
Установка, изменение DataStreamType в заголовке SPX пакета
Это может быть использовано в собственных целях, например, для искусственной сегментации своих данных для совместимости разных реализаций протокола. Например, некоторые реализации протокола для DOS поддерживают максимальную длину пакета в 512 байт либо принудительно ограниченную сетевыми модулями, вот они и используют DataStreamType, чтобы указать последнюю порцию данных.
Устанавливается следующим образом:
Код:
// Можно использовать любое значение между 0 - 0xfd
// следующие значения зарезервированы
// #define SPX_HANG_UP 0xFE
// #define SPX_HANG_UP_ACK 0xFF
int stream_type = 0x05;
setsockopt (spx_skt, NSPROTO_IPX, IPX_DSTYPE, (char*)&stream_type, sizeof(stream_type);
Причём данную установку надо делать перед каждым send. Работает всё ОК, когда посылаются данные ДОС клиенту, ну а при приеме пакетов WIN клиентом от ДОС клиент DataStreamType не хочет устанавливатся, т.е. мы не получим установленное значение DataStreamType ДОС клиентом. Я обошел данную проблему при помощи следующего куска кода:
Код:
int rcv; // количество принятых байтов за один recv
int rcv_total = 0; // общее количество принятых бйтов
do{
// прием данных
rcv = recv (spx_socket, rd_buffer + rcv_total, MAX_BUF_SIZE - rcv_total, 0);
if (rcv > 0) rcv_total += rcv; // если мы что-то получили
}while (rcv > 0); // пока принимаются данные
Данный метод хорош еще тем, что WIN клиент может принять один пакет вместо нескольких, посланных ДОС клиентом.
Другие специфические расширения для данных протоколов, используемые getsockopt/setsockopt, можно найти в файле wsnwlink.h, но, как упоминалось выше, данные расширения - для NT-платформ и могут не работать для других реализаций данных протоколов.
Автор: Александр Ревецкий aka iXANiA
Information
- Posted on 31.01.2010 21:51
- Просмотры: 3690