C++ Вопрос Как правильно обрабатывать отключение клиента от сервера?

Начинающий
Статус
Оффлайн
Регистрация
22 Дек 2018
Сообщения
360
Реакции[?]
21
Поинты[?]
0
Всем Привет, в общем, делаю курсовую, почти доделал в принципе остался последний момент.
При отключении одного из клиентов от сервера у остальных начинает спамить в консоли последнее сообщение.

я сделал пример, но он не очень корректно работает
if (recv(Connections[index], msg, sizeof(msg), NULL) > 0) и дальше, находится строчка в ProcessPacket

C++:
#include <iostream>// Стандартная библиотека для работы большей части кода, так же там есть ввод и вывод, как раз то что нам нужно!
//#include <Windows.h> // Windows. h нужен для того, что бы ты мог использовать в своей программе функционал, предоставляемый операционной системой (Windows 95, 98, NT, 2000, XP).
//#include <TlHelp32.h> // Данная библиотека нужна для того что бы мы могли работать с разными процессами.
#include <conio.h>
#include <winsock2.h>// Для роботы с сетью в Windows есть специальная библиотека Win Сокет, есть 2 версии данной библиотеки: 1 и 2, я буду использовать 2-ю, так как она более свежая и там больше функций для работы с сетью.
#pragma comment(lib, "ws2_32.lib") // без pragma comment'а мы не сможем получить доступ к некоторым функциям.
#include <thread>
#include <WS2tcpip.h>
#include <vector>
#pragma warning(disable: 4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;

SOCKET Connections[100];
int Counter = 0;

enum Packet {
    P_ChatMessage,
    P_Test
};

class Color
{
public:
    Color(int desiredColor) {
        consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        color = desiredColor;
    }

    friend ostream& operator<<(ostream& ss, Color obj) {
        SetConsoleTextAttribute(obj.consoleHandle, obj.color);
        return ss;
    }
private:
    int color;
    HANDLE consoleHandle;
    /*
    0 = чёрный
    1 = синий
    2 = зелёный
    3 = светло-синий
    4 = красный
    5 = фиолетовый
    6 = золотой
    7 = белый
    */
};

bool ProcessPacket(int index, Packet packettype) {
    switch (packettype) {
    case P_ChatMessage:
    {
        int msg_size;
        recv(Connections[index], (char*)&msg_size, sizeof(int), NULL);
        char* msg = new char[msg_size + 1];
        msg[msg_size] = '\0';
        recv(Connections[index], msg, msg_size, NULL);
        //if (recv(Connections[index], msg, sizeof(msg), NULL) > 0) {
            for (int i = 0; i < Counter; i++) {
                if (i == index || Connections[i] == INVALID_SOCKET) {
                    continue;
                }

                Packet msgtype = P_ChatMessage;
                send(Connections[i], (char*)&msgtype, sizeof(Packet), NULL);
                send(Connections[i], (char*)&msg_size, sizeof(int), NULL);
                send(Connections[i], msg, msg_size, NULL);
            }
        //}
        /*else {
            ::closesocket(Connections[index]);
            Connections[index] = INVALID_SOCKET;
            return 1;
        }*/
        delete[] msg;
        break;
    }
    default:
        cout << "Unrecognized packet: " << packettype << endl;
        break;
    }
    return true;
}

void ClientHandler(int index) {
    Packet packettype;
    while (true) {
        recv(Connections[index], (char*)&packettype, sizeof(Packet), NULL);
        
        if (!ProcessPacket(index, packettype)) {
            break;
        }
        //std::cout << packettype;
    }
    closesocket(Connections[index]);
}

void WinSoket()
{
    cout << "Wait on the Client(s)..." << endl;
    // Прежде чем начать работу с сетью, нужно загрузить необходимую версию библиотеки. Если этого не сделать то любой пользователь в сетевой функции вернёт ошибку.
    // Для загрузки библиотеки используется функция WSAStartUp. Но для начала нам необходимо создать структуру WSAData
    WSAData wsaData; // Затем создаем переменную WORD
    WORD DLLVersion = MAKEWORD(2, 1); // Это запрашиваемая версия библиотеки WinSoc, она нам еще понадобиться для того что бы загрузить библиотеку.
    // И так давайте по подробнее про функцию WSAStartUp.
    //Первым параметром передается Запрашиваемая версия Библиотеки. Вторым параметром сюда передается ссылка на структуру WSAData.
    // Сразу же после попытки загрузить библиотеку нужно сделать проверку. Если библиотека загрузилась удачно, то она вернет значение 0.
    // Теперь нам нужно написать проверку Если WSAStartUP не вернет 0 то мы просто выйдем из функции main и выведем ошибку, о том что библиотека не загрузилась.
    if (WSAStartup(DLLVersion, &wsaData) != 0) {
        cout << "Error!!" << endl;
        Sleep(1000);
        exit(1);
    }// И так мы записали функцию библиотеки и проверили ее, вернет ли она ошибку, если да, то мы выходим из функции.
    // После загрузки библиотеки, необходимо заполнить информацию об адрессе Сокета.
    SOCKADDR_IN addr; // Структура SOCKADDR предназначена для хранение адресса. Для Имя протоколов используется имя SOCKADDR_IN, я назвал ее addr.
    int sizeofaddr = sizeof(addr);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // sin_addr это структура SOCKADDR_IN, которая хранит ip address, я указал здесь localhost.
    addr.sin_port = htons(1111); // sin_port - порт для идентификации программы с поступающими данными, я выбрал порт 1111, можно указать любой порт, главное что бы он не был зарезервирован другой программой.
    addr.sin_family = AF_INET; // sin_family - семейство протоколов. Для интернет протоколов указывается константа AF_INET.
    // Что бы 2 комп'ютера смогли установить соединение, один из них должен запустить прослушивание на опредёленном порту.
    SOCKET sListen = socket(AF_INET, SOCK_STREAM, NULL); // И так, я создал сокет и назвал его sListen, которому присвоил результат на выполнение функции сокета, который передал 3 параметра.
    // Первый параметр AF_INET, оначает что будет использоваться семейство интернет протоколов
    // Второй параметр SOCK_STREAM указывает на протокол устанавливающий соединение.
    // Третий параметр нам пока не нужен, по этому я указал NULL.

    // Теперь нам нужно привязать аддресс Сокета, для привязки аддресса сокета используется спец. функция, называется она bind, пишеться след. образом
    bind(sListen, (SOCKADDR*)&addr, sizeof(addr));// Так, что за параметры я сюда передал?
    // Первый параметр это предварительно созданный сокет, в моем случаи это sListen
    // Второй параметр это указатель на структуру SOCK_ADDR_IN
    // Третий параметр это размер структуры SOCK_ADDR_IN
    // После того как локальный аддресс и порт привязаны к сокету, нужно приступить к прослушиванию порта в ожидании соединения со стороны клиента.
    // Для этого служит функция Listen которая выглядит след. образом.
    listen(sListen, SOMAXCONN); // И так, какие я параметры сюда передал:
    // Первый параметр этто все тот же сокет который был создан и к которому был привязан адресс.
    // По этим данным функция определит по какому порту нужно запустить прослушивание
    // Второй параметр это максимально допустимое число запросов ожидающих обработки.
    // Допустим, что я указал здесь значение 3,а мне пришло 5 запросов на соединение от разных клиентов. Только 3 из них станут в очередь, а остальные получат ошибку.
    // Теперь мы фактически принимаем это соединение, по этому я собираюсь запустить новый сокет что бы удерживать соединение с клиентом.
    SOCKET newConnection;
    for (int i = 0; i < 100; i++)
    {
        newConnection = accept(sListen, (SOCKADDR*)&addr, &sizeofaddr);
        // И так я создал новый сокет, назвал его newConnection, присвоил ему результат ввыполнения функции accept в которую передал 3 параметра.
        // Первый параметр это только что созданый и запущенный на прослушивание сокет.
        // Второй параметр это указатель на структуру типа SOCKADDR.
        // Третий параметр это размер на структуры SOCKADDR. Но так как у меня в 3 параметре выбивает ошибку, мне придеться создать новую переменную и присвоить туда размер структуры SOCKADDR, после указать в качестве 3-го параметра функции accept, ссылку на эту переменную.

        // После выполнения функции accept, 2-й параметр addr будет держать сведения об ип адресе клиента, который призвал подключения, эти данные можно использовать для контроля доступа к серверу по ip-адресу.
        // Функция accept возвращает указатель на новый сокет, который можно использовать для общения с клиентом. Если функция 0-е значение, значит клиент не сможет подключится к серверу.
        // на этот случай нам нужно написать проверку

        if (newConnection == 0) { // Если newConnection не будет равен 0, то мы удачно установили соединение с пользователем, выведем в консоль сообщение, что пользователь присоединился.
            cout << "Error #2" << endl;
        }
        else {
            cout << Color(7) << "Client " << Color(4) << getenv("USERNAME") << Color(7) << " Connected!" << endl;
            string msg = " Test message automatically sent";
            int msg_size = msg.size();
            Packet msgtype = P_ChatMessage;
            send(newConnection, (char*)&msgtype, sizeof(Packet), NULL);
            send(newConnection, (char*)&msg_size, sizeof(int), NULL);
            send(newConnection, msg.c_str(), msg_size, NULL);

            Connections[i] = newConnection;
            Counter++;
            CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ClientHandler, (LPVOID)(i), NULL, NULL);

            Packet testpacket = P_Test;
            send(newConnection, (char*)&testpacket, sizeof(Packet), NULL);
        }
    }
}

int main() {
    WinSoket();

    system("pause");
    return 0;
}
 
Пользователь
Статус
Оффлайн
Регистрация
26 Окт 2017
Сообщения
519
Реакции[?]
95
Поинты[?]
2K
Всем Привет, в общем, делаю курсовую, почти доделал в принципе остался последний момент.
При отключении одного из клиентов от сервера у остальных начинает спамить в консоли последнее сообщение.

я сделал пример, но он не очень корректно работает
if (recv(Connections[index], msg, sizeof(msg), NULL) > 0) и дальше, находится строчка в ProcessPacket

C++:
#include <iostream>// Стандартная библиотека для работы большей части кода, так же там есть ввод и вывод, как раз то что нам нужно!
//#include <Windows.h> // Windows. h нужен для того, что бы ты мог использовать в своей программе функционал, предоставляемый операционной системой (Windows 95, 98, NT, 2000, XP).
//#include <TlHelp32.h> // Данная библиотека нужна для того что бы мы могли работать с разными процессами.
#include <conio.h>
#include <winsock2.h>// Для роботы с сетью в Windows есть специальная библиотека Win Сокет, есть 2 версии данной библиотеки: 1 и 2, я буду использовать 2-ю, так как она более свежая и там больше функций для работы с сетью.
#pragma comment(lib, "ws2_32.lib") // без pragma comment'а мы не сможем получить доступ к некоторым функциям.
#include <thread>
#include <WS2tcpip.h>
#include <vector>
#pragma warning(disable: 4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;

SOCKET Connections[100];
int Counter = 0;

enum Packet {
    P_ChatMessage,
    P_Test
};

class Color
{
public:
    Color(int desiredColor) {
        consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        color = desiredColor;
    }

    friend ostream& operator<<(ostream& ss, Color obj) {
        SetConsoleTextAttribute(obj.consoleHandle, obj.color);
        return ss;
    }
private:
    int color;
    HANDLE consoleHandle;
    /*
    0 = чёрный
    1 = синий
    2 = зелёный
    3 = светло-синий
    4 = красный
    5 = фиолетовый
    6 = золотой
    7 = белый
    */
};

bool ProcessPacket(int index, Packet packettype) {
    switch (packettype) {
    case P_ChatMessage:
    {
        int msg_size;
        recv(Connections[index], (char*)&msg_size, sizeof(int), NULL);
        char* msg = new char[msg_size + 1];
        msg[msg_size] = '\0';
        recv(Connections[index], msg, msg_size, NULL);
        //if (recv(Connections[index], msg, sizeof(msg), NULL) > 0) {
            for (int i = 0; i < Counter; i++) {
                if (i == index || Connections[i] == INVALID_SOCKET) {
                    continue;
                }

                Packet msgtype = P_ChatMessage;
                send(Connections[i], (char*)&msgtype, sizeof(Packet), NULL);
                send(Connections[i], (char*)&msg_size, sizeof(int), NULL);
                send(Connections[i], msg, msg_size, NULL);
            }
        //}
        /*else {
            ::closesocket(Connections[index]);
            Connections[index] = INVALID_SOCKET;
            return 1;
        }*/
        delete[] msg;
        break;
    }
    default:
        cout << "Unrecognized packet: " << packettype << endl;
        break;
    }
    return true;
}

void ClientHandler(int index) {
    Packet packettype;
    while (true) {
        recv(Connections[index], (char*)&packettype, sizeof(Packet), NULL);
       
        if (!ProcessPacket(index, packettype)) {
            break;
        }
        //std::cout << packettype;
    }
    closesocket(Connections[index]);
}

void WinSoket()
{
    cout << "Wait on the Client(s)..." << endl;
    // Прежде чем начать работу с сетью, нужно загрузить необходимую версию библиотеки. Если этого не сделать то любой пользователь в сетевой функции вернёт ошибку.
    // Для загрузки библиотеки используется функция WSAStartUp. Но для начала нам необходимо создать структуру WSAData
    WSAData wsaData; // Затем создаем переменную WORD
    WORD DLLVersion = MAKEWORD(2, 1); // Это запрашиваемая версия библиотеки WinSoc, она нам еще понадобиться для того что бы загрузить библиотеку.
    // И так давайте по подробнее про функцию WSAStartUp.
    //Первым параметром передается Запрашиваемая версия Библиотеки. Вторым параметром сюда передается ссылка на структуру WSAData.
    // Сразу же после попытки загрузить библиотеку нужно сделать проверку. Если библиотека загрузилась удачно, то она вернет значение 0.
    // Теперь нам нужно написать проверку Если WSAStartUP не вернет 0 то мы просто выйдем из функции main и выведем ошибку, о том что библиотека не загрузилась.
    if (WSAStartup(DLLVersion, &wsaData) != 0) {
        cout << "Error!!" << endl;
        Sleep(1000);
        exit(1);
    }// И так мы записали функцию библиотеки и проверили ее, вернет ли она ошибку, если да, то мы выходим из функции.
    // После загрузки библиотеки, необходимо заполнить информацию об адрессе Сокета.
    SOCKADDR_IN addr; // Структура SOCKADDR предназначена для хранение адресса. Для Имя протоколов используется имя SOCKADDR_IN, я назвал ее addr.
    int sizeofaddr = sizeof(addr);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // sin_addr это структура SOCKADDR_IN, которая хранит ip address, я указал здесь localhost.
    addr.sin_port = htons(1111); // sin_port - порт для идентификации программы с поступающими данными, я выбрал порт 1111, можно указать любой порт, главное что бы он не был зарезервирован другой программой.
    addr.sin_family = AF_INET; // sin_family - семейство протоколов. Для интернет протоколов указывается константа AF_INET.
    // Что бы 2 комп'ютера смогли установить соединение, один из них должен запустить прослушивание на опредёленном порту.
    SOCKET sListen = socket(AF_INET, SOCK_STREAM, NULL); // И так, я создал сокет и назвал его sListen, которому присвоил результат на выполнение функции сокета, который передал 3 параметра.
    // Первый параметр AF_INET, оначает что будет использоваться семейство интернет протоколов
    // Второй параметр SOCK_STREAM указывает на протокол устанавливающий соединение.
    // Третий параметр нам пока не нужен, по этому я указал NULL.

    // Теперь нам нужно привязать аддресс Сокета, для привязки аддресса сокета используется спец. функция, называется она bind, пишеться след. образом
    bind(sListen, (SOCKADDR*)&addr, sizeof(addr));// Так, что за параметры я сюда передал?
    // Первый параметр это предварительно созданный сокет, в моем случаи это sListen
    // Второй параметр это указатель на структуру SOCK_ADDR_IN
    // Третий параметр это размер структуры SOCK_ADDR_IN
    // После того как локальный аддресс и порт привязаны к сокету, нужно приступить к прослушиванию порта в ожидании соединения со стороны клиента.
    // Для этого служит функция Listen которая выглядит след. образом.
    listen(sListen, SOMAXCONN); // И так, какие я параметры сюда передал:
    // Первый параметр этто все тот же сокет который был создан и к которому был привязан адресс.
    // По этим данным функция определит по какому порту нужно запустить прослушивание
    // Второй параметр это максимально допустимое число запросов ожидающих обработки.
    // Допустим, что я указал здесь значение 3,а мне пришло 5 запросов на соединение от разных клиентов. Только 3 из них станут в очередь, а остальные получат ошибку.
    // Теперь мы фактически принимаем это соединение, по этому я собираюсь запустить новый сокет что бы удерживать соединение с клиентом.
    SOCKET newConnection;
    for (int i = 0; i < 100; i++)
    {
        newConnection = accept(sListen, (SOCKADDR*)&addr, &sizeofaddr);
        // И так я создал новый сокет, назвал его newConnection, присвоил ему результат ввыполнения функции accept в которую передал 3 параметра.
        // Первый параметр это только что созданый и запущенный на прослушивание сокет.
        // Второй параметр это указатель на структуру типа SOCKADDR.
        // Третий параметр это размер на структуры SOCKADDR. Но так как у меня в 3 параметре выбивает ошибку, мне придеться создать новую переменную и присвоить туда размер структуры SOCKADDR, после указать в качестве 3-го параметра функции accept, ссылку на эту переменную.

        // После выполнения функции accept, 2-й параметр addr будет держать сведения об ип адресе клиента, который призвал подключения, эти данные можно использовать для контроля доступа к серверу по ip-адресу.
        // Функция accept возвращает указатель на новый сокет, который можно использовать для общения с клиентом. Если функция 0-е значение, значит клиент не сможет подключится к серверу.
        // на этот случай нам нужно написать проверку

        if (newConnection == 0) { // Если newConnection не будет равен 0, то мы удачно установили соединение с пользователем, выведем в консоль сообщение, что пользователь присоединился.
            cout << "Error #2" << endl;
        }
        else {
            cout << Color(7) << "Client " << Color(4) << getenv("USERNAME") << Color(7) << " Connected!" << endl;
            string msg = " Test message automatically sent";
            int msg_size = msg.size();
            Packet msgtype = P_ChatMessage;
            send(newConnection, (char*)&msgtype, sizeof(Packet), NULL);
            send(newConnection, (char*)&msg_size, sizeof(int), NULL);
            send(newConnection, msg.c_str(), msg_size, NULL);

            Connections[i] = newConnection;
            Counter++;
            CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ClientHandler, (LPVOID)(i), NULL, NULL);

            Packet testpacket = P_Test;
            send(newConnection, (char*)&testpacket, sizeof(Packet), NULL);
        }
    }
}

int main() {
    WinSoket();

    system("pause");
    return 0;
}
100% ошибка в логике кода многопоточности сокета
Логично же нет клиента - нет запросов, а нет запросов - нет ответов. А спамишь ты остальным клиентам своей ошибкой которую допустил.
 
Легенда форума
Статус
Онлайн
Регистрация
10 Дек 2018
Сообщения
4,381
Реакции[?]
2,284
Поинты[?]
189K
Всем Привет, в общем, делаю курсовую, почти доделал в принципе остался последний момент.
При отключении одного из клиентов от сервера у остальных начинает спамить в консоли последнее сообщение.

я сделал пример, но он не очень корректно работает
if (recv(Connections[index], msg, sizeof(msg), NULL) > 0) и дальше, находится строчка в ProcessPacket

C++:
#include <iostream>// Стандартная библиотека для работы большей части кода, так же там есть ввод и вывод, как раз то что нам нужно!
//#include <Windows.h> // Windows. h нужен для того, что бы ты мог использовать в своей программе функционал, предоставляемый операционной системой (Windows 95, 98, NT, 2000, XP).
//#include <TlHelp32.h> // Данная библиотека нужна для того что бы мы могли работать с разными процессами.
#include <conio.h>
#include <winsock2.h>// Для роботы с сетью в Windows есть специальная библиотека Win Сокет, есть 2 версии данной библиотеки: 1 и 2, я буду использовать 2-ю, так как она более свежая и там больше функций для работы с сетью.
#pragma comment(lib, "ws2_32.lib") // без pragma comment'а мы не сможем получить доступ к некоторым функциям.
#include <thread>
#include <WS2tcpip.h>
#include <vector>
#pragma warning(disable: 4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;

SOCKET Connections[100];
int Counter = 0;

enum Packet {
    P_ChatMessage,
    P_Test
};

class Color
{
public:
    Color(int desiredColor) {
        consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        color = desiredColor;
    }

    friend ostream& operator<<(ostream& ss, Color obj) {
        SetConsoleTextAttribute(obj.consoleHandle, obj.color);
        return ss;
    }
private:
    int color;
    HANDLE consoleHandle;
    /*
    0 = чёрный
    1 = синий
    2 = зелёный
    3 = светло-синий
    4 = красный
    5 = фиолетовый
    6 = золотой
    7 = белый
    */
};

bool ProcessPacket(int index, Packet packettype) {
    switch (packettype) {
    case P_ChatMessage:
    {
        int msg_size;
        recv(Connections[index], (char*)&msg_size, sizeof(int), NULL);
        char* msg = new char[msg_size + 1];
        msg[msg_size] = '\0';
        recv(Connections[index], msg, msg_size, NULL);
        //if (recv(Connections[index], msg, sizeof(msg), NULL) > 0) {
            for (int i = 0; i < Counter; i++) {
                if (i == index || Connections[i] == INVALID_SOCKET) {
                    continue;
                }

                Packet msgtype = P_ChatMessage;
                send(Connections[i], (char*)&msgtype, sizeof(Packet), NULL);
                send(Connections[i], (char*)&msg_size, sizeof(int), NULL);
                send(Connections[i], msg, msg_size, NULL);
            }
        //}
        /*else {
            ::closesocket(Connections[index]);
            Connections[index] = INVALID_SOCKET;
            return 1;
        }*/
        delete[] msg;
        break;
    }
    default:
        cout << "Unrecognized packet: " << packettype << endl;
        break;
    }
    return true;
}

void ClientHandler(int index) {
    Packet packettype;
    while (true) {
        recv(Connections[index], (char*)&packettype, sizeof(Packet), NULL);
       
        if (!ProcessPacket(index, packettype)) {
            break;
        }
        //std::cout << packettype;
    }
    closesocket(Connections[index]);
}

void WinSoket()
{
    cout << "Wait on the Client(s)..." << endl;
    // Прежде чем начать работу с сетью, нужно загрузить необходимую версию библиотеки. Если этого не сделать то любой пользователь в сетевой функции вернёт ошибку.
    // Для загрузки библиотеки используется функция WSAStartUp. Но для начала нам необходимо создать структуру WSAData
    WSAData wsaData; // Затем создаем переменную WORD
    WORD DLLVersion = MAKEWORD(2, 1); // Это запрашиваемая версия библиотеки WinSoc, она нам еще понадобиться для того что бы загрузить библиотеку.
    // И так давайте по подробнее про функцию WSAStartUp.
    //Первым параметром передается Запрашиваемая версия Библиотеки. Вторым параметром сюда передается ссылка на структуру WSAData.
    // Сразу же после попытки загрузить библиотеку нужно сделать проверку. Если библиотека загрузилась удачно, то она вернет значение 0.
    // Теперь нам нужно написать проверку Если WSAStartUP не вернет 0 то мы просто выйдем из функции main и выведем ошибку, о том что библиотека не загрузилась.
    if (WSAStartup(DLLVersion, &wsaData) != 0) {
        cout << "Error!!" << endl;
        Sleep(1000);
        exit(1);
    }// И так мы записали функцию библиотеки и проверили ее, вернет ли она ошибку, если да, то мы выходим из функции.
    // После загрузки библиотеки, необходимо заполнить информацию об адрессе Сокета.
    SOCKADDR_IN addr; // Структура SOCKADDR предназначена для хранение адресса. Для Имя протоколов используется имя SOCKADDR_IN, я назвал ее addr.
    int sizeofaddr = sizeof(addr);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // sin_addr это структура SOCKADDR_IN, которая хранит ip address, я указал здесь localhost.
    addr.sin_port = htons(1111); // sin_port - порт для идентификации программы с поступающими данными, я выбрал порт 1111, можно указать любой порт, главное что бы он не был зарезервирован другой программой.
    addr.sin_family = AF_INET; // sin_family - семейство протоколов. Для интернет протоколов указывается константа AF_INET.
    // Что бы 2 комп'ютера смогли установить соединение, один из них должен запустить прослушивание на опредёленном порту.
    SOCKET sListen = socket(AF_INET, SOCK_STREAM, NULL); // И так, я создал сокет и назвал его sListen, которому присвоил результат на выполнение функции сокета, который передал 3 параметра.
    // Первый параметр AF_INET, оначает что будет использоваться семейство интернет протоколов
    // Второй параметр SOCK_STREAM указывает на протокол устанавливающий соединение.
    // Третий параметр нам пока не нужен, по этому я указал NULL.

    // Теперь нам нужно привязать аддресс Сокета, для привязки аддресса сокета используется спец. функция, называется она bind, пишеться след. образом
    bind(sListen, (SOCKADDR*)&addr, sizeof(addr));// Так, что за параметры я сюда передал?
    // Первый параметр это предварительно созданный сокет, в моем случаи это sListen
    // Второй параметр это указатель на структуру SOCK_ADDR_IN
    // Третий параметр это размер структуры SOCK_ADDR_IN
    // После того как локальный аддресс и порт привязаны к сокету, нужно приступить к прослушиванию порта в ожидании соединения со стороны клиента.
    // Для этого служит функция Listen которая выглядит след. образом.
    listen(sListen, SOMAXCONN); // И так, какие я параметры сюда передал:
    // Первый параметр этто все тот же сокет который был создан и к которому был привязан адресс.
    // По этим данным функция определит по какому порту нужно запустить прослушивание
    // Второй параметр это максимально допустимое число запросов ожидающих обработки.
    // Допустим, что я указал здесь значение 3,а мне пришло 5 запросов на соединение от разных клиентов. Только 3 из них станут в очередь, а остальные получат ошибку.
    // Теперь мы фактически принимаем это соединение, по этому я собираюсь запустить новый сокет что бы удерживать соединение с клиентом.
    SOCKET newConnection;
    for (int i = 0; i < 100; i++)
    {
        newConnection = accept(sListen, (SOCKADDR*)&addr, &sizeofaddr);
        // И так я создал новый сокет, назвал его newConnection, присвоил ему результат ввыполнения функции accept в которую передал 3 параметра.
        // Первый параметр это только что созданый и запущенный на прослушивание сокет.
        // Второй параметр это указатель на структуру типа SOCKADDR.
        // Третий параметр это размер на структуры SOCKADDR. Но так как у меня в 3 параметре выбивает ошибку, мне придеться создать новую переменную и присвоить туда размер структуры SOCKADDR, после указать в качестве 3-го параметра функции accept, ссылку на эту переменную.

        // После выполнения функции accept, 2-й параметр addr будет держать сведения об ип адресе клиента, который призвал подключения, эти данные можно использовать для контроля доступа к серверу по ip-адресу.
        // Функция accept возвращает указатель на новый сокет, который можно использовать для общения с клиентом. Если функция 0-е значение, значит клиент не сможет подключится к серверу.
        // на этот случай нам нужно написать проверку

        if (newConnection == 0) { // Если newConnection не будет равен 0, то мы удачно установили соединение с пользователем, выведем в консоль сообщение, что пользователь присоединился.
            cout << "Error #2" << endl;
        }
        else {
            cout << Color(7) << "Client " << Color(4) << getenv("USERNAME") << Color(7) << " Connected!" << endl;
            string msg = " Test message automatically sent";
            int msg_size = msg.size();
            Packet msgtype = P_ChatMessage;
            send(newConnection, (char*)&msgtype, sizeof(Packet), NULL);
            send(newConnection, (char*)&msg_size, sizeof(int), NULL);
            send(newConnection, msg.c_str(), msg_size, NULL);

            Connections[i] = newConnection;
            Counter++;
            CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ClientHandler, (LPVOID)(i), NULL, NULL);

            Packet testpacket = P_Test;
            send(newConnection, (char*)&testpacket, sizeof(Packet), NULL);
        }
    }
}

int main() {
    WinSoket();

    system("pause");
    return 0;
}
Дебаггер в руки и давай проверять по шагам проверять, что перед и после этого происходит.
 
Начинающий
Статус
Оффлайн
Регистрация
22 Дек 2018
Сообщения
360
Реакции[?]
21
Поинты[?]
0
Дебаггер в руки и давай проверять по шагам проверять, что перед и после этого происходит.
Проверял, я не нашёл, я могу добавить сюда клиент что бы все могли посмотреть и подсказать в чем проблема
100% ошибка в логике кода многопоточности сокета
Логично же нет клиента - нет запросов, а нет запросов - нет ответов. А спамишь ты остальным клиентам своей ошибкой которую допустил.
Я могу скинуть клиент, что бы вы подсказать могли
 
Пользователь
Статус
Оффлайн
Регистрация
26 Окт 2017
Сообщения
519
Реакции[?]
95
Поинты[?]
2K
Проверял, я не нашёл, я могу добавить сюда клиент что бы все могли посмотреть и подсказать в чем проблема

Я могу скинуть клиент, что бы вы подсказать могли
мог бы уже давно скинуть, а вообще куча примеров есть работающих корректно
давно бы нашел свой косяк
 
Начинающий
Статус
Оффлайн
Регистрация
22 Дек 2018
Сообщения
360
Реакции[?]
21
Поинты[?]
0
мог бы уже давно скинуть, а вообще куча примеров есть работающих корректно
давно бы нашел свой косяк
Прошу прощения, у меня в стане Война и не было возможности после моего сообщения скинуть.
вот код Клиента:

C++:
// Все что нужно это перенести подключенные заголовки, функцию загрузки библиотеки и информацию об аддрессе сокета
#include <iostream>// Стандартная библиотека для работы большей части кода, так же там есть ввод и вывод, как раз то что нам нужно!
//#include <Windows.h> // Windows. h нужен для того, что бы ты мог использовать в своей программе функционал, предоставляемый операционной системой (Windows 95, 98, NT, 2000, XP).
//#include <TlHelp32.h> // Данная библиотека нужна для того что бы мы могли работать с разными процессами.
#include <conio.h>
#include <string> // библиотека для работы со строками.
#include <winsock2.h>// Для роботы с сетью в Windows есть специальная библиотека Win Сокет, есть 2 версии данной библиотеки: 1 и 2, я буду использовать 2-ю, так как она более свежая и там больше функций для работы с сетью.
#pragma comment(lib, "ws2_32.lib") // без pragma comment'а мы не сможем получить доступ к некоторым функциям.
#pragma warning(disable: 4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;

SOCKET Connection;

enum Packet {
    P_ChatMessage,
    P_Test
};

class Color
{
public:
    Color(int desiredColor) {
        consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        color = desiredColor;
    }

    friend ostream& operator<<(ostream& ss, Color obj) {
        SetConsoleTextAttribute(obj.consoleHandle, obj.color);
        return ss;
    }
private:
    int color;
    HANDLE consoleHandle;
    /*
    0 = чёрный
    1 = синий
    2 = зелёный
    3 = светло-синий
    4 = красный
    5 = фиолетовый
    6 = золотой
    7 = белый
    */
};

bool ProcessPacket(Packet packettype) {
    switch (packettype) {
    case P_ChatMessage:
    {
        int msg_size;
        recv(Connection, (char*)&msg_size, sizeof(int), NULL);
        char* msg = new char[msg_size + 1];
        msg[msg_size] = '\0';
        recv(Connection, msg, msg_size, NULL);
        cout << Color(7) << "Client " << Color(4) << getenv("USERNAME") << Color(7) << " say: " << msg << endl;
        delete[] msg;
        break;
    }
    case P_Test:
        cout << "Test packet." << endl;
        break;
    default:
        cout << "Unrecognized packet: " << packettype << endl;
        break;
    }
    return true;
}

void ClientHandler() {
    Packet packettype;
    while (true) {
        recv(Connection, (char*)&packettype, sizeof(Packet), NULL);

        if (!ProcessPacket(packettype)) {
            break;
        }
    }
    closesocket(Connection);
}

int main()
{
    // WSAStartup
    WSAData wsaData;
    WORD DLLVersion = MAKEWORD(2, 1);
    if (WSAStartup(DLLVersion, &wsaData) != 0) {
        cout << "Error!" << endl;
        exit(1);
    }

    SOCKADDR_IN addr;
    int sizeofaddr = sizeof(addr);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(1111);
    addr.sin_family = AF_INET;

    // После чего, в клиенте нам нужно создать новый Сокет, для соединения с сервером.
    Connection = socket(AF_INET, SOCK_STREAM, NULL); // <-- Новый Сокет.
    // Теперь нам нужно попытаться присоединиться к Серверу, делается это вот таким образом:
    if (connect(Connection, (SOCKADDR*)&addr, sizeof(addr)) != 0) { // Вы можете увидеть знакомые параметры,а именно:
        cout << "Failed Connect to server." << endl;
        return 1;
    }cout << "Connected!" << endl;
    // 1-й параметр это Только что созданный сокет
    // 2-й параметр это указатель на структуру SOCKADDR
    // 3-й параметр это размер структуры SOCKADDR

    // На случай если клиент не сможет соединиться с сервером, необходимо написать проверку, выше!

    // Если мы подключимся к серверу, функция Connect вернет 0-е значение, иначе мы выведем ошибку и выйдем из программы.
    // В случаи успеха, мы выведем в консоль сообщение о том что мы подключились к серверу.

    CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ClientHandler, NULL, NULL, NULL);

    string msg1;
    while (true) {
        getline(cin,msg1);// Мы записываем набраную пользователем строку в отдельную переменную типа стринг.
        int msg_size = msg1.size(); // После, задаем переменную типа int msg_size и присваиваем в нее размер этой строки.
        Packet packettype = P_ChatMessage;
        send(Connection, (char*)&packettype, sizeof(Packet), NULL);
        send(Connection, (char*)&msg_size, sizeof(int), NULL); // После посылаем эту строку серверу, что бы он знал размер строки которую он будет принимать
        // В качестве 2-го параметра я передал char на msg_size
        // 3-й параметр это размер msg_size в байтах
        send(Connection, msg1.c_str(), msg_size, NULL);// После, посылаем саму строку
        Sleep(10);
    }

    system("pause");
    return 0;
}
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
22 Дек 2018
Сообщения
360
Реакции[?]
21
Поинты[?]
0
Так что, знает кто в чем проблема?
Помогите пожалуйста с кодом
 
Пользователь
Статус
Оффлайн
Регистрация
26 Окт 2017
Сообщения
519
Реакции[?]
95
Поинты[?]
2K
Всем Привет, в общем, делаю курсовую, почти доделал в принципе остался последний момент.
При отключении одного из клиентов от сервера у остальных начинает спамить в консоли последнее сообщение.

я сделал пример, но он не очень корректно работает
if (recv(Connections[index], msg, sizeof(msg), NULL) > 0) и дальше, находится строчка в ProcessPacket

C++:
#include <iostream>// Стандартная библиотека для работы большей части кода, так же там есть ввод и вывод, как раз то что нам нужно!
//#include <Windows.h> // Windows. h нужен для того, что бы ты мог использовать в своей программе функционал, предоставляемый операционной системой (Windows 95, 98, NT, 2000, XP).
//#include <TlHelp32.h> // Данная библиотека нужна для того что бы мы могли работать с разными процессами.
#include <conio.h>
#include <winsock2.h>// Для роботы с сетью в Windows есть специальная библиотека Win Сокет, есть 2 версии данной библиотеки: 1 и 2, я буду использовать 2-ю, так как она более свежая и там больше функций для работы с сетью.
#pragma comment(lib, "ws2_32.lib") // без pragma comment'а мы не сможем получить доступ к некоторым функциям.
#include <thread>
#include <WS2tcpip.h>
#include <vector>
#pragma warning(disable: 4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;

SOCKET Connections[100];
int Counter = 0;

enum Packet {
    P_ChatMessage,
    P_Test
};

class Color
{
public:
    Color(int desiredColor) {
        consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        color = desiredColor;
    }

    friend ostream& operator<<(ostream& ss, Color obj) {
        SetConsoleTextAttribute(obj.consoleHandle, obj.color);
        return ss;
    }
private:
    int color;
    HANDLE consoleHandle;
    /*
    0 = чёрный
    1 = синий
    2 = зелёный
    3 = светло-синий
    4 = красный
    5 = фиолетовый
    6 = золотой
    7 = белый
    */
};

bool ProcessPacket(int index, Packet packettype) {
    switch (packettype) {
    case P_ChatMessage:
    {
        int msg_size;
        recv(Connections[index], (char*)&msg_size, sizeof(int), NULL);
        char* msg = new char[msg_size + 1];
        msg[msg_size] = '\0';
        recv(Connections[index], msg, msg_size, NULL);
        //if (recv(Connections[index], msg, sizeof(msg), NULL) > 0) {
            for (int i = 0; i < Counter; i++) {
                if (i == index || Connections[i] == INVALID_SOCKET) {
                    continue;
                }

                Packet msgtype = P_ChatMessage;
                send(Connections[i], (char*)&msgtype, sizeof(Packet), NULL);
                send(Connections[i], (char*)&msg_size, sizeof(int), NULL);
                send(Connections[i], msg, msg_size, NULL);
            }
        //}
        /*else {
            ::closesocket(Connections[index]);
            Connections[index] = INVALID_SOCKET;
            return 1;
        }*/
        delete[] msg;
        break;
    }
    default:
        cout << "Unrecognized packet: " << packettype << endl;
        break;
    }
    return true;
}

void ClientHandler(int index) {
    Packet packettype;
    while (true) {
        recv(Connections[index], (char*)&packettype, sizeof(Packet), NULL);
       
        if (!ProcessPacket(index, packettype)) {
            break;
        }
        //std::cout << packettype;
    }
    closesocket(Connections[index]);
}

void WinSoket()
{
    cout << "Wait on the Client(s)..." << endl;
    // Прежде чем начать работу с сетью, нужно загрузить необходимую версию библиотеки. Если этого не сделать то любой пользователь в сетевой функции вернёт ошибку.
    // Для загрузки библиотеки используется функция WSAStartUp. Но для начала нам необходимо создать структуру WSAData
    WSAData wsaData; // Затем создаем переменную WORD
    WORD DLLVersion = MAKEWORD(2, 1); // Это запрашиваемая версия библиотеки WinSoc, она нам еще понадобиться для того что бы загрузить библиотеку.
    // И так давайте по подробнее про функцию WSAStartUp.
    //Первым параметром передается Запрашиваемая версия Библиотеки. Вторым параметром сюда передается ссылка на структуру WSAData.
    // Сразу же после попытки загрузить библиотеку нужно сделать проверку. Если библиотека загрузилась удачно, то она вернет значение 0.
    // Теперь нам нужно написать проверку Если WSAStartUP не вернет 0 то мы просто выйдем из функции main и выведем ошибку, о том что библиотека не загрузилась.
    if (WSAStartup(DLLVersion, &wsaData) != 0) {
        cout << "Error!!" << endl;
        Sleep(1000);
        exit(1);
    }// И так мы записали функцию библиотеки и проверили ее, вернет ли она ошибку, если да, то мы выходим из функции.
    // После загрузки библиотеки, необходимо заполнить информацию об адрессе Сокета.
    SOCKADDR_IN addr; // Структура SOCKADDR предназначена для хранение адресса. Для Имя протоколов используется имя SOCKADDR_IN, я назвал ее addr.
    int sizeofaddr = sizeof(addr);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // sin_addr это структура SOCKADDR_IN, которая хранит ip address, я указал здесь localhost.
    addr.sin_port = htons(1111); // sin_port - порт для идентификации программы с поступающими данными, я выбрал порт 1111, можно указать любой порт, главное что бы он не был зарезервирован другой программой.
    addr.sin_family = AF_INET; // sin_family - семейство протоколов. Для интернет протоколов указывается константа AF_INET.
    // Что бы 2 комп'ютера смогли установить соединение, один из них должен запустить прослушивание на опредёленном порту.
    SOCKET sListen = socket(AF_INET, SOCK_STREAM, NULL); // И так, я создал сокет и назвал его sListen, которому присвоил результат на выполнение функции сокета, который передал 3 параметра.
    // Первый параметр AF_INET, оначает что будет использоваться семейство интернет протоколов
    // Второй параметр SOCK_STREAM указывает на протокол устанавливающий соединение.
    // Третий параметр нам пока не нужен, по этому я указал NULL.

    // Теперь нам нужно привязать аддресс Сокета, для привязки аддресса сокета используется спец. функция, называется она bind, пишеться след. образом
    bind(sListen, (SOCKADDR*)&addr, sizeof(addr));// Так, что за параметры я сюда передал?
    // Первый параметр это предварительно созданный сокет, в моем случаи это sListen
    // Второй параметр это указатель на структуру SOCK_ADDR_IN
    // Третий параметр это размер структуры SOCK_ADDR_IN
    // После того как локальный аддресс и порт привязаны к сокету, нужно приступить к прослушиванию порта в ожидании соединения со стороны клиента.
    // Для этого служит функция Listen которая выглядит след. образом.
    listen(sListen, SOMAXCONN); // И так, какие я параметры сюда передал:
    // Первый параметр этто все тот же сокет который был создан и к которому был привязан адресс.
    // По этим данным функция определит по какому порту нужно запустить прослушивание
    // Второй параметр это максимально допустимое число запросов ожидающих обработки.
    // Допустим, что я указал здесь значение 3,а мне пришло 5 запросов на соединение от разных клиентов. Только 3 из них станут в очередь, а остальные получат ошибку.
    // Теперь мы фактически принимаем это соединение, по этому я собираюсь запустить новый сокет что бы удерживать соединение с клиентом.
    SOCKET newConnection;
    for (int i = 0; i < 100; i++)
    {
        newConnection = accept(sListen, (SOCKADDR*)&addr, &sizeofaddr);
        // И так я создал новый сокет, назвал его newConnection, присвоил ему результат ввыполнения функции accept в которую передал 3 параметра.
        // Первый параметр это только что созданый и запущенный на прослушивание сокет.
        // Второй параметр это указатель на структуру типа SOCKADDR.
        // Третий параметр это размер на структуры SOCKADDR. Но так как у меня в 3 параметре выбивает ошибку, мне придеться создать новую переменную и присвоить туда размер структуры SOCKADDR, после указать в качестве 3-го параметра функции accept, ссылку на эту переменную.

        // После выполнения функции accept, 2-й параметр addr будет держать сведения об ип адресе клиента, который призвал подключения, эти данные можно использовать для контроля доступа к серверу по ip-адресу.
        // Функция accept возвращает указатель на новый сокет, который можно использовать для общения с клиентом. Если функция 0-е значение, значит клиент не сможет подключится к серверу.
        // на этот случай нам нужно написать проверку

        if (newConnection == 0) { // Если newConnection не будет равен 0, то мы удачно установили соединение с пользователем, выведем в консоль сообщение, что пользователь присоединился.
            cout << "Error #2" << endl;
        }
        else {
            cout << Color(7) << "Client " << Color(4) << getenv("USERNAME") << Color(7) << " Connected!" << endl;
            string msg = " Test message automatically sent";
            int msg_size = msg.size();
            Packet msgtype = P_ChatMessage;
            send(newConnection, (char*)&msgtype, sizeof(Packet), NULL);
            send(newConnection, (char*)&msg_size, sizeof(int), NULL);
            send(newConnection, msg.c_str(), msg_size, NULL);

            Connections[i] = newConnection;
            Counter++;
            CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ClientHandler, (LPVOID)(i), NULL, NULL);

            Packet testpacket = P_Test;
            send(newConnection, (char*)&testpacket, sizeof(Packet), NULL);
        }
    }
}

int main() {
    WinSoket();

    system("pause");
    return 0;
}
Проблема спама клиенту 65-67 строка, а вообще всё сделано плохо и поэтому так работает. Не говорю уже об оптимизации. 1 запрос 1 ответ ты же делая 1 запрос хуячишь минимум 3 ответа.
 
Начинающий
Статус
Оффлайн
Регистрация
22 Дек 2018
Сообщения
360
Реакции[?]
21
Поинты[?]
0
Проблема спама клиенту 65-67 строка, а вообще всё сделано плохо и поэтому так работает. Не говорю уже об оптимизации. 1 запрос 1 ответ ты же делая 1 запрос хуячишь минимум 3 ответа.
Подскажи как лучше сделать, или кодом если можно, я любой помощи буду рад
И где именно 65-67 строчка? Клиент или сервер?
И как мне все таки кодом исправить отключение, я ж собственно эте тему и создал, так как не знаю как самому исправить
 
Keine panik!
Эксперт
Статус
Оффлайн
Регистрация
29 Апр 2020
Сообщения
812
Реакции[?]
417
Поинты[?]
49K
я сделал пример, но он не очень корректно работает
Во первых, нужно проверять всю инициализацию, каждая функция сокетов беркли может зафейлиться, и это надо учитывать.
Во вторых, нужно гораздо лучше знать сокеты, читать о каждой функции, все далеко не так просто:
1. То что ты отправляешь данные в 3 запроса, вовсе не значит что после каждого send() они действительно будут отправляться, это зависит от реализации и она может ждать пока наполнится внутренний буффер, прежде чем отправить их. Поэтому отправляй в один запрос и обязательно проверяй что не произошло ошибки.
2. send() возвращает колво отправленных байт, это число может быть меньше, чем ты отправил, учитывай это. И так же это означает что данные только отправлены, но не то что они приняты.
3. Данные отправленный в один send() вовсе не обязательно придут в один recv(), может быть такое что несколько send() придут в один recv(), или один send() придет в несколько recv() если достаточно большие данные. Поэтому правильно принимать данные в цикле пока они приходят.
4. TCP протокол тебе гарантирует только то, что данные придут в том же порядке и один раз, в отличии от UDP где данные могут приходить и несколько раз и в произвольном порядке.
 
Начинающий
Статус
Оффлайн
Регистрация
22 Дек 2018
Сообщения
360
Реакции[?]
21
Поинты[?]
0
Во первых, нужно проверять всю инициализацию, каждая функция сокетов беркли может зафейлиться, и это надо учитывать.
Во вторых, нужно гораздо лучше знать сокеты, читать о каждой функции, все далеко не так просто:
1. То что ты отправляешь данные в 3 запроса, вовсе не значит что после каждого send() они действительно будут отправляться, это зависит от реализации и она может ждать пока наполнится внутренний буффер, прежде чем отправить их. Поэтому отправляй в один запрос и обязательно проверяй что не произошло ошибки.
2. send() возвращает колво отправленных байт, это число может быть меньше, чем ты отправил, учитывай это. И так же это означает что данные только отправлены, но не то что они приняты.
3. Данные отправленный в один send() вовсе не обязательно придут в один recv(), может быть такое что несколько send() придут в один recv(), или один send() придет в несколько recv() если достаточно большие данные. Поэтому правильно принимать данные в цикле пока они приходят.
4. TCP протокол тебе гарантирует только то, что данные придут в том же порядке и один раз, в отличии от UDP где данные могут приходить и несколько раз и в произвольном порядке.
Я понимаю, но мне нужна помощь с кодом как обрабатывать отключение, я не собирался прям масштабный проект писать на плюсах чисто для курсовой, он по факту готов.(кроме отключения клиента от сервера)
Но эта информация мне была очень полезна, Спасибо.
 
Сверху Снизу