printf(“Local host name has not been configured\n”);
Else
printf(“Host name is %s.\n”, szInfo);
if (WSACleanup())
printf(“Winsock cleanup FAILED!\n”);
else
printf(“Winsock cleanup is successful.\n”);
system(«pause»);
return;
}
Отличие этого примера от предыдущего заключается в использовании информационной функции gethostname(), имеющей следующий прототип:
int gethostname (
char FAR * name,
int namelen
);
В эту функцию передается буфер и его длина для возврата имени. При отсутствии ошибок эта функция вернет 0. Результат штатной работы программы приведен на рисунке 2.4.2.
Рисунок 2.4.2 – Работа функции gethostname()
Рассмотрим процедуру создания и закрытия сокетов, изменив предыдущий пример:
#include <stdio.h>
#include <winsock.h>
const int WINSOCK_VERSION = 0x0101;
void main()
{
SOCKET serverSocket;
Char szInfo[BUFSIZ];
WSADATA wsaData;
if (WSAStartup(WINSOCK_VERSION, &wsaData))
printf(“Winsock startup FAILED!\n”);
else
printf(“Winsock startup is successful.\n”);
if (gethostname(szInfo,sizeof(szInfo)))
printf(“Local host name has not been configured\n”);
else
printf(“Host name is %s.\n”, szInfo);
serverSocket = socket(PF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET)
printf(“Socket creation FAILED!\n”);
closesocket(serverSocket);
if (WSACleanup())
printf(“Winsock cleanup FAILED!\n”);
else
printf(“Winsock cleanup is successful.\n”);
system(«pause»);
return;
}
Добавлена следующая переменная:
SOCKET serverSocket;
Тип SOCKET определяется в файле winsock.h следующим образом:
/*
* The new type to be used in all
* instances which refer to sockets.
*/
typedef UINT_PTR SOCKET;
А UINT_PTR в файле basetsd.h как
typedef _W64 unsigned int UINT_PTR, *PUINT_PTR;
То есть, по сути, является замаскированным целочисленным числом без знака типа unsigned int и содержит идентификатор сокета в системе.
За создание сокета отвечает следующая строчка:
serverSocket = socket(PF_INET, SOCK_STREAM, 0);
Функция socket() создает сокет заданного типа и возвращает его идентификатор в случае успеха или константу INVALID_SOCKET (также определена в файле winsock.h) в противном случае. Все три аргумента функции являются целочисленными, задают тип сокета указанием нужных констант, список которых можно найти в заголовочном файле, в документации среды разработки или в RFC 790, от сентября 1981 года. Первый аргумент – тип сетевой модели, PF_INET соответствует сетевой модели Интернета (TCP/IP), PF_OSI модели OSI, PF_APPLETALK модели AppleTalk и т.д. Однако указание, например, PF_OSI в качестве сетевой модели приведет к ошибке создания сокета, поскольку в рамках Windows данная модель не поддерживается, а аргумент функции и константы введены для возможности расширения интерфейса и, возможно, использования в будущих операционных системах. Дальше указывается тип потока: SOCK_STREAM используется для TCP, а SOCK_DGRAM для UDP. Третий аргумент – собственно протокол, где 0 по умолчанию IP.
После работы сокет необходимо закрыть:
closesocket(serverSocket);
Единственным параметром функции является сокет, который требуется закрыть.
Попробуем реализовать простой сервер, работающий по протоколу http и использующий соответственно порт 80.
Рассмотрим, как производится подключение к серверу. Сначала программа подключается к адресу IP с созданием сокета. Программа будет ждать подключения. Для подключения программа клиент тоже создает сокет и пытается подключиться к сокету сервера. Как только сервер зарегистрирует попытку подключения, он создаст новый сокет. И этот новый сокет будет использоваться для взаимодействия с клиентом. А тот сокет, к которому была попытка подключения, будет ждать следующего. Сокет может быть создан на основе TCP или UDP. На этой основе производится взаимодействие сервера и многими программами.
Для организации связи серверу необходимо вызвать функцию bind() для действительного сокета и связать его с номером порта, который будет «прослушиваться» для ожидания подключения. Функция требует заполненную структуру SOCKADDR_IN с такими параметрами связи, как порт и атрибуты. Ее прототип:
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];};
В данной структуре присутствует вложенная структура sin_addr, она описана следующим образом:
struct in_addr {
union {
struct{
unsigned char
s_b1,
s_b2,
s_b3,
s_b4;
} S_un_b;
struct{
unsigned short
s_w1,
s_w2;
} S_un_w;
unsigned long S_addr;
} S_un;
};
Смысл полей достаточно прозрачен: тип сетевого адреса (соответствующий разным сетевым моделям), адрес, порт.
После описания структур и заполнения данными можно вызывать bind() с проверкой результата на ошибку:
SOCKADDR_IN socketaddr;
socketaddr.sin_family = AF_INET;
socketaddr.sin_addr.s_addr = INADDR_ANY;
socketaddr.sin_port = htons(80);
if (bind(serverSocket,(LPSOCKADDR)&socketaddr,sizeof(socketaddr)) == SOCKET_ERROR)
printf(“Socket binding FAILED!”);
else
printf(“Socked binding is successful.\n”);
Ее прототип:
int bind (
SOCKET s,
const struct sockaddr FAR* name,
int namelen
);
Если всё нормально, то данная функция вернет 0 в противном случае SOCKET_ERROR. Как видно адрес не указывается (INADDR_ANY это константа нуля), поскольку создается сокет для сервера. Полный текст программы выглядит следующим образом:
#include <stdio.h>
#include <winsock.h>
const int WINSOCK_VERSION = 0x0101;
void main()
{
SOCKET serverSocket;
SOCKADDR_IN socketaddr;
char szInfo[BUFSIZ];
WSADATA wsaData;
if (WSAStartup(WINSOCK_VERSION, &wsaData))
printf(“Winsock startup FAILED!\n”);
else
printf(“Winsock startup is successful.\n”);
if (gethostname(szInfo,sizeof(szInfo)))
printf(“Local host name has not been configured\n”);
else
printf(“Host name is %s.\n”, szInfo);
serverSocket = socket(PF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET)
printf(“Socket creation FAILED!\n”);
socketaddr.sin_family = AF_INET;
socketaddr.sin_addr.s_addr = INADDR_ANY;
socketaddr.sin_port = htons(80);
if (bind(serverSocket, (LPSOCKADDR)&socketaddr, sizeof(socketaddr)) == SOCKET_ERROR)
printf(“Socket binding FAILED!\n”);
Else
printf(“Socked binding is successful.\n”);
closesocket(serverSocket);
if (WSACleanup())
printf(“Winsock cleanup FAILED!\n”);
else
printf(“Winsock cleanup is successful.\n”);
system(«pause»);
return;
}
В компьютерах архитектуры Intel 80x86 и совместимыми с ними слово (двухбайтовое число) хранится следующим образом:
младщий байт n;
старший байт n+1,
где n – номер ячейки в памяти.
К сожалению в Интернете наоборот:
младщий байт n+1;
старший байт n.
Для решения этой проблемы WinSock API предоставляет следующие функции:
1. htohl – Преобразует 32 битные локальные числа к сетевым, сортируя байты;
2. htohs – Преобразует 16 битные локальные числа к сетевым, сортируя байты;
3. ntonl – Преобразует 32 битные сетевые числа к локальным, сортируя байты;
4. ntons – Преобразует 16 битные сетевые числа к локальным, сортируя байты.
На данном этапе, программа почти готова принимать сообщения по сети, однако как узнать, что клиент установил соединение с сервером? Для этого используются события и сообщения ОС Windows. Как известно, обработчиком сообщений является окно, а, следовательно, необходимо связать сокет с дескриптором окна. Для этого служит функция WSAAsyncSelect(). Как следует из приставки WSA, она относится к расширениям Windows для функций Беркли, то есть, специфична для этой ОС.
Первым аргументом передается сокет, вторым – дескриптор окна, третьим – сообщение, которое будет послано, четвертым – событие, при котором генерируется сообщение. FD_ACCEPT означает, что сообщение сгенерируется при запросе от клиента.
Здесь интересен параметр unsigned int wMsg – этот параметр говорит о том, какое сообщение будет послано в случае подключения к серверу. Сообщения WM_SERVER_ACCEPT в Windows не существует, это пользовательское сообщение, его можно описать следующим образом:
const int WM_SERVER_ACCEPT = WM_USER + 1;
Включается «прослушивание» функцией listen().
Error = listen(serverSocket, 10);
if (error == SOCKET_ERROR)
printf(«listen() FAILED!\n»);
Прототип функции listen() выглядит следующим образом:
int listen (
SOCKET s,
int backlog
);
Первый аргументом является сокет, вторым – максимально количество подключений.
К сожалению, просто добавить в текст последней программы функцию WSAAsyncSelect не получится. Первая проблема очевидна – это отсутствие дескриптора окна у консольного приложения, вторая – отсутствие обработки поступающих сообщений. И если первую еще можно решить, например, поиском дескриптора окна по его заголовку функцией FindWindow(), то вторая приводит к необходимости создания оконной процедуры и полноценного Windows приложения.
Ниже приведен текст Winsock программы, выступающей в роли сервера HTTP:
// Стандартный включаемый файл Windows
#include <windows.h>
#include <winsock.h>
const int WINSOCK_VERSION = 0x0101;
const int WM_SERVER_ACCEPT = WM_USER + 1;
// Прототип функции обратного вызова для обработки сообщений