Асинхронно получать сообщение с сервера

#c #multithreading #server #client #write

Вопрос:

У меня есть клиентская программа и серверная программа. Может быть несколько серверов и несколько
клиентов, которые могут подключаться к нескольким серверам по своему выбору
В клиентской программе отображается меню

  1. подключение 4000 // подключается к серверу через порт 4000
  2. ставка 1000 4000 // отправьте значение ставки 1000 на сервер через порт 4000

Теперь сервер может получать ставки от нескольких подключенных к нему клиентов и до сих пор отслеживает самую высокую
ставку. Всякий раз, когда делается новая ставка, сервер отправляет широковещательную передачу каждому клиенту, подключенному
к нему, по одному, как — write(users[i].sock_fd, msg, size) . Как мне прослушать это сообщение на стороне клиента ? Здесь есть две вещи

  1. Клиенту необходимо прослушать сообщение, отправленное сервером.
  2. Клиент также считывает текст или пункты меню (подключение и ставка) из командной строки пользователя.

Я закодировал часть 2), но запутался, как закодировать 1) в клиенте и одновременно сделать 2) также работающим

Код клиента :

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define BUF_SIZE 128

#define MAX_AUCTIONS 5
#ifndef VERBOSE
#define VERBOSE 0
#endif

#define ADD 0
#define SHOW 1
#define BID 2
#define QUIT 3

/* Auction struct - this is different than the struct in the server program
 */
typedef struct auction_data
{
    int sock_fd;
    char item[BUF_SIZE];
    int current_bid;
} auction_data;
auction_data *auction_data_ptr;

/* Displays the command options available for the user.
 * The user will type these commands on stdin.
 */

void print_menu()
{
    printf("The following operations are available:n");
    printf("    shown");
    printf("    add <server address> <port number>n");
    printf("    bid <item index> <bid value>n");
    printf("    quitn");
}

/* Prompt the user for the next command 
 */
void print_prompt()
{
    printf("Enter new command: ");
    fflush(stdout);
}

/* Unpack buf which contains the input entered by the user.
 * Return the command that is found as the first word in the line, or -1
 * for an invalid command.
 * If the command has arguments (add and bid), then copy these values to
 * arg1 and arg2.
 */
int parse_command(char *buf, int size, char *arg1, char *arg2)
{
    int result = -1;
    char *ptr = NULL;
    if (strncmp(buf, "show", strlen("show")) == 0)
    {
        return SHOW;
    }
    else if (strncmp(buf, "quit", strlen("quit")) == 0)
    {
        return QUIT;
    }
    else if (strncmp(buf, "add", strlen("add")) == 0)
    {
        result = ADD;
    }
    else if (strncmp(buf, "bid", strlen("bid")) == 0)
    {
        result = BID;
    }

    ptr = strtok(buf, " ");  // first word in buf
    ptr = strtok(NULL, " "); // second word in buf

    if (ptr != NULL)
    {
        strncpy(arg1, ptr, BUF_SIZE);
    }
    else
    {
        return -1;
    }
    ptr = strtok(NULL, " "); // third word in buf

    if (ptr != NULL)
    {
        strncpy(arg2, ptr, BUF_SIZE);
        return resu<
    }
    else
    {
        return -1;
    }
    return -1;
}

/* Connect to a server given a hostname and port number.
 * Return the socket for this server
 */
int add_server(char *hostname, int port)
{
    // Create the socket FD.
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0)
    {
        perror("client: socket");
        exit(1);
    }

    // Set the IP and port of the server to connect to.
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    struct addrinfo *ai;

    /* this call declares memory and populates ailist */
    if (getaddrinfo(hostname, NULL, NULL, amp;ai) != 0)
    {
        close(sock_fd);
        return -1;
    }
    /* we only make use of the first element in the list */
    server.sin_addr = ((struct sockaddr_in *)ai->ai_addr)->sin_addr;

    // free the memory that was allocated by getaddrinfo for this list
    freeaddrinfo(ai);

    // Connect to the server.
    if (connect(sock_fd, (struct sockaddr *)amp;server, sizeof(server)) == -1)
    {
        perror("client: connect");
        close(sock_fd);
        return -1;
    }
    if (VERBOSE)
    {
        fprintf(stderr, "nDebug: New server connected on socket %d. Awaiting itemn", sock_fd);
    }
    return sock_fd;
}
/* ========================= Add helper functions below ========================
 * Please add helper functions below to make it easier for the TAs to find the 
 * work that you have done.  Helper functions that you need to complete are also
 * given below.
 */

/* Print to standard output information about the auction
 */
void print_auctions(struct auction_data *a, int size)
{
    printf("Current Auctions:n");

    for (int i = 0; i < size; i  )
    {
        struct auction_data auction_data = a[i];
        printf("(%d) %s bid = %dn", i, auction_data.item, auction_data.current_bid);
    }

    /* TODO Print the auction data for each currently connected 
     * server.  Use the follosing format string:
     *     "(%d) %s bid = %dn", index, item, current bid
     * The array may have some elements where the auction has closed and
     * should not be printed.
     */
}

/* Process the input that was sent from the auction server at a[index].
 * If it is the first message from the server, then copy the item name
 * to the item field.  (Note that an item cannot have a space character in it.)
 */
void update_auction(char *buf, int size, struct auction_data *a, int index)
{

    // TODO: Complete this function

    // fprintf(stderr, "ERROR malformed bid: %s", buf);
    // printf("nNew bid for %s [%d] is %d (%d seconds left)n",           );
}

int main(void)
{

    char name[BUF_SIZE];
    int size = 0;
    // Declare and initialize necessary variables
    // TODO

    // Get the user to provide a name.
    printf("Please enter a username: ");
    fflush(stdout);
    int num_read = read(STDIN_FILENO, name, BUF_SIZE);
    printf("%s-namen", name);
    if (num_read <= 0)
    {
        fprintf(stderr, "ERROR: read from stdin failedn");
        exit(1);
    }
    print_menu();
    // TODO
    char server_reply[2000];

    while (1)
    {

        print_prompt();
        char *command;
        scanf("%m[^n]s", amp;command);
        getchar();
        char arg1[100];
        char arg2[100];
        int commandNumber = parse_command(command, 1000, arg1, arg2);
        char dest[100] = "";
        strcpy(dest, name);
        dest[strlen(dest) - 1] = '';
        if (commandNumber == ADD)
        {
            printf("%s-name4n", dest);

            int port = atoi(arg2);
            int sock_fd = add_server(arg1, port);
            printf("%s-servern", server_reply);
            write(sock_fd, dest, strlen(dest));
            auction_data_ptr = (auction_data *)realloc(auction_data_ptr, (size   1) * sizeof(auction_data_ptr));
            auction_data_ptr[size].sock_fd = sock_fd;
            size  ;
        }
        else if (commandNumber == SHOW)
        {
            print_auctions(auction_data_ptr, size);
        }
        else if (commandNumber == BID)
        {
            int itemIndex = atoi(arg1);
            int bidValue = atoi(arg2);
            printf("%d-testn", auction_data_ptr[itemIndex].sock_fd);
            send(auction_data_ptr[itemIndex].sock_fd, arg2, strlen(arg2), 0);
        }
        else if (commandNumber == QUIT)
        {
        }

        // TODO
    }
    return 0; // Shoud never get here
}
 

Код сервера :

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

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

#ifndef PORT
#define PORT 30000
#endif
#define MAX_BACKLOG 5
#define MAX_CONNECTIONS 20
#define BUF_SIZE 128
#define MAX_NAME 56

int verbose = 0;

struct user
{
    int sock_fd;
    char name[MAX_NAME];
    int bid;
};

typedef struct
{
    char *item;
    int highest_bid; // value of the highest bid so far
    int client;      // index into the users array of the top bidder
} Auction;

/*
 * Accept a connection. Note that a new file descriptor is created for
 * communication with the client. The initial socket descriptor is used
 * to accept connections, but the new socket is used to communicate.
 * Return the new client's file descriptor or -1 on error.
 */
int accept_connection(int fd, struct user *users)
{
    int user_index = 0;
    while (user_index < MAX_CONNECTIONS amp;amp; users[user_index].sock_fd != -1)
    {
        user_index  ;
    }

    if (user_index == MAX_CONNECTIONS)
    {
        fprintf(stderr, "server: max concurrent connectionsn");
        return -1;
    }

    int client_fd = accept(fd, NULL, NULL);
    if (client_fd < 0)
    {
        perror("server: accept");
        close(fd);
        exit(1);
    }

    users[user_index].sock_fd = client_fd;
    users[user_index].name[0] = '';
    return client_fd;
}

/* Remove rn from str if the characters are at the end of the string.
 * Defensively assuming that r could be the last or second last character.
 */
void strip_newline(char *str)
{
    if (str[strlen(str) - 1] == 'n' || str[strlen(str) - 1] == 'r')
    {
        if (str[strlen(str) - 2] == 'r')
        {
            str[strlen(str) - 2] = '';
        }
        else
        {
            str[strlen(str) - 1] = '';
        }
    }
}

/*
 * Read a name from a client and store in users.
 * Return the fd if it has been closed or 0 otherwise.
 */
int read_name(int client_index, struct user *users)
{
    int fd = users[client_index].sock_fd;

    /* Note: This is not the best way to do this.  We are counting
     * on the client not to send more than BUF_SIZE bytes for the
     * name.
     */
    int num_read = read(fd, users[client_index].name, MAX_NAME);
    if (num_read == 0)
    {
        users[client_index].sock_fd = -1;
        return fd;
    }
    users[client_index].name[num_read] = '';
    strip_newline(users[client_index].name);

    if (verbose)
    {
        fprintf(stderr, "[%d] Name: %sn", fd, users[client_index].name);
    }

    /*
    if (num_read == 0 || write(fd, buf, strlen(buf)) != strlen(buf)) {
        users[client_index].sock_fd = -1;
        return fd;
    }
*/

    return 0;
}

/* Read a bid from a client and store it in bid.
 * If the client does not send a number, bid will be set to -1
 * Return fd if the socket is closed, or 0 otherwise.
 */
int read_bid(int client_index, struct user *users, int *bid)
{
    printf("inside bidn");
    int fd = users[client_index].sock_fd;
    char buf[BUF_SIZE];
    char *endptr;
    int num_read = read(fd, buf, BUF_SIZE);
    if (num_read == 0)
    {
        return fd;
    }
    buf[num_read] = '';

    if (verbose)
    {
        fprintf(stderr, "[%d] bid: %s", fd, buf);
    }

    // Check if the client sent a valid number
    // (We are not checking for a good bid here.)
    errno = 0;
    *bid = strtol(buf, amp;endptr, 10);
    if (errno != 0 || endptr == buf)
    {
        *bid = -1;
    }

    return 0;
}

void broadcast(struct user *users, char *msg, int size)
{
    for (int i = 0; i < MAX_CONNECTIONS; i  )
    {
        if (users[i].sock_fd != -1)
        {
            if (write(users[i].sock_fd, msg, size) == -1)
            {
                // Design flaw: can't remove this socket from select set
                close(users[i].sock_fd);
                users[i].sock_fd = -1;
            }
        }
    }
}

int prep_bid(char *buf, Auction *a, struct timeval *t)
{
    // send item, current bid, time left in seconds
    printf("robin2-%s-%dn", a->item, a->highest_bid);

    printf("robin-%ldn", t->tv_sec);
    sprintf(buf, "%s %d %ld", a->item, a->highest_bid, t->tv_sec);
    printf("robin-bid2n");

    return 0;
}

/* Update auction if new_bid is higher than current bid.  
 * Write to the client who made the bid if it is lower
 * Broadcast to all clients if the bid is higher
 */
int update_bids(int client_index, struct user *users,
                int new_bid, Auction *auction, struct timeval *t)
{
    char buf[BUF_SIZE];

    if (new_bid > auction->highest_bid)
    {
        auction->highest_bid = new_bid;
        auction->client = client_index;

        prep_bid(buf, auction, t);
        if (verbose)
        {
            fprintf(stderr, "[%d] Sending to %d:n    %sn",
                    getpid(), users[client_index].sock_fd, buf);
        }

        broadcast(users, buf, strlen(buf)   1);
    }
    else
    {
        fprintf(stderr, "Client %d sent bid that was too low.  Ignoredn",
                client_index);
    }
    return 0;
}

int main(int argc, char **argv)
{
    argc = 7;
    argv[1] = "-v";
    argv[2] = "-t";
    argv[3] = "5";
    argv[4] = "-p";
    argv[5] = "4000";
    argv[6] = "robin";
    Auction auction;
    int opt;
    int port = PORT;
    struct timeval timeout;
    struct timeval *time_ptr = NULL;
    int minutes = 0;
    while ((opt = getopt(argc, argv, "vt:p:")) != -1)
    {
        switch (opt)
        {
        case 'v':
            verbose = 1;
            break;
        case 't':
            minutes = atoi(optarg);
            timeout.tv_sec = minutes * 60;
            timeout.tv_usec = 0;
            time_ptr = amp;timeout;
            break;
        case 'p':
            port = atoi(optarg);
            break;
        default:
            fprintf(stderr, "Usage: auction_server [-v] [-t timeout] [-p port] itemn");
            exit(1);
        }
    }
    if (optind >= argc)
    {
        fprintf(stderr, "Expected argument after optionsn");
        exit(1);
    }

    auction.item = argv[optind];
    auction.client = -1;
    auction.highest_bid = -1;

    struct user users[MAX_CONNECTIONS];
    for (int index = 0; index < MAX_CONNECTIONS; index  )
    {
        users[index].sock_fd = -1;
        users[index].name[0] = '';
    }

    // Create the socket FD.
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0)
    {
        perror("server: socket");
        exit(1);
    }

    // Set information about the port (and IP) we want to be connected to.
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;

    // This sets an option on the socket so that its port can be reused right
    // away. Since you are likely to run, stop, edit, compile and rerun your
    // server fairly quickly, this will mean you can reuse the same port.
    int on = 1;
    int status = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
                            (const char *)amp;on, sizeof(on));
    if (status == -1)
    {
        perror("setsockopt -- REUSEADDR");
    }

    // This should always be zero. On some systems, it won't error if you
    // forget, but on others, you'll get mysterious errors. So zero it.
    memset(amp;server.sin_zero, 0, 8);

    // Bind the selected port to the socket.
    if (bind(sock_fd, (struct sockaddr *)amp;server, sizeof(server)) < 0)
    {
        perror("server: bind");
        close(sock_fd);
        exit(1);
    }

    // Announce willingness to accept connections on this socket.
    if (listen(sock_fd, MAX_BACKLOG) < 0)
    {
        perror("server: listen");
        close(sock_fd);
        exit(1);
    }

    if (verbose)
    {
        fprintf(stderr, "[%d] Ready to accept connections on %dn",
                getpid(), port);
    }

    // The client accept - message accept loop. First, we prepare to listen
    // to multiple file descriptors by initializing a set of file descriptors.
    int max_fd = sock_fd;
    fd_set all_fds;
    FD_ZERO(amp;all_fds);
    FD_SET(sock_fd, amp;all_fds);

    while (1)
    {
        // select updates the fd_set it receives, so we always use a copy
        // and retain the original.
        fd_set listen_fds = all_fds;
        int nready;
        if ((nready = select(max_fd   1, amp;listen_fds, NULL, NULL, time_ptr)) == -1)
        {
            perror("server: select");
            exit(1);
        }
        if (nready == 0)
        {
            char buf[BUF_SIZE];
            sprintf(buf, "Auction closed: %s wins with a bid of %drn",
                    users[auction.client].name, auction.highest_bid);
            printf("%s", buf);
            broadcast(users, buf, BUF_SIZE);
            exit(0);
        }
        // Is it the original socket? Create a new connection ...
        if (FD_ISSET(sock_fd, amp;listen_fds))
        {

            int client_fd = accept_connection(sock_fd, users);
            if (client_fd != -1)
            {
                if (client_fd > max_fd)
                {
                    max_fd = client_fd;
                }
                FD_SET(client_fd, amp;all_fds);
                if (verbose)
                {
                    fprintf(stderr, "[%d] Accepted connection on %dn",
                            getpid(), client_fd);
                }
            }
        }

        // Next, check the clients.
        for (int index = 0; index < MAX_CONNECTIONS; index  )
        {
            if (users[index].sock_fd > -1 amp;amp; FD_ISSET(users[index].sock_fd, amp;listen_fds))
            {
                int client_closed = 0;
                int new_bid = 0;

                if (users[index].name[0] == '')
                {
                    client_closed = read_name(index, users);
                    if (client_closed == 0)
                    {
                        char buf[BUF_SIZE];
                        prep_bid(buf, amp;auction, time_ptr);
                        if (verbose)
                        {
                            fprintf(stderr, "[%d] Sending to %d:n    %sn",
                                    getpid(), users[index].sock_fd, buf);
                        }
                        if (write(users[index].sock_fd, buf, strlen(buf)   1) == -1)
                        {
                            fprintf(stderr, "Write to %d failedn", sock_fd);
                            close(sock_fd);
                        }
                    }
                }
                else
                { // read a bid
                    client_closed = read_bid(index, users, amp;new_bid);
                    if (client_closed == 0)
                    {
                        update_bids(index, users, new_bid, amp;auction, time_ptr);
                    }
                }

                if (client_closed > 0)
                {
                    FD_CLR(client_closed, amp;all_fds);
                    printf("Client %d disconnectedn", client_closed);
                }
            }
        }
    }

    // Should never get here.
    return 1;
}
 

Комментарии:

1. Пожалуйста, отредактируйте свой вопрос и разместите свой сервер и клиент в виде текста в блоках кода здесь. Мы не можем по-настоящему разумно комментировать, не видя того, что у вас уже есть.

2. Добавлен код клиента и сервера. Это не полный код, так как он был бы слишком большим. Хотя я могу включить, если это не поможет. void broadcast(struct user *users, char *msg, int size) в коде сервера записывается каждому подключенному к нему клиенту. Что мне нужно сделать в клиенте, чтобы прочитать это сообщение

3. Короче говоря, я должен сделать что-то вроде recv(sock_fd, server_reply, 2000, 0 , но мне нужно прослушать сервер/ подождать, пока он получит сообщение. Я думаю, что мы можем проигнорировать весь код и описание и просто сделать его одним лайнером, как — How does client wait for the server to recieve a message in a separate thread

Ответ №1:

Предостережение: Поскольку вы опубликовали только частичный код для сервера и клиента, это будет несколько предложений.

Ваш клиент может подключаться/подключаться к нескольким серверам ставок одновременно. Таким образом, он должен иметь возможность отслеживать несколько подключений аналогично серверу.

Ваша основная [заявленная] проблема заключается в том, что вы блокируете клиента по приглашению пользователя (например, с stdin помощью scanf и т. Д.). В настоящее время это означает, что клиент «застрял» в приглашении ввода пользователя и не может отправлять сообщения с серверов, к которым он подключен. Подробнее о том, как это исправить, ниже.

Таким образом, у вас будет куча кода с сервера, который должен быть в клиенте с некоторыми незначительными отличиями. Возможно, вы захотите немного обобщить часть серверного кода, чтобы он мог работать как на сервере, так и на клиенте (например, вы можете переместить его common.c ).

У вас уже есть код на сервере для обработки нескольких подключений. Серверу нужна select маска, которая является ИЛИ прослушиваемого fd и всех активных клиентских fd.

Аналогично, вашему клиенту нужна select маска, которая является ИЛИ fd для ввода пользователем (например, 0) и всех активных подключений к серверу.

Выполнение select на fd 0 и использование stdio.h потоков не будет работать слишком хорошо. Итак, замените доступ на stdin (например) read(0,line_buffer,sizeof(line_buffer)) . Вы делаете это, если в маске задано значение fd 0 select . Эта роль очень похожа на то, что ваш сервер выполняет для accept on sock_fd .

Вам нужно будет разрешить частичное чтение и добавлять в буфер, пока вы не увидите новую строку. Таким образом, вам придется выполнить работу, которую fgets обычно выполняют при сборке целой линии. Тогда ты можешь позвонить parse_command .

Поскольку read пользователь не понимает разграничения новой строки, он может ввести более одной строки, прежде чем вы сможете выполнить чтение.

Итак, для ввода пользователем:

 connect 4000n
bid 100 4000n
connect 5000n
 

Вы можете получить частичное чтение:

 conn
ect
 4000nbid 100 4000
nconnect
 5000n
 

Возможно, вам также потребуется использовать FIONREAD ioctl на fd 0, чтобы предотвратить блокировку. И, возможно, вам потребуется перевести слой TTY ядра в режим raw с помощью termios вызовов.

Теперь клиент становится очень похожим на ваш серверный код. Он будет обрабатывать [асинхронно] действия любых подключенных серверов и ввод данных пользователем.

Совет: По СУХОМУ принципу [«не повторяйся»] …

У вас уже есть a struct user на сервере. Клиенту понадобится что-то похожее/идентичное, например struct server . При обобщении кода вместо того, чтобы иметь две разные структуры, которые по сути делают одно и то же, подумайте о переименовании существующей структуры в (например) struct connection

Комментарии:

1. Я обновил полный рабочий код на данный момент. вероятно, вы могли бы указать, где именно необходимо внести изменения. Этот тип кодирования кажется мне довольно загадочным