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