Binary Rape cos125@hotmail.com
Anarki blaze@hotbox.ru
http://blacksun.box.sk

 

Как известно, концепция сокетов была разработана в Беркли, и затем реализована сначала в BSD, затем в Linux и, наконец, с некоторыми изменениями, и в Windows. Таким образом, это делает сетевое программирование на обеих платформах очень сильно похожим и перенос Unix приложения на Win-платформу становится не очень сложным делом.

Эта статья — попытка помочь тем, кто впервые решил осуществить порт сокетного приложения из Unix (Linux, BSD) в Windows. Зачем? Кто-то решает поднять свой уровень кодинга на новую высоту, кому-то это просто интересно, а кто-то ищет новые задачи для решения. Совет: не пытайтесь сразу портировать что-нибудь достаточно большое и сложное, начните с простых утилит вроде traceroute, nslookup и с прочтения этой статьи :-). Базовых знаний приемов сетевого программирования на C++ будет вполне достаточно. Все примеры из статьи компилировались на VC++ 6.0 под Win2k prof. и WinXP prof.

Unix и Windows

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 <sys/types.h> ] 
[ #include <sys/socket.h> ] 

bind() 
[ #include <sys/types.h> ] 
[ #include <sys/socket.h> ] 

connect() 
[ #include <sys/types.h> ] 
[ #include <sys/socket.h> ] 

listen() 
[ #include <sys/types.h> ] 
[ #include <sys/socket.h> ] 

accept() 
[ #include <sys/socket.h> ] 

gethostname() 
[ #include <netdb.h> ] 

Эти два файла к сокетам не относятся, но обычно присутствуют в Unix программах:

#include <netinet/in.h> 
#include <arpa/inet.h>

Т.е. типичное начало сетевой UNIX программы выглядит так:

#include <sys/types.h> 
#include <sys/socket.h> 
#include <netdb.h> 
#include <netinet/in.h> 
#include <arpa/inet.h>

При переносе в Windows, все эти строки заменяются на одну:

#include <windows.h>

Объявление 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 <stdio.h> 
#include <netdb.h> /* Этот файл нужен функции
gethostbyname() */ 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 

int main(int argc, char *argv[]) 

struct hostent *he; 

if (argc!=2)

printf («Usage: %s <hostname>\n",argv[0]); 
exit(-1); 

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 строки, заменив их на:

#include <windows.h>

и немного изменим строку 6:

void main(int argc, char **argv)

не забыв прилинковать wsock32.lib к проекту. Теперь программа компилируется без проблем, но при попытке ею воспользоваться выдает лаконичное: «gethostbyname() error». В чем дело? Все просто, не были вызваны WSAStartup() и WSACleanup()! Добавляем вызовы этих функций и код нормально компилируется.

Вот код портированного, работающего Win приложения:

#include <windows.h> 
#include <stdio.h> 

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)));

WSACleanup(); 
}

Конечно, это — очень простое приложение. Попробуем перенести программу посложнее
— TCP streaming server.

UNIX код:

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 

#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); 

if (bind(fd,(struct sockaddr*)&server, sizeof(struct sockaddr))==-1)
{ /* вызов bind() */ 
printf ("bind() error\n"); 
exit (-1); 

if (listen(fd,BACKLOG) == -1)
{ /* вызов listen() */ 
printf ("listen() error\n"); 
exit (-1); 

while (1)

sin_size=sizeof (struct sockaddr_in); 
if ((fd2 = accept(fd,(struct sockaddr *)&client, &sin_size))==-1)
{ /* вызов accept() */ 
printf ("accept() error\n"); 
exit (-1); 

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 <stdio.h> 
#include <windows.h>
#define PORT 3550
#define BACKLOG 2

main() 

WSADATA wsdata; 
WSAStartup(0x0101,&wsdata); 

int fd, fd2;

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 (bind(fd,(struct sockaddr*)&server, sizeof(struct sockaddr))==-1)

printf («bind() error\n»); 
exit (-1); 

if (listen (fd,BACKLOG) == -1)

printf («listen() error\n»); 
exit (-1); 

while (1) 

sin_size=sizeof (struct sockaddr_in); 

if ((fd2 = accept(fd,(struct sockaddr *)&client,&sin_size)) == -1) 

printf («accept() error\n»); 
exit (-1); 

printf («You got a connection from %s\n»,inet_ntoa(client.sin_addr) ); 
send (fd2,»Welcome to my server.\n»,22,0); 
closesocket (fd2); 

WSACleanup(); 
return -1; 
}}

TCP streaming client

UNIX код:

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <netdb.h> /* необходим для 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 (argc !=2) 
{ /* Программе нужен аргумент — IP-адрес*/ 
printf(«Usage: %s <IP Address>\n»,argv[0]); 
exit(-1); 

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(connect(fd, (struct sockaddr *)&server,sizeof(struct sockaddr))==-1)

printf(«connect() error\n»); 
exit(-1); 

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 код — для его получения нужно проделать те же, вышеописанные, шаги.

#include <windows.h> 
#include <stdio.h> 

#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 (argc !=2) 
{
printf(«Usage: %s <IP Address>\n»,argv[0]); 
exit(-1); 

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(connect(fd, (struct sockaddr *)&server,sizeof(struct sockaddr))==-1)

printf(«connect() error\n»); 
exit(-1); 

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 + исходники) можно скачать
здесь.

Оставить мнение

Check Also

Признаки «Оперы». Йон фон Течнер об идеологии браузера Vivaldi

«Хакер» побеседовал с сооснователем фирмы Opera и основателем Vivaldi Йоном фон Течнером, …