Всем привет. Потихоньку перебирая листы книг, занимаюсь я серверным программированием. И дошёл мой разум до того, что можно было бы и на C++ сервер написать. Ну и недолго думая(точнее вообще не думая и плохо зная плюсы), я пошёл писать сервер.
И начал я натыкаться на ошибки разные, до этого чуждые мне, да на синтаксис непонятный. И в итоге, сейчас делюсь с Вами тем, как надо и как не надо писать даже самый простой сервер.
И так начнём.
Начнём с самого главного элемента — класс сервера SServer:
#pragma once #include "includes.h" class SServer { public: SServer(); ~SServer(); void startServer(); void closeServer(); void handle(); unsigned short port; private: SOCKET this_s; WSAData wData; };
Тут думаю сложно не будет. SOCKET создаём как сокет для сервера, он будет слушать.
WSAData нужен для активации использования сокетов в Windows.
Это был заголовок. Перейдём к CPP:
#include "SServer.h" #include "includes.h" SServer::SServer(){ } SServer::~SServer(){ } void SServer::startServer(){ if (WSAStartup(MAKEWORD(2, 2), &wData) == 0) { printf("WSA Startup succes\n"); } SOCKADDR_IN addr; int addrl = sizeof(addr); addr.sin_addr.S_un.S_addr = INADDR_ANY; addr.sin_port = htons(port); addr.sin_family = AF_INET; this_s = socket(AF_INET, SOCK_STREAM, NULL); if (this_s == SOCKET_ERROR) { printf("Socket not created\n"); } if (bind(this_s, (struct sockaddr*)&addr, sizeof(addr)) != SOCKET_ERROR) { printf("Socket succed binded\n"); } if (listen(this_s, SOMAXCONN) != SOCKET_ERROR){ printf("Start listenin at port%u\n", ntohs(addr.sin_port)); } handle(); } void SServer::closeServer() { closesocket(this_s); WSACleanup(); cout << "Server was stoped. You can close app" << endl; } void SServer::handle() { while (true) { SOCKET acceptS; SOCKADDR_IN addr_c; int addrlen = sizeof(addr_c); if ((acceptS = accept(this_s, (struct sockaddr*)&addr_c, &addrlen)) != 0) { printf("send\n"); printf("sended Client connected from 0 %u.%u.%u.%u:%u\n", (unsigned char)addr_c.sin_addr.S_un.S_un_b.s_b1, (unsigned char)addr_c.sin_addr.S_un.S_un_b.s_b2, (unsigned char)addr_c.sin_addr.S_un.S_un_b.s_b3, (unsigned char)addr_c.sin_addr.S_un.S_un_b.s_b4, ntohs(addr_c.sin_port)); SClient* client = new SClient(acceptS, addr_c); } Sleep(50); } }
А теперь по-порядку. Сначала активируем WSA вызовом WSAStartup(). MAKEWORD задаёт версию библиотек, которые будут подключаться и ссылка на объект WSAData. Обычно всегда срабатывает успешно, только ели у вас не *unix, хе-хе. Дальше. структура SOCKADDR_IN помогает нам определить заранее порт и доступность нашего сервера. А теперь поясню:
- INETADDR_ANY говорит о том, что сервер будет доступен с любой пользовательской машины. Если указать как пишут inet_aadr(«127.0.0.1»), то это не даст вам даже в локальной сети тестировать
- htons() — функция, которая превращает hardware to network short при этом используется ushort. Дальше будет функция ntohs() — действует наоборот
- AF_INET — константа, отвечающая за то, что устройство использует глобальную сеть по протоколу IPv4, AF_INET6 — IPv6
Дальше происходит волшебство вызов трёх функций и проверка их значений на SOCKET_ERROR. Думаю тут более ли менее понятно. Проинициализировали, забиндили и заставили слушать.
SOCK_STREAM говорит об использовании TCP протокола, SOCK_DGRAM — UDP соответственно.
Вызываем обработчик входящих соединений handle() и в бесконечном цикле начинаем проверку.
Создаём сокет для входящего подключения, структуру для заполнения адреса и длину структуры.
Производим проверку на то, что не является ли наш созданный сокет подключённым по адресу такому-то от компьютера того-то. И если всё хорошо, то выводим сообщение и подключении с адреса нашего клиента и создаём клиента, который уже в дальнейшем будет сам всё обрабатывать.
Теперь же код клиента. Заголовок и имплементация вместе:
#pragma once #include "includes.h" class SClient { public: SClient(SOCKET s, SOCKADDR_IN sock_in); ~SClient(); void handle(); private: SOCKET c_sock; SOCKADDR_IN c_addr; char buffer[1024]; }; SClient::SClient(SOCKET s, SOCKADDR_IN sock_in) { c_sock = s; c_addr = sock_in; printf("Client created\n"); handle(); } SClient::~SClient() { } void SClient::handle() { while (true) { int k = recv(c_sock, buffer, sizeof(buffer), NULL); if(k>0){ printf(buffer); } Sleep(30); } }
Тут тоже как видите, ничего сложного нет. главное это handle() функция, которую мы сразу запускаем в конструкторе. Функцию recv() предпочтительно использовать с TCP, а recvfrom() с UDP протоколом соответственно.
И на последок. В main.cpp Всё выглядит вот так:
#include "SServer.h" int main() { SServer server ; server.port = 3487;//порт это ushort - так что cin.get() и т.п. функции тут работаю некорректно. Лучше задать фвручную server.startServer(); return 0; }
А файлик, который вы так часто видели includes.h. Вот и он:
#pragma once #pragma comment(lib, "ws2_32.lib") #pragma warning(disable: 4996) #include <iostream> #include <WinSock2.h> #include <winsock.h> #include "SClient.h" using namespace std;
Итог: Мы научились создавать простой TCP сервер на C++, прочитав хорошую статью на хабре
Будет так же и UDP версия, но отличия очень малы и думаю скоро тоже будет здесь.
Так же всё есть на GitHub
ссылка на оригинал статьи https://habrahabr.ru/post/327574/
Добавить комментарий