Простой и быстрый сервер на C/C++ с клиентом на C#: TCP версия

от автора

Всем привет. Потихоньку перебирая листы книг, занимаюсь я серверным программированием. И дошёл мой разум до того, что можно было бы и на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *