БлогNot. Пишем консольный сервер и клиент на C++ в Windows 32/64

Пишем консольный сервер и клиент на C++ в Windows 32/64

Как обычно, создаём новый пустой проект C++, но для решения указываем имя, например, ClientServer, а для проекта - другое имя, пусть будет Server.

Потом ещё раз создаём пустой проект с именем Client, но добавляем его в то то же самое решение.

Жмём правую кнопку мыши на "исходных файлах" приложения-сервера, выбираем команды Добавить, Создать элемент, Visual C++, файл C++, имя файла с исходным текстом можно оставить Source.cpp (по умолчанию).

Вот полный исходник этого файла, откомпилированный в Visual Studio 2019 под актуальной сборкой Windows 10.

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream> 
#include <cstdio> 
#include <cstring> 
#include <winsock2.h> 
#pragma comment(lib, "WS2_32.lib")
using namespace std;

DWORD WINAPI serverReceive(LPVOID lpParam) { //Получение данных от клиента
 char buffer[1024] = { 0 }; //Буфер для данных
 SOCKET client = *(SOCKET*)lpParam; //Сокет для клиента
 while (true) { //Цикл работы сервера
  if (recv(client, buffer, sizeof(buffer), 0) == SOCKET_ERROR) {
   //Если не удалось получить данные буфера, сообщить об ошибке и выйти
   cout << "recv function failed with error " << WSAGetLastError() << endl;
   return -1;
  }
  if (strcmp(buffer, "exit\n") == 0) { //Если клиент отсоединился
   cout << "Client Disconnected." << endl;
   break;
  }
  cout << "Client: " << buffer << endl; //Иначе вывести сообщение от клиента из буфера
  memset(buffer, 0, sizeof(buffer)); //Очистить буфер
 }
 return 1;
}

DWORD WINAPI serverSend(LPVOID lpParam) { //Отправка данных клиенту
 char buffer[1024] = { 0 };
 SOCKET client = *(SOCKET*)lpParam;
 while (true) {
  fgets(buffer, 1024, stdin);
  if (send(client, buffer, sizeof(buffer), 0) == SOCKET_ERROR) {
   cout << "send failed with error " << WSAGetLastError() << endl;
   return -1;
  }
  if (strcmp(buffer, "exit\n") == 0) {
   cout << "Thank you for using the application" << endl;
   break;
  }
 }
 return 1;
}

int main() {
 WSADATA WSAData; //Данные 
 SOCKET server, client; //Сокеты сервера и клиента
 SOCKADDR_IN serverAddr, clientAddr; //Адреса сокетов
 WSAStartup(MAKEWORD(2, 0), &WSAData);
 server = socket(AF_INET, SOCK_STREAM, 0); //Создали сервер
 if (server == INVALID_SOCKET) {
  cout << "Socket creation failed with error:" << WSAGetLastError() << endl;
  return -1;
 }
 serverAddr.sin_addr.s_addr = INADDR_ANY;
 serverAddr.sin_family = AF_INET;
 serverAddr.sin_port = htons(5555);
 if (bind(server, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
  cout << "Bind function failed with error: " << WSAGetLastError() << endl;
  return -1;
 }

 if (listen(server, 0) == SOCKET_ERROR) { //Если не удалось получить запрос
  cout << "Listen function failed with error:" << WSAGetLastError() << endl;
  return -1;
 }
 cout << "Listening for incoming connections...." << endl; 

 char buffer[1024]; //Создать буфер для данных
 int clientAddrSize = sizeof(clientAddr); //Инициализировать адерс клиента
 if ((client = accept(server, (SOCKADDR*)&clientAddr, &clientAddrSize)) != INVALID_SOCKET) {
  //Если соединение установлено
  cout << "Client connected!" << endl;
  cout << "Now you can use our live chat application. " << "Enter \"exit\" to disconnect" << endl;

  DWORD tid; //Идентификатор
  HANDLE t1 = CreateThread(NULL, 0, serverReceive, &client, 0, &tid); //Создание потока для получения данных
  if (t1 == NULL) { //Ошибка создания потока
   cout << "Thread Creation Error: " << WSAGetLastError() << endl;
  }
  HANDLE t2 = CreateThread(NULL, 0, serverSend, &client, 0, &tid); //Создание потока для отправки данных
  if (t2 == NULL) {
   cout << "Thread Creation Error: " << WSAGetLastError() << endl;
  }

  WaitForSingleObject(t1, INFINITE);
  WaitForSingleObject(t2, INFINITE);

  closesocket(client); //Закрыть сокет
  if (closesocket(server) == SOCKET_ERROR) { //Ошибка закрытия сокета
   cout << "Close socket failed with error: " << WSAGetLastError() << endl;
   return -1;
  }
  WSACleanup();
 }
}

Я выбрал в стандартной панели инструментов конфигурацию Debug - X64. Если нужно, разрешить приложению работать с сетью, подтверждаем это.

Разрешить консольному приложению сервера доступ к сети
Разрешить консольному приложению сервера доступ к сети

Аналогично добавляем файл Source.cpp в приложение Client, вот его полный исходный код, так как он, в сущности, очень похож, можно без столь подробных комментариев:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream> 
#include <cstdio> 
#include <cstring> 
#include <winsock2.h> 
#pragma comment(lib, "WS2_32.lib")
using namespace std;

DWORD WINAPI clientReceive(LPVOID lpParam) { //Получение данных от сервера
 char buffer[1024] = { 0 };
 SOCKET server = *(SOCKET*)lpParam;
 while (true) {
  if (recv(server, buffer, sizeof(buffer), 0)  == SOCKET_ERROR) {
   cout << "recv function failed with error: " << WSAGetLastError() << endl;
   return -1;
  }
  if (strcmp(buffer, "exit\n") == 0) {
   cout << "Server disconnected." << endl;
   return 1;
  }
  cout << "Server: " << buffer << endl;
  memset(buffer, 0, sizeof(buffer));
 }
 return 1;
}

DWORD WINAPI clientSend(LPVOID lpParam) { //Отправка данных на сервер
 char buffer[1024] = { 0 };
 SOCKET server = *(SOCKET*)lpParam;
 while (true) {
  fgets(buffer, 1024, stdin);
  if (send(server, buffer, sizeof(buffer), 0) == SOCKET_ERROR) {
   cout << "send failed with error: " << WSAGetLastError() << endl;
   return -1;
  }
  if (strcmp(buffer, "exit") == 0) {
   cout << "Thank you for using the application" << endl;
   break;
  }
 }
 return 1;
}

int main() {
 WSADATA WSAData;
 SOCKET server;
 SOCKADDR_IN addr;
 WSAStartup(MAKEWORD(2, 0), &WSAData);
 if ((server = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
  cout << "Socket creation failed with error: " << WSAGetLastError() << endl;
  return -1;
 }
 
 addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //коннект к серверу
 addr.sin_family = AF_INET;
 addr.sin_port = htons(5555); //порт
 if (connect(server, (SOCKADDR*)&addr, sizeof(addr)) == SOCKET_ERROR) {
  cout << "Server connection failed with error: " << WSAGetLastError() << endl;
  return -1;
 }
 
 cout << "Connected to server!" << endl;
 cout << "Now you can use our live chat application. "  << " Enter \"exit\" to disconnect" << endl;
 
 DWORD tid;
 HANDLE t1 = CreateThread(NULL, 0, clientReceive, &server, 0, &tid);
 if (t1 == NULL) cout << "Thread creation error: " << GetLastError();
 HANDLE t2 = CreateThread(NULL, 0, clientSend, &server, 0, &tid);
 if (t2 == NULL) cout << "Thread creation error: " << GetLastError();

 WaitForSingleObject(t1, INFINITE);
 WaitForSingleObject(t2, INFINITE);
 closesocket(server);
 WSACleanup();
}

Обратите внимание на директивы покдлючения библиотек в начале обоих листингов.

В Обозревателе Решений жмём правую кнопку на заголовке решения (самого верхнего уровня), выбираем "Назначить запускаемые проекты", подтверждаем запуск нескольких проектов.

Запуск нескольких проектов из решения
Запуск нескольких проектов из решения

Наши клиент и сервер могут обмениваться сообщениями в режиме чата, конечно, функционал нетрудно расширить. Работа приложения предполагает, что у вас есть нужные компоненты в системе и настройки стандартны, в частности, доступен стандартный "локалхост" с IP-адресом 127.0.0.1

Проект в работе
Проект в работе

 Скачать это решение в архиве .zip, главная папка решения уже создана в архиве (6 Кб)

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

28.07.2020, 01:02 [23542 просмотра]


теги: windows c++ программирование чат сервер сеть

показать комментарии (3)