Начну статью с того, что один мой друг предложил работу и тестовым заданием на роль программиста с\с++ было, создать такой клиент-сервер.


Конечно я сразу же полез в интернет за кодом, его там куча. Куча ненужного кода, как оказалось. Нашел более подходящий для меня не обратив особого внимания на то, что существует различие между windows и unix. Вообще начнем с того, что такие клиент-сервера работают по принципу socket'ов и реализовать их нет ни чего сложного. Достаточно открыть книжку по игре с winsock и там все написано, 3 строчки которые создают этакое соединение по которому можно общаться между клиентом и сервером.


Скопировал код в клиент, скопировал код в сервер, скомпилировал(qt creator еще та борода), запустил, УРААА! Заработало! Пишем в клиенте, сервер делает ЭХО и мы видим мгновенный ответ.


вот код клиента:


struct sockaddr_in addr; // структура с адресом
    struct hostent* hostinfo;
    port = atoi(PORT);
    sock = socket(AF_INET, SOCK_STREAM, 0); // создание TCP-сокета
    if(sock < 0)
    {
        perror("socket");
        exit(1);
    }

    // Указываем параметры сервера
    addr.sin_family = AF_INET; // домены Internet
    addr.sin_port = htons(port); // или любой другой порт...
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

 if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) // установка соединения с сервером
    {
        perror("Подключение");
        exit(2);
    }

и код сервера


 if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0)
    {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    //set master socket to allow multiple connections , this is just a good habit, it will work without this
    if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
    {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    //type of socket created
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons( PORT );

    //bind the socket to localhost port 8888
    if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0)
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    printf("Listener on port %d \n", PORT);

    //try to specify maximum of 3 pending connections for the master socket
    if (listen(master_socket, 3) < 0)
    {
        perror("listen");
        exit(EXIT_FAILURE);

if ((new_socket = accept(master_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0)
            {
                perror("accept");
                exit(EXIT_FAILURE);
            }         
    }

В общем виде этого достаточно чтобы отправлять и принимать сообщения от сервера для этого используются функции send, recv или read, write.


В чем разница можно прочитать на любом сайте, где описана работа сокетов, я особо не углублялся, понял лишь то, что один из них вместе с сообщением передает еще и флаг.
Ну хорошо, один клиент есть, а если их к примеру два? Что делать тогда? Запускаем два клиента. Все отлично каждый шлет на сервер каждый получает ответ, но нам необходимо чтобы они общались между собой, хм, что же делать то?
Необходимо где-то хранить список сокетов чтобы можно было в цикле отправлять каждому их них сообщение пришедшее на сервер. Так как сокет это всего лишь переменная типа INT, то и создать массив такого же типа не сложно:


for (i = 0; i < max_clients; i++)
            {
                //if position is empty
                if( client_socket[i] == 0 )
                {
                    client_socket[i] = new_socket;
                    printf("Adding to list of sockets as %d\n" , i);

                    break;
                }
            }

Переменная max_clients задается на старте сервера, чтобы задать количество подключаемых клиентов.


Вот теперь есть массив с сокетами подключаемых клиентов, мы радуемся этому, делаем цикл на отправку сообщений и очень сильно расстраиваемся когда видим что у нас ничего не полилось…
Дело все в том, что TCP и сокеты это дуплекс и общение происходит в одном канале, поэтому внешний сокет по отношению к текущему не может отправлять в него сообщения.
Просидев над этой задачей 4 дня и не найдя решения (как не странно вокруг все на этом основано, но нет ни одного примера когда) я все таки нашел способ отправки таких сообщений, а именно необходимо было создать структуру и не такой уж простой массив для клиентов:


//clear the socket set
        FD_ZERO(&readfds);

        //add master socket to set
        FD_SET(master_socket, &readfds);
        max_sd = master_socket;

        //add child sockets to set
        for ( i = 0 ; i < max_clients ; i++)
        {
            //socket descriptor
            sd = client_socket[i];

            //if valid socket descriptor then add to read list
            if(sd > 0)
                FD_SET( sd , &readfds);

            //highest file descriptor number, need it for the select function
            if(sd > max_sd)
                max_sd = sd;
        }

        //wait for an activity on one of the sockets , timeout is NULL , so wait indefinitely
        activity = select( max_sd + 1 , &readfds , NULL , NULL , NULL);

Суть даже не в создании массива клиентов, а в том, что в unix системах есть функция select которая выбирает сокет, это нам и нужно было все это время.
То есть мы пишем в текущий сокет, выбираем следующий и пишем в него, и так до тех пор, пока не закончатся все сокеты подключенные к серверу.


Ну и все, ставим это в цикл while запираем в функцию server и пихаем в поток:


      pthread_create(&threadA[0], NULL, server, (void *)max_clients);

                pthread_join(threadA[0], NULL);

Все сделано! Все отлично! Мы рады! Запускаем сервер, запускаем клиент, пишем сообщение и понимаем, что ничего не работает, для того, чтобы принять сообщение от сервера нам необходимо сначала его отправить, для того чтобы была создана дуплексная связь между ними. Но как же тогда принимать сообщения от другого пользователя? Постоянно слать сообщения серверу? Нет! Это же полный бред.


Постоянно ждать сообщения от сервера? Вот это уже похоже на правду.


Пихаем функцию чтения read в бесконечный цикл и понимаем, что пока мы не примем сообщение от сервера мы не сможем ни чего написать. Снова не получилось. Неделя каких-то расстройств. И тут приходит мысль, а что, если разделить чтение и запись на два разных потока?


Не долго думая создаем в клиенте два потока на чтение и запись:


 pthread_create(&threadA[0], NULL, write, NULL);
 pthread_create(&threadA[1], NULL, read, NULL);

         pthread_join(threadA[1], NULL);
         pthread_join(threadA[0], NULL);

Запускаем.


И понимаем что теперь все работает. Радуемся. Идем за конфетами в магазин и наедаемся, ведь мы потратили столько сил и нервов на все это!


Всем спасибо за внимание!


Для меня это важно, потому, что во первых я не нашел решения в интернете и второе, меня все равно не взяли, потому что я вместо std::thread использовал pthread_create.
Поделиться с друзьями
-->

Комментарии (3)


  1. ToSHiC
    04.06.2016 12:05
    +3

    А вы уверены, что вас не взяли именно из-за использования pthread_create вместо std::thread, а не потому, что вы совершенно странные вещи пишете? Что означает вот это ваше: «Дело все в том, что TCP и сокеты это дуплекс и общение происходит в одном канале, поэтому внешний сокет по отношению к текущему не может отправлять в него сообщения.»? Сокет — это сокет, он не может быть внешним или внутренним по отношению к другому сокету.

    В целом, раз дали задание написать подобный сервер, то хотели проверить ваши знания программирования сетевых приложений. Из этой статьи понятно, что знания примерно равны нулю.


  1. GamePad64
    04.06.2016 12:28
    -1

    Клиент-сервер по принципу общения «все со всеми»

    Это называется "брокер сообщений". Очень простой, с одним широковещательным каналом.


    Скопировал код в клиент, скопировал код в сервер
    УРААА! Заработало!

    Вы ни разу не писали работу с сетью на плюсах, верно? Вероятно, вы неправильно выбрали себе должность.


  1. segment
    04.06.2016 13:10

    Жесть конечно. В интернете много хороших статей и книг на данную тематику, вероятно лень читать было. Да еще на гиктаймс эту же статью выложил -_-