• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

question-closed c/cpp socket potrzebna opinia

Object Storage Arubacloud
0 głosów
289 wizyt
pytanie zadane 24 marca 2023 w C i C++ przez edwardkraweznik Dyskutant (9,930 p.)
zamknięte 11 czerwca 2023 przez edwardkraweznik

Witajcie.

Napisałem prosty server sock. Potrzebuję opinii kogoś kto ma doświadczenie w pisaniu socketów wielowątkowych tryb blokujący.

Czy poniższe rozwiązanie jest ok? może warto coś zmienić...

Projekt do pobrania: https://craws.pl/srv.7z

server diała pod linuxem, można testować telnetem łącząc się na port 2121

main.hpp

#ifndef _CRAWS_MAIN_HPP_
#define _CRAWS_MAIN_HPP_

//----------------------------------------------------------------------------------------------------

#include <thread>

#include "server_socket.hpp"
#include "client_socket.hpp"

//----------------------------------------------------------------------------------------------------

#endif

//----------------------------------------------------------------------------------------------------

main.cpp

#include "main.hpp"

//----------------------------------------------------------------------------------------------------

#define DEF_SERWER_PORT 2121
#define DEF_SERWER_IP "127.0.0.1"

#define DEF_MAX_CONNECTION 16 // --- Maksymalna ilość połączeń oczekujących

//----------------------------------------------------------------------------------------------------

int main()
{
    ServerSock serv_sock(DEF_SERWER_IP, DEF_SERWER_PORT, DEF_MAX_CONNECTION);

    while(true)
    {

        std::cout << "--- while ---" << std::endl;

        struct sockaddr_in client = { };

        int new_sock = accept(serv_sock.sock,(struct sockaddr *) &client, &serv_sock.len);

        //---------------------------------------------------------------------------------------------

        if(new_sock < 0)
        {

            perror( "accept() ERROR" );
            continue;

        }

        //---------------------------------------------------------------------------------------------

        else
        {

            ClientSock *c_s = new ClientSock(new_sock);
            (void)c_s; // disable - warning: unused variable...

        }

        //---------------------------------------------------------------------------------------------

    }
}

//----------------------------------------------------------------------------------------------------

server_socket.hpp

#ifndef _CRAWS_SERVER_SOCKET_HPP_
#define _CRAWS_SERVER_SOCKET_HPP_

//----------------------------------------------------------------------------------------------------

#include <sys/socket.h>
#include <arpa/inet.h>

#include <unistd.h>

#include <iostream>

//----------------------------------------------------------------------------------------------------

class ServerSock
{
    public:

        ServerSock(const std::string &ip_addr, const unsigned int &port, const unsigned int &max_conn);
        ~ServerSock();

        int sock; // <--- Gniazdo/Socket
        socklen_t len;

};

//----------------------------------------------------------------------------------------------------

#endif

//----------------------------------------------------------------------------------------------------

server_socket.cpp

#include "server_socket.hpp"

//----------------------------------------------------------------------------------------------------

ServerSock::ServerSock(const std::string &ip_addr, const unsigned int &port, const unsigned int &max_conn)
{
    struct sockaddr_in serwer =
    {
        .sin_family = AF_INET,
        .sin_port = htons(port)
    };

    if(inet_pton(AF_INET, ip_addr.c_str(), &serwer.sin_addr) <= 0)
    {
        std::cout << "err:server_socket:1:socket create" << std::endl;
    }

    sock = socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    if(sock < 0)
    {
        std::cout << "err:server_socket:2:socket create" << std::endl;
    }

    len = sizeof(serwer);

    if(bind(sock,(struct sockaddr *) &serwer, len) < 0)
    {
        std::cout << "err:server_socket:4:socket create" << std::endl;
    }

    if(listen(sock, max_conn) < 0)
    {
        std::cout << "err:server_socket:5:socket create" << std::endl;
    }
}

//----------------------------------------------------------------------------------------------------

ServerSock::~ServerSock()
{
    shutdown(sock, SHUT_RDWR);
    close(sock);
}

//----------------------------------------------------------------------------------------------------

client_socket.hpp

#ifndef _CRAWS_CLIENT_SOCKET_HPP_
#define _CRAWS_CLIENT_SOCKET_HPP_

//----------------------------------------------------------------------------------------------------

#include <cstring>

#include <sys/socket.h>

#include <thread>

#include <unistd.h>

#include <iostream>
#include <sstream>

//----------------------------------------------------------------------------------------------------

class ClientSock
{
    public:

        ClientSock(const int &new_sock);

        int fd;

        std::string msg; // <--- Bufor msg z postfixa

        int validate(const std::string &msg);

        ~ClientSock();

};

//----------------------------------------------------------------------------------------------------

#endif

//----------------------------------------------------------------------------------------------------

client_socket.cpp

#include "client_socket.hpp"

//----------------------------------------------------------------------------------------------------

/*
 * Maksymalny rozmiar tablicy odczytu
 */
#define DEF_MAX_MSG_LEN 2048

//----------------------------------------------------------------------------------------------------

ClientSock::ClientSock(const int &new_sock)
{

    fd = dup(new_sock); // <--- Skopiuj deskryptor plik/gniazdo
    close(new_sock);

    std::cout << "--- discriptor --- ---" << fd << std::endl;

    std::thread th([this]()
    {
        /*
         * Koniecznie zastosuj break gdy ERR
         */
        while(true)
        {

            char buffer[DEF_MAX_MSG_LEN] = { };

            if(recv(fd, buffer, sizeof(buffer)-1, 0) <= 0)
            {

                std::cout << "--- disconnected --- ---" << std::endl;

                delete this;
                break;

            }

            msg += buffer;

            std::cout << buffer << std::endl;

            /*
             *
             *
             */

            if (msg.find("\n\n") != std::string::npos) // --- pobrano cały bufor socket
            {
                /*
                 * Opis zwracanego kodu - int ret = validate(msg);
                 *
                 * 0 = walidacja ok
                 *
                 */

                //int ret = validate(msg); // --- walidacja

                //----------------------------------------------------------------------------

                //const char* msgsnd = "action=dunno\n\n";

                std::string msg_snd = "action=dunno\n\n";

                send(fd, msg_snd.c_str(), msg_snd.size(), 0);


                std::cout << msg << std::endl;

                msg.clear(); // --- wyczyść zmienną msg globalna dla klasy
            }

        }
    });

    std::cout << "--- odłączenie wątku" << std::endl;
    th.detach();

}

//----------------------------------------------------------------------------------------------------

int ClientSock::validate(const std::string &msg)
{
    int ret = 0;

    std::istringstream s_str(msg);

    std::string line;

    while (std::getline(s_str, line))
    {

        std::string token = line.substr(0, line.find("=")); // token is "scott"

        std::cout << "1 " << token << std::endl;

    }

    return ret;
}

//----------------------------------------------------------------------------------------------------

ClientSock::~ClientSock()
{
    shutdown(fd, SHUT_RDWR);
    close(fd);
    std::cout << "--- destruktor " << std::endl;
}

//----------------------------------------------------------------------------------------------------

 

komentarz zamknięcia: rozwiązane
komentarz 11 czerwca 2023 przez edwardkraweznik Dyskutant (9,930 p.)
Jeszcze takie maleńkie pytanie:

Ten socket działa już i na thread i na select

przy select mam problem z zablokowaniem przez jednego klienta wszystkich pozostałych. Dzieje się to w momencie gdy przy jednym z podłączonych klientów serwer ma wykonać sporo operacji, które zajmują powiedzmy 1s. reszta w tym czasie jest zablokowana.

W przypadku wielu wątków nie ma problemu.

Jest na to jakiś sposób?
komentarz 11 czerwca 2023 przez Oscar Nałogowiec (29,290 p.)
Nie robić operacji które długo trwają. Jak się da podzielić na krótsze fragmenty.
komentarz 11 czerwca 2023 przez edwardkraweznik Dyskutant (9,930 p.)
ok dziękuję.

Pozdrawiam

1 odpowiedź

0 głosów
odpowiedź 25 marca 2023 przez j23 Mędrzec (194,920 p.)
wybrane 25 marca 2023 przez edwardkraweznik
 
Najlepsza

Na szybko:

  • skoro masz klasę ServerSock, to dlaczego ten accept jest poza nią? Zrób metodę ServerSock::Accept, która zwraca obiekt klasy ClientSock, wtedy będzie większy sens klasy ServerSock, niż jest teraz.
  • klasa ClientSock jest niszczona w wątku, zatem nie powinno być możliwości tworzenia jej na stosie, ergo: konstruktory powinny być prywatne. Zrób statyczną metodę tworzącą obiekt tej klasy, ewentualnie zaprzyjaźnij ją z ServerSock, która wspomnianą metodą Accept zwróci wskaźnik.
  • brak synchronizowanego dostępu do ClientSock::msg, a to proszenie się o problemy. Użyj muteksa.

 

komentarz 28 marca 2023 przez edwardkraweznik Dyskutant (9,930 p.)
edycja 28 marca 2023 przez edwardkraweznik
Z ciekawości sprawdziłem ile wątków tworzy apache. Wychodzi 52 na proces.

Znalazłem także taki art: https://superuser.com/questions/1024251/how-can-apache-http-server-allow-multiple-connections-to-port-80

 

root@crw0:~# ps aux | grep apa
root         680  0.0  0.4  12604  9432 ?        Ss   mar24   0:16 /usr/sbin/apache2 -k start
www-data   23488  0.0  0.8 966024 16408 ?        Sl   00:00   0:01 /usr/sbin/apache2 -k start
www-data   23489  0.0  0.8 965800 15996 ?        Sl   00:00   0:00 /usr/sbin/apache2 -k start
root       28979  0.0  0.0   6336   640 pts/0    R+   20:49   0:00 grep apa
root@crw0:~# ps -o thcount 23489
THCNT
   52
root@crw0:~#

EDIT:

https://mariadb.com/kb/en/thread-pool-in-mariadb/
komentarz 28 marca 2023 przez j23 Mędrzec (194,920 p.)

Apache jeszcze inaczej obsługuje połączenia - każde w osobnym procesie, co ma swoje zalety i wady.


Dla domknięcia tematu jeszcze kod serwera w Pythonie ;)

import asyncio

async def handle_client(reader, writer):
    while True:
        data = await reader.readline()
        if not data:
            break        
        message = data.decode().strip()
        addr = writer.get_extra_info('peername')
        print(f"Received {message} from {addr}")

        response = f"Received your message: {message}\n".encode()
        writer.write(response)
        await writer.drain()

    writer.close()
    print(f"Client {addr} disconnected...")

async def main():
    server = await asyncio.start_server(handle_client, 'localhost', 1234)
    print(f"Server running on {server.sockets[0].getsockname()}")

    async with server:
        await server.serve_forever()

asyncio.run(main())

 

komentarz 1 kwietnia 2023 przez edwardkraweznik Dyskutant (9,930 p.)
Witajcie ponownie :)

Przeanalizowałem sobie jak działa select, z tego co zrozumiałem select wrzuca sobie deskryptory do wektora następnie je skanuje w pętli. Optymalizacja działa na zasadzie FD_SETSIZE (w takim przypadku select nie skanuje całego wektora)

Pomyślałem, aby zrobić to tak:

Pozostawić thread (w konfiguracji będzie możliwość wyboru czy chceny działać na jednym wątku czy na wielu wątkach)

deskryptor kopiować w obiekcie, obiekty wrzucać do wektora.

Następnie normalnie skanować obiekty w pętli...

Co o tym sądzicie?
komentarz 1 kwietnia 2023 przez j23 Mędrzec (194,920 p.)

Przeanalizuj przykład z funkcją poll, który podałem. On w zasadzie realizuje to, co zaproponowałeś ;)

Optymalizacja działa na zasadzie FD_SETSIZE

Nie nazwałbym tego optymalizacją. FD_SETSIZE to stała, która określa wielkość struktury fd_set, a dokładniej tablicy bitów, gdzie każdy bit odpowiada wartości deskryptora. Domyślna wartość w Linuksie to 1024.

komentarz 1 kwietnia 2023 przez edwardkraweznik Dyskutant (9,930 p.)
no fakt, dzięki

Podobne pytania

0 głosów
1 odpowiedź 280 wizyt
pytanie zadane 25 stycznia 2016 w C i C++ przez Royd Obywatel (1,250 p.)
+1 głos
1 odpowiedź 117 wizyt
pytanie zadane 11 stycznia w Java przez Specjalny Nowicjusz (230 p.)
0 głosów
1 odpowiedź 87 wizyt
pytanie zadane 6 sierpnia 2023 w Python przez Specjalny Nowicjusz (230 p.)

92,555 zapytań

141,404 odpowiedzi

319,557 komentarzy

61,940 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...