Введение
Сокеты - название программного интерфейса, который обеспечивает обмен данными между процессами. Процессы, участвующие в обмене данными, могут исполняться как на локальном компьютере, так и на разных компьютерах, объединенных в компьютерную сеть. Сокет - абстрактный объект, представляющий конечную точку соединения.
Выделяют клиентские и слушающие (серверные) сокеты. Различия между ними очевидны: клиентские подключаются к процессу (к слушающему сокету), слушающие сокеты, соответственно, обрабатывают эти подключения. Передача данных между процессами происходит через клиентские сокеты.
Общая схема работы с сокетами в Windows
Клиент
- Инициализация WSA
- Создание сокета
- Присоединение к серверу
- Прием/передача данных
- Разрыв соединения
Сервер
- Инициализация WSA
- Создание слушающего сокета и привязка к порту
- Прослушивание порта
- Обработка входящих подключений
- Прием/передача данных
- Разрыв соединения
Шаблон программы
Этот код содержит основные включения WSA.
?
#include < winsock2.h > #include < ws2tcpip.h > #include < iostream > #pragma comment(lib, "Ws2_32.lib") int main() { return 0; } |
Введение в Windows Sockets API: Клиентские сокеты C++
Клиентские сокеты используют приложения, которым необходимо передавать и принимать данные с другим приложением. Примером такого приложения является браузер: по определенному ip-адресу он передает серверу сформированный HTTP-заголовок, в качестве ответа сервер передает данные, которые затребовал в HTTP-заголовке клиент (web-страницу, изображение и т.п.).
Инициализация WSA
WSADATA wsaData; int result; result = WSAStartup(MAKEWORD(2, 2), &wsaData); if(result!= 0) { std::cout << "Ошибка WSAStartup: " << result << std::endl; return 1; } |
Перед началом работы с сокетами Windows необходимо инициализировать Windows sockets API (WSA)
Для этого надо вызывать функцию WSAStartup и передать в аргументах версию WSA и указатель на структуру WSAData. Функция возвращает 0, если инициализация прошла успешно, в противном случае возвращает код ошибки.
Создание сокета
SOCKET clientSocket = INVALID_SOCKET; clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(clientSocket == INVALID_SOCKET) { std::cout << "Ошибка socket(): " << WSAGetLastError() << std::endl; WSACleanup(); return 1; } |
Функция socket возвращает дескриптор сокета. Если при создании сокета произошла ошибка, она возвращает 0 (INVALID_SOCKET).
Присоединение к серверу
?
#define ip "127.0.0.1" #define port 80 sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr(ip); clientService.sin_port = htons(port); int result; result = connect( clientSocket, reinterpret_cast< SOCKADDR* >(&clientService), sizeof(clientService) ); if(result!= 0) { std::cout << "Ошибка в connect(): " << WSAGetLastError() << std::endl; WSACleanup(); return 1; } |
После того как сокет создан, можно начинать подключаться через него к серверам.
Для соединения нужен сам сокет, ip-адрес и порт процесса (сервера). Эти данные передаются функции connect, которая возвращает 0 в случае успешного соединения.
Ip-адрес и порт следует передать структуре sockaddr.
Передача данных серверу
?
int result; char data[] = "Test"; result = send(clientSocket, data, static_cast< int >(strlen(data)), 0); if(result < 0) { std::cout << "Ошибка в send(): " << WSAGetLastError() << std::endl; return 1; } |
Для передачи данных серверу необходимо вызвать функцию send. Вторым аргументом является указатель на данные, третьим количество данных.
Функция возвращает отрицательное число, если произошла ошибка, иначе возвращается число, соответствующие количеству переданных данных.
Прием данных от сервера
?
#define bufsize 256 char buf[bufsize]; int r; do { r = recv(clientSocket, buf, bufsize, 0); if(r > 0) std::cout << "Приянтно " << r << " байт" << std::endl; else if(r == 0) std::cout << "Соединение разорвано" << std::endl; else std::cout << "Ошибка в recv(): " << WSAGetLastError() << std::endl; } while(r > 0); |
Что бы принять данные надо вызывать функцию recv и передать ей в качестве аргументов указатель на буфер данных и на размер этого буфера.
Из кода видно, что за один раз можно принять не более bufsize байт информации.
Разрыв соединения
closesocket(clientSocket); // Если работа с сокетами больше не предполагается вызываем WSACleanup() WSACleanup(); |
Заключение
Дополнительную информацию о функциях, структурах WSA можно найти в MSDN: Winsock reference.
Введение в Windows Sockets API: Серверные сокеты C++
Входящие подключения со стороны сторонних приложений обрабатываются через специальный серверный (слушающий) сокет, который создается на стороне сервера с привязкой к конкретному порту.
Примером программы, использующей серверные сокеты, может служить приложение, обрабатывающие некую телеметрическую информацию, поступающую от дочерних приложений-клиентов. Приложение-сервер создает слушающий сокет с привязкой к конкретному порту и уходит в режим ожидания подключений (можно создать отельный поток для ожидания подключений, чтобы программа "не висла"). Приложение-клиент подключается к слушающему сокету, после чего можно начинать обмен данными.
Инициализация WSA
int iResult; WSAData d; iResult = WSAStartup(MAKEWORD(2, 2), &d); if(iResult!= 0) { std::cout << "Error at WSAStartup: " << iResult; return 1; } |
перед началом работы с сокетами необходимо провести инициализацию Windows Sockets API.
Подготовка данных для создания сокета
#define DEFAULT_PORT "27015" struct addrinfo *result = NULL, *ptr = NULL, hints; ZeroMemory(&hints, sizeof (hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); if (iResult!= 0) { std::cout << "Ошибка getaddrinfo: " << iResult; WSACleanup(); return 1; } |
Функция getaddrinfo используется для определения значений в структуре sockaddr.
- AF_INET указывает, что используется четвертая версия IP протокола.
- SOCK_STREAM используется для определения потока сокета.
- IPPROTO_TCP используется для определения TCP протокола.
- 270015 это номер порта, к которому будет подключаться клиент.
Создание сокета
SOCKET listenSocket = INVALID_SOCKET; listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if(listenSocket == INVALID_SOCKET) { std::cout << "Error at socket(): " << WSAGetLastError(); freeaddrinfo(result); WSACleanup(); return 1; } |
Для создания сокета вызываем функцию socket с данными, подготовленными в предыдущем шаге, если создание пройдет успешно, то функция вернет дескриптор сокета.