Asynchronous C client for a multiclient C server

后端 未结 1 1547
你的背包
你的背包 2021-01-15 17:32

I have a client which is working fine, but whenever I run a new client, sometimes I don\'t receive the sent message on the other client already running, while using telnet i

相关标签:
1条回答
  • 2021-01-15 17:55

    The reason a client can't receive a message until they send one is because.

    fgets(message, 256,stdin);
    

    Will keep "reading" (and will therefore block) until an EOF or a newline character has been read from the input stream

    Also, note that

    if( recv(sock , server_reply , 256 , 0) < 0)
    

    blocks if there is nothing to read, which will prevent that user from sending more messages to the server until there is something new to read from the server. Assuming that you've played online games before, I hope that you can see that such a setup would be rather annoying!

    So, we have to find someway of checking to see if we can read from STDIN and the server socket without incurring a block. Using select() will prevent us blocking on the sever socket, but it wouldn't work for STDIN whilst using fgets() to read input from the user. This is because, as mentioned above, fgets() blocks until an EOF or newline is detected.

    The main solution I have in mind is to replace fgets with a method buffer_message() that will only read from STDIN when it won't block on read (we'll use select() to implement this). We'll then place what is read into a buffer. If there is a full message, this message will then be written to the server. Otherwise, we'll let the control keep going through the program until there is something to read or write.

    This is code from a recent university assignment I did and so a small portion of the code isn't mine

    Declarations:

    //directives are above (e.g. #include ...)
    
    //message buffer related delcartions/macros
    int buffer_message(char * message);
    int find_network_newline(char * message, int inbuf);
    #define COMPLETE 0
    #define BUF_SIZE 256
    
    static int inbuf; // how many bytes are currently in the buffer?
    static int room; // how much room left in buffer?
    static char *after; // pointer to position after the received characters
    //main starts below
    

    Main:

    //insert the code below into main, after you've connected to the server
    puts("Connected\n");    
    
    //set up variables for select()
    fd_set all_set, r_set;
    int maxfd = sock + 1;
    FD_ZERO(&all_set);
    FD_SET(STDIN_FILENO, &all_set); FD_SET(sock, &all_set);
    r_set = all_set;
    struct timeval tv; tv.tv_sec = 2; tv.tv_usec = 0;
    
    //set the initial position of after
    after = message;
    
    puts("Enter message: ");
    //keep communicating with server
    for(;;){
    
        r_set = all_set;
        //check to see if we can read from STDIN or sock
        select(maxfd, &r_set, NULL, NULL, &tv);
    
        if(FD_ISSET(STDIN_FILENO, &r_set)){
    
            if(buffer_message(message) == COMPLETE){
                //Send some data
                if(send(sock, message, strlen(message) + 1, 0) < 0)//NOTE: we have to do strlen(message) + 1 because we MUST include '\0'
                {
                    puts("Send failed");
                    return 1;
                }
    
                puts("Enter message:");
            }
        }
    
        if(FD_ISSET(sock, &r_set)){
            //Receive a reply from the server
            if( recv(sock , server_reply , 256 , 0) < 0)
            {
                puts("recv failed");
                break;
            }
    
            printf("\nServer Reply: %s\n", server_reply);
            server_reply[0]='\0';
    
        }
    }
    
    close(sock);
    return 0;
    //end of main
    

    Buffer functions:

    int buffer_message(char * message){
    
        int bytes_read = read(STDIN_FILENO, after, 256 - inbuf);
        short flag = -1; // indicates if returned_data has been set 
        inbuf += bytes_read;
        int where; // location of network newline
    
        // Step 1: call findeol, store result in where
        where = find_network_newline(message, inbuf);
        if (where >= 0) { // OK. we have a full line
    
            // Step 2: place a null terminator at the end of the string
            char * null_c = {'\0'};
            memcpy(message + where, &null_c, 1); 
    
            // Step 3: update inbuf and remove the full line from the clients's buffer
            memmove(message, message + where + 1, inbuf - (where + 1)); 
            inbuf -= (where+1);
            flag = 0;
        }
    
        // Step 4: update room and after, in preparation for the next read
        room = sizeof(message) - inbuf;
        after = message + inbuf;
    
        return flag;
    }
    
    int find_network_newline(char * message, int bytes_inbuf){
        int i;
        for(i = 0; i<inbuf; i++){
            if( *(message + i) == '\n')
            return i;
        }
        return -1;
    }
    

    P.S.

    if( send(sock , message , strlen(message) , 0) < 0)
    

    The above can also block if there's no space to write to the server, but there's no need to worry about that here. Also, I'd like to point out a few things you should implement for your client and your server:

    1. Whenever you send data over a network, the standard newline is \r\n, or carriage return / newline, or simply the network newline. All messages sent between the client and the server should have this appended at the end.
    2. You should be buffering all data sent between the server and the client. Why? Because you're not guaranteed to receive all packets in a message in a single read of a socket. I don't have time to find a source, but when using TCP/IP, packets for a message/file don't have to arrive together, meaning that if you do read, you may not be reading all of the data you intend to read. I'm not well versed in this, so please investigate this more. Open to having this edited / corrected
    0 讨论(0)
提交回复
热议问题