Komunikacja pomiędzy procesami za pomocą socketów w domenie AF_UNIX

0

Witam
Próbuję zrozumieć komunikację pomiędzy procesami za pomocą socketów w domenie AF_UNIX, czyli gdy nasz socket przyjmuje nazwę zaczynającą się od slasha (a nie IP::port jak w domenie AF_INET) i używa się read/write zamiast send/recv.
Poniżej przykład jaki sobie napisałem.
Rzeczy których nie rozumiem:
1.) Zanim robię write to po stronie klienta dałem sleepa 10[s] żeby zobaczyć co się stanie po stronie serwera - okazuje się że serwer czeka na funkcji read te 10 [s] aż klient wywoła write'a. Nie rozumiem czemu tak jest, skąd serwer wie że ma czekać? Spodziewałem się, że gdy nie będzie danych to read zwróci 0.
2.) Jak wywalę sleepa z pętli klienta to jest tak, że po stronie serwera otrzymuję z 1 reada wszystko co klient wysłał. Jak nie wyrzucę sleepa to nie wiem jak mam poraz drugi połączyć się z serwerem. Gdy wstawię w pętlę for klienta connect to zwraca się ERRNO że "Transport endpoint is already connected". Więc nie wiem jak tutaj nawiązać ponownie połączenie. Czy jedyne wyjście to zamknąć socket po stronie klienta, jeszcze raz go utworzyć (socket(AF_UNIX...)) i wywołać connect? Czy może nie trzeba tego wszystkiego robić na nowo?

const char* SERVER_PATH = "/tmp/mySocket";

void freeSockets(int sockfd, int sockfdClient=-1)
{
    close(sockfd);
    close(sockfdClient);
    unlink(SERVER_PATH);
}

void sockStreamAfUnixExample()
{
    pid_t pid = fork();

    if (pid == 0) //Server
    {
        sockaddr_un serveraddr, clientaddr;
        socklen_t len;
        int sockfdClient = -1;
        int sockfd = -1;

        //create endpoint of communication, socket descriptor
        sockfd = socket(AF_UNIX, SOCK_STREAM, 0); //AF_UNIX also know as AS_LOCAL), SOCK_DGRAM/SOCK_DGRAM
        if (sockfd == -1)
        {
            perror("Cannot create server socket");
            exit(EXIT_FAILURE);
        }

        //bind socket on unique name
        unlink(SERVER_PATH); //sot that bind never fail
        memset(&serveraddr, 0, sizeof(serveraddr));
        serveraddr.sun_family = AF_UNIX;
        strncpy(serveraddr.sun_path, SERVER_PATH, sizeof(serveraddr.sun_path));
        //serveraddr = {AF_UNIX, SERVER_PATH};
        if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(sockaddr_un)) == -1)
        {
            perror("Server bind failed");
            freeSockets(sockfd, sockfdClient);
            exit(EXIT_FAILURE);
        }

        //The listen() function basically sets a flag in the internal socket structure marking the socket as a passive 
        //listening socket, one that you can call accept on. It opens the bound port so the socket can then start receiving connections
        // from clients.
        //The accept() function asks a listening socket to accept the next incoming connection and return a socket descriptor for
        // that connection.
        if (listen(sockfd, 128) == -1)//128 queue size (max number of client connections in pending state on acceptance)
        {
            perror("Server listen failed");
            freeSockets(sockfd, sockfdClient);
            exit(EXIT_FAILURE);
        }

        while (1)
        {
            cout << "Server ready to accept next connection" << endl;
            //accept client connection and remove from queue
            len = sizeof(sockaddr_un);
            sockfdClient = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
            if (sockfdClient == -1)
            {
                perror("Cannot accept socket");
                freeSockets(sockfd, sockfdClient);
                exit(EXIT_FAILURE);
            }

            cout << "Connection accepted from client with addr " << clientaddr.sun_path << endl;

            //Receive data from client
            string buff(1000, '\0'); //goto cannot cross initialized variables
            int bytes = read(sockfdClient, &buff[0], buff.size());
            if (bytes == -1)
            {
                perror("recv failed");
                freeSockets(sockfd, sockfdClient);
                exit(EXIT_FAILURE);
            }

            cout << "Server receives " << bytes << " bytes: " << &buff[0] << endl;
        }

        freeSockets(sockfd, sockfdClient);
        exit(0);
    }
    else //Client
    {
        sockaddr_un serveraddr;
        int sockfd = -1;

        sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sockfd == -1)
        {
            perror("Cannot create client socket");
            exit(EXIT_FAILURE);
        }

        this_thread::sleep_for(chrono::seconds(1));
        memset(&serveraddr, 0, sizeof(serveraddr));
        serveraddr.sun_family = AF_UNIX;
        strncpy(serveraddr.sun_path, SERVER_PATH, sizeof(serveraddr.sun_path));
        if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(sockaddr_un)) == -1)
        {
            perror("Connect failed");
            freeSockets(sockfd);
            exit(0);
        }

        std::array<string, 10> table =
        {
            "Msg priority 0",
            "Msg priority 1",
            "Msg priority 2",
            "Msg priority 3",
            "Msg priority 4",
            "Msg priority 5",
            "Msg priority 6",
            "Msg priority 7",
            "Msg priority 8",
            "Msg priority 9"
        };
        for (int i = 0; i<10; ++i) //the message might be delivered as one without boundaries if You remove sleep_for and will have a luck
        {
            this_thread::sleep_for(chrono::seconds(10)); //each time connect is required
            int bytes = write(sockfd, table.at(i).c_str(), table.at(i).size());
            if (bytes == -1)
            {
                perror("Client write error");
                freeSockets(sockfd);
                exit(0);
            }
            cout << "Client sent " << bytes << " bytes: " << table.at(i).c_str() << endl;
        }

        int status = 0;
        wait(&status);

        freeSockets(sockfd);
    }
}
0

Hmm,

  1. Funkcje typu write/read/send/recv przejawiają dwa typy zachowań: jeden rodzaj to tzw. zachowanie blokujące, które zaobserwowałeś powyżej. System wstrzymuje działanie procesu, aż druga strona połączenia coś wyśle. I jest to jak najbardziej ok. 0 jako zwrócona wartość oznacza raczej koniec połączenia. Drugie z zachowań, to tzw. zachowanie nieblokujące. W takim przypadku, gdy wywołasz recv/read na deskryptorze zamiast dostać 0 dostaniesz zapewne -1 i errno ustawi się na kod błędu EAGAIN. To z kolei wymaga bądź to aktywnego odpytywania systemu o status deskryptora, czy pojawiły się nowe dane albo wymaga jakiegoś mechanizmu powiadomienia o zdarzeniu. Poczytaj manual: man 2 select.
  2. Tutaj akurat wszystko jest jasne - w przypadku gniazda typu STREAM system może buforować dane i po drugiej stronie połączenia przesłać tyle danych, ile tylko podano jako bufor do read/recv. Analogicznie, connect(2) można wywołać wyłącznie na jeszcze nie połączonym gniazdku, więc masz rację - musisz w pętli też tworzyć socket (oraz na końcu go zamykać rzecz jasna). Dopiero na nowoutworzonym gniazdku możesz wołać connect.

1 użytkowników online, w tym zalogowanych: 0, gości: 1