Програмирование IPX/SPX-сокетов
Когда пишут про сокетное программирование, конечно-же подразумевают TCP/IP. Вот тут мы и отступимся от правил и поговорим про IPX/SPX.
Многие либо давно забыли это, либо даже и не знали о существовании данных протоколов. Даже Novell в последние годы открещивается от своего детища.
Может оно и к лучшему, но тем не менее еще многие программные модули и системы до сих пор используют IPX/SPX. Стало быть, если в общем и целом эти старые программыработают удовлетворительно, но нуждаются в добавлении какого-то функционала — может быть и нету смысла переписывать их с нуля под IP, а только дописать нужные модули. Вот на этот случай вам и шпаргалка :)
Итак, все начинается, как обычно, с инициализации библиотеки WINSOCK (обработка ошибок упускается для упрощения кода):
#include // прототипы функции библиотеки
#include // IPX/SPX структуры
#include // 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));
В данном режиме 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);
// для широковещательных пакетов
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-пакета
Спросите: «когда и зачем используется?» — отвечу коротко: «Не знаю...». Это, в принципе, может быть использованно в собственных целях, например, для искусственной сегментации своих данных для совместимости разных реализаций протокола.
Делается это следующим образом:
// Можно использовать любое значение между 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.
Другие специфические расширения для данных протоколов, используемые getsockopt/setsockopt, можно найти в файле wsnwlink.h, но, как упоминалось выше, данные расширеня для NT-платформ и могут не работать для других реализаций данных протоколов.
Многие либо давно забыли это, либо даже и не знали о существовании данных протоколов. Даже Novell в последние годы открещивается от своего детища.
Может оно и к лучшему, но тем не менее еще многие программные модули и системы до сих пор используют IPX/SPX. Стало быть, если в общем и целом эти старые программыработают удовлетворительно, но нуждаются в добавлении какого-то функционала — может быть и нету смысла переписывать их с нуля под IP, а только дописать нужные модули. Вот на этот случай вам и шпаргалка :)
Итак, все начинается, как обычно, с инициализации библиотеки WINSOCK (обработка ошибок упускается для упрощения кода):
#include // прототипы функции библиотеки
#include // IPX/SPX структуры
#include // 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));
В данном режиме 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);
// для широковещательных пакетов
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-пакета
Спросите: «когда и зачем используется?» — отвечу коротко: «Не знаю...». Это, в принципе, может быть использованно в собственных целях, например, для искусственной сегментации своих данных для совместимости разных реализаций протокола.
Делается это следующим образом:
// Можно использовать любое значение между 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.
Другие специфические расширения для данных протоколов, используемые getsockopt/setsockopt, можно найти в файле wsnwlink.h, но, как упоминалось выше, данные расширеня для NT-платформ и могут не работать для других реализаций данных протоколов.
Александр Ревецкий aka iXANiA
обсуждение статьи
Сетевые решения. Статья была опубликована в номере 08 за 2003 год в рубрике программирование