- Struct sockaddr in windows
- Примеры простых программ
- Некоторые особенности и приемы
- 1. Создание сокета
- 2. Структуры sockaddr_in и sockaddr
- 3. Манипуляции IP-адресами
- 4. Функция listen()
- 5. Обслуживание нескольких клиентов одновременно
- sockaddr
- Example Code
- Struct sockaddr vs. struct sockaddr_in
- Порт сокетных приложений из Unix в Windows
Struct sockaddr in windows
Кратко ознакомиться с интерфейсом сокетов можно по следующему ниже описанию и примерам, а такеж по книге А.Робачевского «ОС Unix» с. 420-426, 264-277. Для программистов настоятельно рекомендуется книга Й.Снейдер. «Эффективное программирование TCP/IP. Библиотека программиста» — СПб: «Питер», 2002.
При выполнении задания предполагается интенсивное использование студентом документации man по требуемым функциям (socket, bind, listen, accept, connect, recv, send, fork, gethostbyname, inet_ntoa, bzero, bcopy, htons).
Примеры простых программ
Ниже приведены тексты двух простых программ на языке С — клиента и сервера, — взаимодействующие через протокол TCP, используя интерфейс сокетов. Операционная система — Solaris.
Сервер работает как эхо: каждому подсоединившемуся клиенту он возвращает полученные от него данные, предварив их строкой с именем и IP-адресом клиента. Сервер использует порт 7890 для приема соединений от клиентов.
Клиент, запускаясь, подсоединяется к серверу, отправляет ему строку «Hello», получает отклик и закрывает соединение.
Некоторые особенности и приемы
1. Создание сокета
Для создания сокета используется функция socket(). Аргументы:
domain — идентификатор коммуникационного домена (области обмена данными): AF_UNIX (внутреннее межпроцессное взаимодействие) или AF_INET (стек TCP/IP). Таким образом, существует сокет вообще — как некий абстрактный механизм межпроцессного взаимодействия и сокет, созданный в конкретном коммуникационном домене и имеющий свойства, специфичные для данного домена. В данной работе используется домен AF_INET .
type — тип сервиса, предоставляемого сокетом, при передаче данных между взаимодействующими процессами. Возможны следующие варианты:
- SOCK_DGRAM — ненадежная передача данных с сохранением границ сообщений (соответствует протоколу UDP),
- SOCK_STREAM — надежная передача данных без сохранения границ сообщений (соответствует протоколу TCP),
- SOCK_SEQ — надежная передача данных с сохранением границ сообщений (в стеке TCP/IP не поддерживается),
- SOCK_RAW — низкоуровневый доступ к сокету (уровень IP).
protocol — протокол передачи данных (должен соответствовать типу сокета). Нулевое значание подразумевает выбор протокола по умолчанию Поскольку в стеке TCP/IP каждому поддерживаемому типу сокета соответствует ровно один протокол, то указывать его нет смысла; используйте нулевое значение.
Пример создания сокета для работы с протоколом TCP:
2. Структуры sockaddr_in и sockaddr
Структуры sockaddr_in , описывающая сокет в домене AF_INET (TCP/IP), и sockaddr , описывающая сокет вообще, определены следующим образом:
При вызове функций bind(), connect(), accept() и некоторых других, указатель на сокет типа sockaddr_in , содержащий адреса клиента или сервера, приводится к указателю на sockaddr , например, если сокет сервера описан как:
то в вызовах указанных выше функций имеем, например:
3. Манипуляции IP-адресами
IP-адрес (поле sin_addr в sockaddr_in ) является структурой. Если требуется указать нулевой адрес (например, при формировании серверного сокета для того, чтобы соединения принимались со всех адресов), эта структура заполняется нулями с помощью функции bzero():
Для записи в эту структуру некоторого IP-адреса, содержащегося в строке из 4-х октетов, расположенных в «правильном» (сетевом) порядке, вызывается функция bcopy(). Получить такую строку по доменному имени узла сети можно из поля h_addr структуры hostent , указатель на которую возвращает функция gethostbyname():
Для преобразования адреса, содержащегося в структуре типа struct in_addr , в точечно-десятичную нотацию используется функция inet_ntoa(), возвращающая указатель на строку символов, содержащую требуемый адрес в виде » d.d.d.d «:
4. Функция listen()
Функция listen() переводит сокет в состояние пассивного открытия, но собственно прослушивание сети и прием поступающих соединений делается функцией accept(). Если требуется обрабатывать несколько соединений, функция accept() вызывается в цикле (см. также п. 4).
5. Обслуживание нескольких клиентов одновременно
Для того чтобы сервер продолжал слушать сеть после установления очередного соединения и мог принять запрос от другого клиента, распространенным приемом является создание дочернего процесса для обслуживания каждого клиента. Для обработки вновь поступившего соединения порождается дочерний процесс с помощью функции fork(). В этом случае используется следующая схема:
sockaddr
The sockaddr structure varies depending on the protocol selected. Except for the sin*_family parameter, sockaddr contents are expressed in network byte order.
Winsock functions using sockaddr are not strictly interpreted to be pointers to a sockaddr structure. The structure is interpreted differently in the context of different address families. The only requirements are that the first u_short is the address family and the total size of the memory buffer in bytes is namelen.
The SOCKADDR_STORAGE structure also stores socket address information and the structure is sufficiently large to store IPv4 or IPv6 address information. The use of the SOCKADDR_STORAGE structure promotes protocol-family and protocol-version independence, and simplifies development. It is recommended that the SOCKADDR_STORAGE structure be used in place of the sockaddr structure. The SOCKADDR_STORAGE structure is supported on Windows ServerВ 2003 and later.
The sockaddr structure and sockaddr_in structures below are used with IPv4. Other protocols use similar structures.
The sockaddr_in6 and sockaddr_in6_old structures below are used with IPv6.
On the Microsoft Windows Software Development Kit (SDK) released for Windows Vista and later, SOCKADDR and SOCKADDR_IN typedef tags are defined for sockaddr and sockaddr_in structures as follows:
On the Windows SDK released for Windows Vista and later, the organization of header files has changed and the sockaddr and sockaddr_in structures are defined in the Ws2def.h header file, not the Winsock2.h header file. The Ws2def.h header file is automatically included by the Winsock2.h header file. The sockaddr_in6 structure is defined in the Ws2ipdef.h header file, not the Ws2tcpip.h header file. The Ws2ipdef.h header file is automatically included by the Ws2tcpip.h header file. The Ws2def.h and Ws2ipdef.h header files should never be used directly.
Example Code
The following example demonstrates the use of the sockaddr structure.
Struct sockaddr vs. struct sockaddr_in
. указатель на struct sockaddr_in может быть приведён к указателю на struct sockaddr и обратно. И несмотря на то,что connect() принимает struct sockaddr*, вы можете использовать struct sockaddr_in* и привести его к нужному типу в последний момент!
typedef struct Foo или struct Foo
В чём разница между: typedef struct < int a; >Foo; и struct Foo
typedef struct X
Собственно сабж: typedef struct X< X* ptrX; >X; Когда происходит объявление ptrX тип.
Typedef struct
Добрый день!! пытаюсь разобраться со структурами,вроде бы как все понятно кроме одного.Ключевое.
Struct somestruct ;
Что значит ? Если в файле такая строчка : struct somestruct ; Обявление структуры?
Важно лишь то, что по указателю на любой из этих типов мы можем читать первое поле структуры, которое в обоих случаях будет по нулевому смещению от указателя. В этом полу по сути закодирована информация о том, указатель какого типа структура на самом деле подан
Т.е. функции connect ты передаёшь указатель на sockaddr (грубо говоря, можно передавать и указатель на void, принципиально ничего не изменится). Далее читается первое поле структуры (p->sa_family). Если значение в нём равно AF_INET, то этот указатель приводится к указателю на sockaddr_in. При этом приведение указателя — чисто техническая формальность для компилятора, адрес в указателе не меняется. Если в этом поле записано значение AF_UNIX, то далее указатель приводится к указателю sockaddr_un
По сути этонекий способ моделирования наследования классов на Си
> Меня смущает размер этих двух структур.Разве он всегда,на всех машинах одинаков?
> Гарантировано,что char = 1 байту,а как быть с остальными int-ами?
На разных платформах поле sa_data описано по разному (каждая платформа «знает» о том, сколько байт занимают структуры sockaddr_in и sockaddr_un). Пользователь напрямую с этим полем работать не должен. Это поле нужно для того, чтобы можно было взять размер от этой структуры (чтобы была возможностьработать не только с указателем, но и копировать объект целиком, не разбираясь, какго он на самом деле типа)
Порт сокетных приложений из 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 + исходники) можно скачать
здесь.