Порт сокетных приложений из Unix в Windows
Как известно, концепция сокетов была разработана в Беркли, и затем реализована сначала в BSD, затем в Linux и, наконец, с некоторыми изменениями, и в Windows. Таким образом, это делает сетевое программирование на обеих платформах очень сильно похожим и перенос Unix приложения на Win-платформу становится не очень сложным делом.
Эта статья — попытка помочь тем, кто впервые решил осуществить порт сокетного приложения из Unix (Linux, BSD) в Windows. Зачем? Кто-то решает поднять свой уровень кодинга на новую высоту, кому-то это просто интересно, а кто-то ищет новые задачи для решения. Совет: не пытайтесь сразу портировать что-нибудь достаточно большое и сложное, начните с простых утилит вроде traceroute, nslookup и с прочтения этой статьи :-). Базовых знаний приемов сетевого программирования на C++ будет вполне достаточно. Все примеры из статьи компилировались на VC++ 6.0 под Win2k prof. и WinXP prof.
UNIX и Windows по разному обращаются с сокетами: в UNIX сокеты обрабатываются системой точно так же, как дескрипторы файлов integer типа, в то время как Windows это хэндл unsigned типа — SOCKET. В Unix все I/O действия выполняются чтением или записью в соответствующий дескриптор — число (integer) ассоциированное с открытым файлом, сетевым соединением, терминалом и т.п.
В Unix коды ошибок доступны через переменную errno, в Windows нужно использовать функцию WSAGetLastError().
И в Unix и в Windows порт определяется параметром, переданном функции htons(), но в Windows некоторые, наиболее часто используемые порты предопределены в
winsock.h:
IPPORT_ECHO — 7
IPPORT_DISCARD — 9
IPPORT_SYSTAT — 11
IPPORT_DAYTIME — 13
IPPORT_NETSTAT — 15
IPPORT_FTP — 21
IPPORT_TELNET — 23
IPPORT_SMTP — 25
IPPORT_TIMESERVER — 37
IPPORT_NAMESERVER — 42
IPPORT_WHOIS — 43
IPPORT_MTP — 57
Заголовочные файлы
Вот список функций Unix и соответствующих им .h-файлов
socket()
[ #include ]
[ #include ]
bind()
[ #include ]
[ #include ]
connect()
[ #include ]
[ #include ]
listen()
[ #include ]
[ #include ]
Эти два файла к сокетам не относятся, но обычно присутствуют в Unix программах:
Т.е. типичное начало сетевой UNIX программы выглядит так:
#include
#include
#include
#include
#include
При переносе в Windows, все эти строки заменяются на одну:
Объявление winsock.h уже включено в windows.h.
Вторым шагом будет линковка приложению Wsock32.lib (Для VC++: меню Project->Settings, на вкладке Link, дописать wsock32.lib к списку библиотек).
UNIX и Windows имеют ряд общих, выполняющих одинаковые функций
процедур. Это большинство функций работы с TCP/UDP, все функции преобразования + используемые ими структуры. Это, например, функции htons() и inet_addr() и структуры sockaddr и sockaddr_in.
Вот список этих функций:
socket()
bind()
listen()
connect()
accept()
sendto()
recvfrom()
gethostname()
А вот список функций, делающих одно и то же, различающихся только названиями:
Unix Windows
close() closesocket()
ioctl() ioctlsocket()
read() recv()
write() send()
Дополнительно к вышесказанному, каждое сокетное приложение Windows должно содержать вызовы функций WSASStartup() и WSACleanup(), которые подготавливают к использованию Winsock и освобождают его, соответственно.
Для начала, перенесем что-нибудь простенькое. Например утилиту, определяющую IP-адрес хоста по его имени. Вот UNIX код:
#include
#include /* Этот файл нужен функции
gethostbyname() */
#include
#include
#include
int main(int argc, char *argv[])
<
struct hostent *he;
if ((he=gethostbyname(argv[1]))==NULL)
<
printf («gethostbyname() error\n»);
exit (-1);
>
printf («Hostname : %s\n»,he->h_name); /* Вывод имени хоста */
printf («IP Address: %s\n»,inet_ntoa(*((struct in_addr *)he->h_addr))); /* И его IP-адреса
*/
>
Попытка скомпилировать этот код без изменений была горячо воспринята VC++ — компилятор ругнулся на отсутствие .h файлов и сообщил, что компилировать программу он не собирается :-). Первым делом удаляем 2-5 строки, заменив их на:
и немного изменим строку 6:
void main(int argc, char **argv)
не забыв прилинковать wsock32.lib к проекту. Теперь программа компилируется без проблем, но при попытке ею воспользоваться выдает лаконичное: «gethostbyname() error». В чем дело? Все просто, не были вызваны WSAStartup() и WSACleanup()! Добавляем вызовы этих функций и код нормально компилируется.
Вот код портированного, работающего Win приложения:
void main(int argc, char **argv)
<
WSADATA wsdata;
WSAStartup(0x0101,&wsdata);
struct hostent *he;
if (argc! = 2)
<
printf(«Usage: %s hostname\n»,argv[0]);
>
if ((he = gethostbyname(argv[1])) == NULL)
<
printf(«gethostbyname() error\n»);
>
printf («Hostname : %s\n»,he->h_name);
printf(«IP Address: %s\n»,inet_ntoa(*((struct in_addr *)he->h_addr)));
Конечно, это — очень простое приложение. Попробуем перенести программу посложнее
— TCP streaming server.
#include
#include
#include
#include
#define PORT 3550 /* Порт, открываемый программой */
#define BACKLOG 2 /* Число соединений */
main()
<
int fd, fd2; /* дескрипторы */
struct sockaddr_in server; /* информация о сервере */
struct sockaddr_in client; /* информация о клиенте */
int sin_size;
if ((fd=socket(AF_INET, SOCK_STREAM, 0)) == -1 )
< /* вызов socket() */
printf («socket() error\n»);
exit (-1);
>
server.sin_family = AF_INET;
server.sin_port = htons (PORT);
server.sin_addr.s_addr = INADDR_ANY;
bzero (&(server.sin_zero),8);
printf («You got a connection from %s\n», inet_ntoa(client.sin_addr) );
send (fd2,»Welcome to my server.\n»,22,0);
close (fd2);
>>
Итак, сначала повторим шаги из предыдущего примера — оставим объявления только windows.h и stdio.h и прилинкуем wsock32.lib. Попытка компиляции приносит две ошибки: одна — по поводу функции bzero(), вторая — по поводу функции
close() — компилятор сообщает, что она — invalid identifier :-). Первая ошибка лечится очень просто — удаляем всю 21-ю строку. Для исправления второй, смотрим в таблицу, приведенную выше и заменяем close() на ее Win-аналог — closesocket(). Добавляем вызовы WSAStartup() и WSACleanup() и voila — программа компилируется без проблем. После запуска программы видим пустую командную строку, все правильно — сервер ждет клиента :-).
Windows код сервера:
#include
#include
#define PORT 3550
#define BACKLOG 2
main()
<
WSADATA wsdata;
WSAStartup(0x0101,&wsdata);
struct sockaddr_in server;
struct sockaddr_in client;
int sin_size;
if ((fd=socket(AF_INET, SOCK_STREAM, 0)) == -1 )
<
printf(«socket() error\n»);
exit(-1);
>
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = INADDR_ANY;
if (listen (fd,BACKLOG) == -1)
<
printf («listen() error\n»);
exit (-1);
>
while (1)
<
sin_size=sizeof (struct sockaddr_in);
printf («You got a connection from %s\n»,inet_ntoa(client.sin_addr) );
send (fd2,»Welcome to my server.\n»,22,0);
closesocket (fd2);
TCP streaming client
#include
#include
#include
#include
#include /* необходим для struct hostent */
#define PORT 3550 /* Порт, к которому будем коннектиться */
#define MAXDATASIZE 100 /* Макс. размер данных в байтах */
int main (int argc, char *argv[])
<
int fd, numbytes; /* дескрипторы */
char buf[MAXDATASIZE]; /* здесь будем хранить полученный текст */
struct hostent *he;
struct sockaddr_in server;
if ((he=gethostbyname(argv[1]))==NULL)
<
printf(«gethostbyname() error\n»);
exit(-1);
>
if ((fd=socket(AF_INET, SOCK_STREAM, 0))==-1)
<
printf(«socket() error\n»);
exit(-1);
>
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(server.sin_zero),8);
if ((numbytes=recv(fd,buf,MAXDATASIZE,0)) == -1)
<
printf(«recv() error\n»);
exit(-1);
>
buf[numbytes]=’\0′;
printf(«Server Message: %s\n»,buf);
close(fd);
>
И, без лишних слов, Windows код — для его получения нужно проделать те же, вышеописанные, шаги.
#define PORT 3550
#define MAXDATASIZE 100
int main(int argc, char *argv[])
<
WSADATA wsdata;
WSAStartup(0x0101,&wsdata);
int fd, numbytes;
char buf[MAXDATASIZE];
struct hostent *he;
struct sockaddr_in server;
if ((he=gethostbyname(argv[1])) == NULL)
<
printf(«gethostbyname() error\n»);
exit(-1);
>
if ((fd=socket(AF_INET, SOCK_STREAM, 0))==-1)
<
printf(«socket() error\n»);
exit(-1);
>
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr *)he->h_addr);
if ((numbytes=recv(fd,buf,MAXDATASIZE,0)) == -1)
<
printf(«recv() error\n»);
exit(-1);
>
buf[numbytes] = ‘\0’;
printf(«Server Message: %s\n»,buf);
closesocket(fd);
WSACleanup();
return -1;
>
Запустив клиента (при запущенном сервере, естественно) с аргументом localhost (что-то вроде tcp_client.exe localhost) видим приветствие сервера — «Welcome to my server». С чем я тебя и поздравляю
:-).
Вот те шаги, которые нужно делать в первую очередь, при переносе приложений:
1. Заменить объявления заголовочных файлов UNIX на windows.h и прилинковать wsock32.lib
2. Добавить вызовы функций WSAStartup() и WSACleanup()
3. Заменить функции вроде close() и ioctl() на их Windows-аналоги
Можно даже написать небольшую программу делающую это автоматически (например на Perl). Она здорово облегчит процесс порта приложений.
Все примеры из статьи (exe + исходники) можно скачать
здесь.
Netinet in h windows
netinet/in.h — Internet address family
SYNOPSIS
DESCRIPTION
The header shall define the following types:
in_port_t Equivalent to the type uint16_t as defined in . in_addr_t Equivalent to the type uint32_t as defined in .
The sa_family_t type shall be defined as described in .
The uint8_t and uint32_t type shall be defined as described in . Inclusion of the header may also make visible all symbols from and .
The header shall define the in_addr structure that includes at least the following member:
The header shall define the sockaddr_in structure that includes at least the following members:
The sin_port and sin_addr members shall be in network byte order.
The sockaddr_in structure is used to store addresses for the Internet address family. Values of this type shall be cast by applications to struct sockaddr for use with socket functions.
[IP6] The header shall define the in6_addr structure that contains at least the following member:
This array is used to contain a 128-bit IPv6 address, stored in network byte order.
The header shall define the sockaddr_in6 structure that includes at least the following members:
The sin6_port and sin6_addr members shall be in network byte order.
The sockaddr_in6 structure shall be set to zero by an application prior to using it, since implementations are free to have additional, implementation-defined fields in sockaddr_in6.
The sin6_scope_id field is a 32-bit integer that identifies a set of interfaces as appropriate for the scope of the address carried in the sin6_addr field. For a link scope sin6_addr, the application shall ensure that sin6_scope_id is a link index. For a site scope sin6_addr, the application shall ensure that sin6_scope_id is a site index. The mapping of sin6_scope_id to an interface or set of interfaces is implementation-defined.
The header shall declare the following external variable:
This variable is initialized by the system to contain the wildcard IPv6 address. The header also defines the IN6ADDR_ANY_INIT macro. This macro must be constant at compile time and can be used to initialize a variable of type struct in6_addr to the IPv6 wildcard address.
The header shall declare the following external variable:
This variable is initialized by the system to contain the loopback IPv6 address. The header also defines the IN6ADDR_LOOPBACK_INIT macro. This macro must be constant at compile time and can be used to initialize a variable of type struct in6_addr to the IPv6 loopback address.
The header shall define the ipv6_mreq structure that includes at least the following members:
The header shall define the following macros for use as values of the level argument of getsockopt() and setsockopt():
IPPROTO_IP Internet protocol. IPPROTO_IPV6 [IP6] Internet Protocol Version 6.
IPPROTO_ICMP Control message protocol. IPPROTO_RAW [RS]
Raw IP Packets Protocol.
IPPROTO_TCP Transmission control protocol. IPPROTO_UDP User datagram protocol.
The header shall define the following macros for use as destination addresses for connect(), sendmsg(), and sendto():
INADDR_ANY IPv4 local host address. INADDR_BROADCAST IPv4 broadcast address.
The header shall define the following macro to help applications declare buffers of the proper size to store IPv4 addresses in string form:
INET_ADDRSTRLEN 16. Length of the string form for IP.
[IP6] The header shall define the following macro to help applications declare buffers of the proper size to store IPv6 addresses in string form:
INET6_ADDRSTRLEN 46. Length of the string form for IPv6.
The header shall define the following macros, with distinct integer values, for use in the option_name argument in the getsockopt() or setsockopt() functions at protocol level IPPROTO_IPV6:
IPV6_JOIN_GROUP Join a multicast group. IPV6_LEAVE_GROUP Quit a multicast group. IPV6_MULTICAST_HOPS
Multicast hop limit. IPV6_MULTICAST_IF Interface to use for outgoing multicast packets. IPV6_MULTICAST_LOOP
Multicast packets are delivered back to the local application. IPV6_UNICAST_HOPS Unicast hop limit. IPV6_V6ONLY Restrict AF_INET6 socket to IPv6 communications only.
The header shall define the following macros that test for special IPv6 addresses. Each macro is of type int and takes a single argument of type const struct in6_addr *:
IN6_IS_ADDR_UNSPECIFIED
Unspecified address. IN6_IS_ADDR_LOOPBACK
Loopback address. IN6_IS_ADDR_MULTICAST
Multicast address. IN6_IS_ADDR_LINKLOCAL
Unicast link-local address. IN6_IS_ADDR_SITELOCAL
Unicast site-local address. IN6_IS_ADDR_V4MAPPED
IPv4 mapped address. IN6_IS_ADDR_V4COMPAT
IPv4-compatible address. IN6_IS_ADDR_MC_NODELOCAL
Multicast node-local address. IN6_IS_ADDR_MC_LINKLOCAL
Multicast link-local address. IN6_IS_ADDR_MC_SITELOCAL
Multicast site-local address. IN6_IS_ADDR_MC_ORGLOCAL
Multicast organization-local address. IN6_IS_ADDR_MC_GLOBAL
Multicast global address.
APPLICATION USAGE
RATIONALE
FUTURE DIRECTIONS
SEE ALSO
CHANGE HISTORY
First released in Issue 6. Derived from the XNS, Issue 5.2 specification.
The sin_zero member was removed from the sockaddr_in structure as per The Open Group Base Resolution bwg2001-004.
IEEE Std 1003.1-2001/Cor 1-2002, item XBD/TC1/D6/12 is applied, adding const qualifiers to the in6addr_any and in6addr_loopback external variables.
IEEE Std 1003.1-2001/Cor 2-2004, item XBD/TC2/D6/22 is applied, making it clear which structure members are in network byte order.