netlib.narod.ru< Назад | Оглавление | Далее >

Пример программирования сокетов

Я люблю учить на примерах, так что как насчет программы, использующей TCP/IP, которая подключается к Интернету, посылает HTTP-запрос на Web-сервер и отображает главную страницу сайта? Перед тем, как я перейду к коду, посмотрите на рис. 14.10, где показано что именно мы будем делать.


Рис. 14.10. Ход выполнения простой программы, использующей сокеты

Рис. 14.10. Ход выполнения простой программы, использующей сокеты


Здесь вы можете видеть этапы, необходимые для того, чтобы подключиться к Веб-серверу и загрузить с него главную страницу. Сперва вы инициализируете сокеты, чтобы коммуникационный уровень был готов к работе. Затем вы создаете сокет, который будет использоваться для подключения к Web-серверу. Когда сокет готов, вы находите IP-адрес Web-сервера и устанавливаете соединение с ним. После установки соединения вы отправляете HTTP-запрос на получение содержимого главной страницы. После этого вам остается только ждать, когда запрошенная информация придет в буфер ответа. Получив данные вы закрываете сокет и отключаете всю систему сокетов.

Программа Sockets_Receive

Я реализовал код, необходимый для воссоздания этапов, изображенных на рис. 14.10. Загрузите программу Sockets_Receive и следуйте за мной. Проект состоит из файла main.cpp и единственной библиотеки ws2_32.lib, которая содержит все, что необходимо для программирования сокетов в Windows. Скомпилируйте программу и запустите ее. Вы увидите окно консольного приложения, в котором отображается содержимое главной страницы сайта, имя которого задано в коде. Как это должно выглядеть, показано на рис. 14.11.


Рис. 14.11. Окно программы Sockets_Receive

Рис. 14.11. Окно программы Sockets_Receive


Открыв файл main.cpp вы увидите следующий код:

#include <iostream.h>
#include <winsock.h>
#include <stdio.h>

void main(void)
{
   SOCKET    skSocket;
   sockaddr_in saServerAddress;
   int       iPort = 80;
   int       iStatus;
   WSADATA   wsaData;
   WORD      wVersionRequested;
   LPHOSTENT lpHost;
   char      szHost[128];
   char      szSendBuffer[256];
   char      szRecvBuffer[32768];
   int       iBytesSent;
   int       iBytesReceived;

   sprintf(szHost,"www.lostlogic.com");
   // Сообщаем WinSock, что нам нужна версия 2
   wVersionRequested = MAKEWORD(2, 0);
   // Инициализируем дескриптор сокета
   skSocket = INVALID_SOCKET;
   // Запускаем WinSock
   iStatus = WSAStartup(wVersionRequested, &wsaData);
   // Создаем сокет
   skSocket = socket(AF_INET, SOCK_STREAM, 0);
   // Проверяем наличие ошибок
   if(skSocket == INVALID_SOCKET) {
      cout << "**ERROR** Could Not Create Socket" << endl;
      exit(1);
   }
   memset(&saServerAddress, 0, sizeof(sockaddr_in));
   saServerAddress.sin_family = AF_INET;
   saServerAddress.sin_addr.s_addr = inet_addr(szHost);

   if(saServerAddress.sin_addr.s_addr == INADDR_NONE)
   {
      lpHost = gethostbyname(szHost);
      if (lpHost != NULL) {
         // Получаем адрес сервера из информации хоста
         saServerAddress.sin_addr.s_addr =
            ((LPIN_ADDR)lpHost->h_addr)->s_addr;
      }
      else {
         cout << "**ERROR** Could Not Locate Host" << endl;
         exit(1);
      }
   }
   // Задаем порт сервера
   saServerAddress.sin_port = htons(iPort);
   // Пытаемся подключиться к серверу
   iStatus = connect(skSocket,
          (struct sockaddr*)&saServerAddress,
          sizeof(sockaddr));

   // Проверяем наличие ошибок
   if(iStatus == SOCKET_ERROR) {
      cout << "**ERROR** Could Not Connect To Server" << endl;
      exit(1);
   }
   sprintf(szSendBuffer,"GET / HTTP/1.0\n\n");
   // Отправляем HTTP-запрос
   iBytesSent = send(skSocket, szSendBuffer, 256, 0);
   memset(szRecvBuffer, 0x00, 32768);
   // Получаем данные
   iBytesReceived = recv(skSocket, szRecvBuffer, 32768, 0);
   cout << szRecvBuffer << endl;
   // Завершаем работу
   closesocket(skSocket);
   WSACleanup();
}

Включение заголовочного файла WinSock

Заголовочный файл winsock.h содержит всю необходимую информацию для работы с библиотекой сокетов ws2_32.lib. Убедитесь, что включаете его в любой ваш код, который работает с сокетами. Остальные заголовочные файлы применяются ежедневно в обычном программировании.

Установка версии WinSock

Перед тем, как вы вообще сможете использовать какие-либо сокеты, необходимо инициализировать систему сокетов Windows, вызвав функцию WSAStartup(). Эта функция получает номер версии сокетов, которую вы намереваетесь использовать и инициализирует коммуникационную систему. Раз вы хотите использовать сокеты версии 2, установите номер запрашиваемой версии равным 2.

Создание сокета

Чтобы подключиться к внешнему миру вам нужен канал связи в виде сокета. Чтобы создать такой канал вызовите функцию socket(), предоставляемую библиотекой сокетов. Успешно завершившаяся функция возвращает идентификационный номер (дескриптор) сокета.

СОВЕТ
Дескриптор сокета это всего лишь обычное число. Для каждого, создаваемого вами сокета, система возвращает уникальное представляющее его число.

Поиск сервера по URL

Если вы хотите установить соединение с сервером, используя URL, а не IP-адрес, вам сперва надо будет найти IP-адрес по URL. Это делается с помощью функции gethostbyname(). Она получает имя сервера и преобразует его в соответствующий IP-адрес.

Установка номера порта

Сервер может принимать подключения по нескольким линиям связи. Каждая из таких линий называется портом. Поскольку предоставляется несколько портов на выбор, необходимо указывать конкретный порт, с которым вы хотите соединиться. Это делается путем указания номера порта во время инициализации структуры адреса сервера sockaddr_in. Требуемая информация содержится в поле sin_port вышеупомянутой структуры. Задайте значение переменной sin_port и вы готовы идти дальше.

Подключение к серверу

После того, как заданы IP-адрес и порт, вы можете подключаться к серверу. Это делается путем вызова предоставляемой сокетом функции connect(). Ей передаются дескриптор сокета, который вы хотите использовать, и параметры сервера к которому вы хотите подключиться. Если функция возвращает значение SOCKET_ERROR, значит произошла ошибка; в противном случае соединение установлено.

Отправка данных серверу

Теперь, когда соединение с сервером установлено, можно отправить пакет с HTTP-запросом. Этот пакет сообщает серверу, что вы хотите увидеть содержимое предоставляемой по умолчанию веб-страницы. Для отправки пакета необходимо воспользоваться функцией send(). Она получает сокет, через который будут отправлены данные, сами отправляемые данные и их размер. В рассматриваемом примере я отправляю содержимое буфера szSendBuffer через сокет, идентификатор которого хранится в переменной skSocket.

Если какие-либо данные были переданы, функция возвращает количество отправленных байт.

Получение данных от сервера

После того, как вы отправили HTTP-запрос серверу, следует получить ответ от него. Чтобы увидеть ответ, вы должны вызвать функцию recv(), которая вернет данные из буфера связи сокета. Самое лучшее в сокетах то, что они автоматически принимают данные и помещают их в системный буфер, так что вам не следует беспокоиться, что данные могут быть потеряны из-за того, что ваша программа занята. Тем не менее, следует проявлять осторожность и не ждать слишком долго, поскольку данные, которые находятся в буфере слишком долго будут потеряны.

Функции приема в параметрах передаются идентификатор сокета, от которого вы хотите получить информацию, буфер для размещения данных и его размер. Как только появятся какие-нибудь данные для получения, они будут переданы в буфер приема и программа продолжит работу. Если данные никогда не будут отправлены, функция будет ждать вечно, пока вы не завершите программу. Такова природа синхронных сокетов (blocking socket).

ПРИМЕЧАНИЕ
Синхронный сокет ждет завершения выполнения команды, прежде чем продолжить работу. Это может приводить к такми проблемам, как блокировка программы. Гораздо лучше использовать асинхронные сокеты, которые возвращают управление программе сразу после вызова функции. Основной недостаток асинхронных сокетов в том, что программы, использующие их, труднее писать.

После того, как функция приема данных вернет управление программе, вы можете взглянуть на содержимое веб-страницы, выведя на экран буфер szRecvBuffer.

Закрытие сокета

Теперь, когда работа с сокетом завершена, необходимо закрыть его. Это делает функция closesocket(). Она получает идентификатор сокета, который будет закрыт и отключен.

Отключение сокетов

Когда вы полностью завершили работу с сокетами, необходимо отключить коммуникационную систему сокетов, вызвав функцию WSACleanup(). Ее требуется вызывать один раз в конце вашей программы.

Мы вихрем промчались по теме синхронных сокетов! Я знаю что действительно сильно разогнался, но моей главной целью было заложить основы для более интересного материала.

Программирование сетевых походовых игр

Как вы знаете, есть два типа стратегических игр: походовые и реального времени. Хотели ли вы когда-нибудь создать походовую игру, в которую можно играть через Интернет? Я говорю о том, что коллективные игры доставляют много удовольствия, но не слишком удобны, потому что игрокам необходимо собраться в одном месте. Здесь на сцену выходит программа, которую я сейчас опишу. В сопроводительных файлах к книге есть проект Sockets_TurnGame. Будучи скомпилированной, эта программа демонстрирует как осуществляется походовая игра в локальной сети или через Интернет. Пойдемте дальше и загрузим этот проект.

Ход выполнения походовой игры

Походовые сетевые игры следуют очень прямолинейной схеме работы. Чтобы узнать, как она выглядит, взгляните на рис. 14.12.


Рис. 14.12. Ход выполнения походовой сетевой игры

Рис. 14.12. Ход выполнения походовой сетевой игры


На иллюстрации изображены клиент и сервер для походовой сетевой игры. Процесс начинается с запуска сервера, который прослушивает указанный порт, ожидая подключения клиента. Как только клиент установил соединение, сервер принимает его и ждет, пока клиент сделает свой ход. Как только клиент будет готов, он посылает пакет с данными своего хода серверу и ждет ответного хода сервера. Когда игрок на сервере будет готов, он завершает свой ход и отправляет пакет с данными хода клиенту. Этот процесс повторяется до завершения игры. Получается игра в которой вы получаете право хода, делаете ход и затем ждете, пока другой игрок сделает то же самое.

Программа Sockets_TurnGame

Программа Sockets_TurnGame предлагает пример реализации схемы, изображенной на рис. 14.12. Запустите программу и вы увидите окно, изображенное на рис. 14.13.


Рис. 14.13. Окно программы Sockets_TurnGame

Рис. 14.13. Окно программы Sockets_TurnGame


На рис. 14.13 изображено небольшое окно с элементами управления, позволяющими работать как главный компьютер или установить соединение. Щелчок по кнопке Host переключает программу в режим игрового сервера, а щелчок по кнопке Connect переключает программу в режим клиента. Так как нельзя получить цыпленка раньше яйца, вы должны сначала запустить программу главного компьютера и уже потом подключаться к ней посредством клиента.

Если вы до сих пор не поняли, чтобы программа работала должным образом, вам необходимо запустить ее дважды. Это необходимо потому, что для демонстрации передачи ходов туда и обратно необходим как ведущий компьютер, так и клиент. Если вы еще не сделали этого, запустите программу дважды и в одном из экземпляров щелкните по кнопке Host. После этого в другом экземпляре программы щелкните по кнопке Connect. В результате вы должны увидеть что-нибудь, напоминающее рис. 14.14.


Рис. 14.14. Клиент установил соединение с сервером

Рис. 14.14. Клиент установил соединение с сервером


На рис. 14.14 и на вашем экране вы видите два экземпляра программы. Программа сервера должна ожидать, пока клиент сделает свой ход, и у программы клиента должна быть готовая к использованию кнопка Turn Done. Игрок, у которого видна кнопка Turn Done в данный момент получил контроль над игрой и может передать его другому игроку, щелкнув по кнопке. Так вы можете передавать ход туда и обратно, щелкая по появляющейся кнопке. Я знаю, что понадобится напрячь воображение, но попытайтесь представить себе, что между щелчками по кнопке вы выполняете сложные игровые ходы.

В работающую с сокетами программу я решил включить функции как для сервера, так и для клиента. Из-за этого код разветвляется в двух направлениях, в зависимости от того, какую роль выберет пользователь. Ход выполнения программы изображен на рис. 14.15.


Рис. 14.15. Ход выполнения программы Sockets_TurnGame

Рис. 14.15. Ход выполнения программы Sockets_TurnGame


На иллюстрации показано, что программа начинает работу с инициализации элементов управления и сокетов. После того, как инициализация успешно завершена, программа переходит в цикл обработки сообщений и ждет, пока пользователь щелкнет по кнопке Host или Connect. Если пользователь выбрал кнопку Connect, программа ждет, пока он закончит свой ход. Если же пользователь выберет кнопку Host, программа ждет подключения клиента.

Код проекта содержится в файлах main.cpp и main.h. Для работы программе требуются две библиотеки: winmm.lib и ws2_32.lib. Библиотека winmm.lib не требуется для работы с сетью, я использую ее для воспроизведения звукового сигнала, когда пользователь щелкает по кнопке завершения хода.

Глобальные переменные программы Sockets_TurnGame

Загрузите заголовочный файл main.h, и вы увидите в нем такой код:

// Переменные сокетов
SOCKET g_skListenSocket;
SOCKET g_skClientSocket;
bool      g_bIsServer = 0;
bool      g_bMyTurn = 0;
bool      g_bConnected = 0;

В приведенном выше коде объявлены два дескриптора сокетов. Ведущий компьютер для прослушивания сети в ожидании новых соединений использует дескриптор g_skListenSocket. Другой дескриптор, g_skClientSocket, используется клиентом для подключения к серверу или сервер назначает его подключившемуся клиенту. Так или иначе, клиентский сокет используется для управления соединением между клиентом и сервером.

Логическое значение g_bIsServer сообщает вам работает ли программа в режиме ведущего компьютера. Если значение равно 1, значит программа является сервером и должна ожидать подключения клиента. Если значение равно 0, программа является клиентом и должна установить соединение с ведущим игровым компьютером.

Логическое значение g_bMyTurn сообщает принадлежит ли вам в данный момент право хода в игре. Если сейчас ваш ход, будет отображаться кнопка Turn Done, щелкнув по которой вы передадите ход другому игроку. Если сейчас ваш ход, значение переменной равно 1, если нет — значение переменной равно 0.

Логическое значение g_bConnected сообщает вам установила ли программа соединение с другим игроком. 1 означает что соединение существует, 0 — что нет.

Есть и еще несколько глобальных переменных, но они относятся к элементам управления Windows и другим подобным вещам.

Функции программы Sockets_TurnGame

Далее в заголовочном файле main.h приведены прототипы используемых в программе функций. Вот они:

void vHost();
void vInitializeSockets(void);
void vShutdownSockets(void);
void vConnect(void);
void vSendTurnMessage(void);
void vReceiveTurnMessage(void);
void vTurnDone(void);

Функция vHost() вызывается когда пользователь щелкает по кнопке Host. Она прослушивает указанный порт на ведущем компьютере и ждет входящих подключений. Как только клиент установит соединение, ведущий компьютер принимает его, после чего можно производить обмен данными.

Функция vInitializeSockets() используется для начальной инициализации WinSock.

Функция vShutdownSockets() отключает все активные соединения и систему WinSock.

Функция vConnect() вызывается когда пользователь щелкает по кнопке Connect. Она пытается подключиться к ведущему компьютеру, чей IP-адрес указан в окне. После того, как соединение установлено, клиент получает контроль над игрой и может закончить ход в выбранный им момент времени.

Функция vSendTurnMessage() отправляет сообщающий о завершении хода пакет другому игроку. На самом деле пакет не содержит никакой полезной информации, он просто показывает вам как пересылать данные по проводам.

Функция vReceiveTurnMessage() ждет, пока другой игрок не пришлет сообщающий о завершении хода пакет. Функция будет сидеть и ждать, пока пока рак на горе не свистнет.

Функция vTurnDone() вызывается функциями отправки и приема хода для завершения хода. Это происходит когда пользователь щелкает по кнопке Turn Done.

Остальные перечисленные в заголовочном файле main.h функции являются стандартным каркасом приложения для Windows и не слишком интересны, поэтому я не буду их рассматривать. Вы же не хотите, чтобы я по сто раз описывал одно и то же? Лучше я лишний раз сыграю в Age of Mythology!

Функция vHost()

Представьте на минуту, что вы загрузили программу походовой игры и щелкнули по кнопке Host. Начнет выполняться следующий код:

sockaddr_in saServerAddress;
sockaddr_in saClientAddress;
int    iClientSize = sizeof(sockaddr_in);
int    iPort = 6001;
int    iStatus;

// Установка глобальных переменных
g_bIsServer = 1;
// Инициализация дескриптора сокета
g_skListenSocket = INVALID_SOCKET;
// Создание сокета
g_skListenSocket = socket(AF_INET, SOCK_STREAM, 0);
// Проверим, не произошла ли ошибка
if(g_skListenSocket == INVALID_SOCKET) {
   vShowText("** ERROR ** Could Not Create Socket");
   return;
}
vShowText("<- Socket Created ->");
// Очищаем структуру адреса сокета
memset(&saServerAddress, 0, sizeof(sockaddr_in));
// Инициализируем структуру адреса сокета
saServerAddress.sin_family = AF_INET;
saServerAddress.sin_addr.s_addr = htonl(INADDR_ANY);
saServerAddress.sin_port = htons(iPort);
// Пытаемся выполнить привязку
if(bind(g_skListenSocket, (sockaddr*) &saServerAddress, sizeof(sockaddr)) ==
          SOCKET_ERROR) {
   vShowText("** ERROR ** Could Not Bind Socket");
   return;
}
vShowText("<- Socket Bound ->");
// Прослушиваем подключения
iStatus = listen(g_skListenSocket, 32);
if(iStatus == SOCKET_ERROR) {
   vShowText("** ERROR ** Could Not Listen");
   // Закрываем сокет
   closesocket(g_skListenSocket);
   return;
}
vShowText("<- Socket Listening ->");
g_skClientSocket = accept(g_skListenSocket, (struct sockaddr*)&saClientAddress,
                          &iClientSize);
if(g_skClientSocket == INVALID_SOCKET) {
   vShowText("** ERROR ** Could Not Accept Client");
   // Закрываем сокет
   closesocket(g_skListenSocket);
   return;
}
// Убираем кнопки
DestroyWindow(hBU_Connect);
DestroyWindow(hBU_Host);
vShowText("<- Client Connected ->");
// Устанавливаем флаг подключения
g_bConnected = 1;
// Устанавливаем флаг, сообщающий, что сейчас
// ход делает другой игрок
g_bMyTurn = 0;
// Ждем первый ход клиента
vTurnDone();

Первая часть кода реализует логику для подключения клиента. Фактически программа прослушивает порт, ожидая соединения и принимает соединение, когда оно происходит. После этого код убирает кнопки Host и Connect, чтобы пользователь не мог щелкнуть по ним еще раз. Затем программа устанавливает переменную хода, чтобы она указывала, что контроль над игрой находится у клиента. И, наконец, ход заканчивается вызовом функции завершения хода. Это переводит сервер в режим приема, чтобы он мог получить сообщение о завершении хода от клиента. Все эти действия показаны на рис. 14.16.


Рис. 14.16. Ход выполнения функции vHost()

Рис. 14.16. Ход выполнения функции vHost()


Функция vConnect()

Функция vConnect() вызывается, когда игрок щелкает по кнопке Connect. Вот как выглядит ее код:

sockaddr_in saServerAddress;
int iPort = 6001,iStatus;
LPHOSTENT lpHost;
char szHost[128];

// Установка глобальных переменных
g_bIsServer = 0;
// Инициализация параметров сервера, смените указанный здесь IP-адрес
// на корректный IP-адрес вашей сети 
sprintf(szHost, "192.168.0.2");
// Инициализация дескриптора сокета
g_skClientSocket = INVALID_SOCKET;
// Создание сокета
g_skClientSocket = socket(AF_INET, SOCK_STREAM, 0);
// Проверка наличия ошибок
if(g_skClientSocket == INVALID_SOCKET) {
   vShowText("** ERROR ** Could Not Create Socket");
   return;
}
vShowText("<- Socket Created ->");
// Инициализация структуры данных адреса сервера
memset(&saServerAddress, 0, sizeof(sockaddr_in));
// Установка значений по умолчанию
saServerAddress.sin_family = AF_INET;
// Загрузка IP-адреса
saServerAddress.sin_addr.s_addr = inet_addr(szHost);

// Если задано имя сервера, а IP-адрес отсутствует, 
// попытаемся получить требуемое значение
if(saServerAddress.sin_addr.s_addr == INADDR_NONE) {
   vShowText("<- Looking Up Host ID ->");
   // Получаем имя сервера
   lpHost = gethostbyname(szHost);
   // Проверяем, получили ли мы что-нибудь
   if (lpHost != NULL) {
      // Загружаем адрес сервера из его данных
      saServerAddress.sin_addr.s_addr = ((LPIN_ADDR)lpHost->h_addr)->s_addr;
   }
   else {
      vShowText("** ERROR ** Could Not locate host");
      return;
   }
}
// Устанавливаем порт сервера
saServerAddress.sin_port = htons(iPort);
// Пытаемся подключиться к серверу
iStatus = connect(g_skClientSocket, (struct
              sockaddr*)&saServerAddress, sizeof(sockaddr));
// Проверяем наличие ошибок
if(iStatus == SOCKET_ERROR) {
   vShowText("** ERROR ** Could Not Connect To Server");
   return;
}
// Убираем кнопки
DestroyWindow(hBU_Connect);
DestroyWindow(hBU_Host);
vShowText("<- Connected To Server ->");
// Устанавливаем флаг подключения
g_bConnected = 1;
// Устанавливаем флаг, указывающий что право хода принадлежит нам
g_bMyTurn = 1;
// Отображаем кнопку Turn Done
hBU_TurnDone = CreateWindow(
   "BUTTON",
   "Turn Done",
   WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
   5,
   280,
   100,
   28,
   g_hWnd, (HMENU)IDC_hBU_TurnDone, g_hInst, NULL);
vShowText(":Server waiting, make your turn");

Код подключения очень похож на тот, который я показывал вам в примере подключения к веб-серверу. Клиент сперва получает адрес сервера, а затем пытается установить соединение с ним. Как только соединение успешно установлено, клиент убирает кнопки Connect и Host и отображает новую кнопку Turn Done. В этот момент клиенту дается время, чтобы он мог сделать свой ход. Ход выполнения функции показан на рис. 14.17.


Рис. 14.17. Ход выполнения функции vConnect()

Рис. 14.17. Ход выполнения функции vConnect()


Функция vTurnDone()

Функция завершения хода выполняет две различных задачи. Если сейчас ваш ход, она отправляет сообщение о завершении хода другому игроку и ждет получения сообщения. Если сейчас ход другого игрока, функция ждет, пока он не завершит свой ход. Ход выполнения функции показан на рис. 14.18.


Рис. 14.18. Ход выполнения функции vTurnDone()

Рис. 14.18. Ход выполнения функции vTurnDone()


Хотя блок-схема и выглядит запутанной, сам код не слишком сложен. Большая его часть занимается обработкой сообщений от окна. Если ее отбросить, оставшийся код будет выглядеть примерно так:

// Если соединение установлено, проверяем 
// надо получать или отправлять сообщение о ходе
if(g_bConnected) {
   // Мой ход, отправляю сообщение
   if(g_bMyTurn) {
      // Убираем кнопку завершения хода
      DestroyWindow(hBU_TurnDone);
      // Отправляем сообщение о завершении хода
      vSendTurnMessage();
      // Ждем получения сообщения
      vReceiveTurnMessage();
   }
   else {
      // Ждем получения сообщения
   }
}

Если вы сравните приведенный выше код с тем, который находится в файле main.cpp, то увидите что здесь код значительно короче. Я удалил из него текстовые сообщения, чтобы вам проще было увидеть, что происходит.

Функция vSendTurnMessage()

Когда приходит время отправлять сообщение о завершении хода, вызывается функция отправки сообщений. Ее код выглядит так:

void vSendTurnMessage(void)
{
   char szTurnPacket[32];
   intiBytes = 0;

   // Создаем пакет-заглушку
   sprintf(szTurnPacket, "turnpacket");
   // Отправляем пакет
   iBytes = send(g_skClientSocket, szTurnPacket, 32, 0);
   if(iBytes != SOCKET_ERROR) {
   }
   else {
      vShowText("** ERROR ** Sending");
      return;
   }
   // Устанавливаем режим приема
   g_bMyTurn = 0;
}

Код начинается с создания пакета для отправки его клиенту или серверу. Для демонстрационных целей в пакет помещается строка текста. Как только пакет собран, код посылает его другому игроку. Программа блокируется и ждет, пока получатель не подтвердит, что данные приняты. Так только подтверждение получено, сбрасывается флаг хода и функция завершает работу.

Функция vReceiveTurnMessage()

Когда вы ждете получения хода от другого игрока, работает функция приема сообщения о ходе. Она сидит и ждет пока пакет с данными хода не придет по проводам. Как только прибывает пакет, устанавливается флаг хода и отображается кнопка Turn Done. Вот как выглядит код, выполняющий эти задачи:

void vReceiveTurnMessage(void)
{
   char szTurnPacket[32];
   intiBytes = 0;

   iBytes = recv(g_skClientSocket, szTurnPacket, 32, 0);
   // Проверка возвращенного кода
   if(iBytes != SOCKET_ERROR) {
   }
   else {
      vShowText("** ERROR ** Receiving");
      return;
   }
   // Переключение в режим отправки
   g_bMyTurn = 1;

   // Отображение кнопки Turn Done
   hBU_TurnDone = CreateWindow(
      "BUTTON",
      "Turn Done",
      WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
      5,
      280,
      100,
      28,
      g_hWnd, (HMENU)IDC_hBU_TurnDone, g_hInst, NULL);
}

В функции приема я вызываю функцию recv для приема пакета от другого игрока. Как только пакет пришел, код устанавливает флаг хода и создает кнопку Turn Done. Вот и все об отправке и получении пакетов!

В этом разделе я только прикоснулся к поверхности огромной темы программирования многопользовательских игр. Надеюсь, вам хватит предоставленной информации, чтобы хотя бы начать работу над походовой сетевой игрой.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz