BSD Sockets - How to use non-blocking sockets?

11,036

Solution 1

Whenever I run it, the server still waits for me to send something before it will read and output what the client has sent.

Well, that is how you wrote it. You block on IO from stdin, and then and only then do you send/receive.

cin>>out;
cin.get();

Also, you are using a local socket (AF_UNIX) which creates a special file in your filesystem for interprocess communication - this is a different mechanism than IP, and is definitely not TCP as you indicate in your question. I suppose you could name the file 127.0.0.1, but that really doesn't make sense and implies confusion on your part, because that is an IP loopback address. You'll want to use AF_INET for IP.

For an excellent starter guide on unix networking, I'd recommend http://beej.us/guide/bgnet/

If you want the display of messages received to be independant of your cin statements, either fork() off a seperate process to handle your network IO, or use a separate thread.

You might be interested in select(). In my opinion non-blocking sockets are usually a hack, and proper usage of select() or poll() is generally much better design and more flexible (and more portable). try

man select_tut

for more information.

Solution 2

General approach for a TCP server where you want to handle many connections at the same time:

  • make listening socket non-blocking
  • add it to select(2) or poll(2) read event set
  • enter select(2)/poll(2) loop
  • on wakeup check if it's the listening socket, then
    • accept(2)
    • check for failure (the client might've dropped the connection attempt by now)
    • make newly created client socket non-blocking, add it to the polling event set
  • else, if it's one of the client sockets
    • consume input, process it
    • watch out for EAGAIN error code - it's not really an error, but indication that there's no input now
    • if read zero bytes - client closed connection, close(2) client socket, remove it from event set
  • re-init event set (omitting this is a common error with select(2))
  • repeat the loop

Client side is a little simpler since you only have one socket. Advanced applications like web browsers that handle many connections often do non-blocking connect(2) though.

Solution 3

I think you have to set non-block sooner (ie get the socket then set it non block)

also check that the fcntl to set it actually worked

Share:
11,036
Sterling
Author by

Sterling

Updated on June 24, 2022

Comments

  • Sterling
    Sterling almost 2 years

    I am trying to use non-blocking TCP sockets. The problem is that they are still blocking. The code is below -

    server code -

    struct sockaddr name;
    char buf[80];
    
    void set_nonblock(int socket) {
        int flags;
        flags = fcntl(socket,F_GETFL,0);
        assert(flags != -1);
        fcntl(socket, F_SETFL, flags | O_NONBLOCK);
    }
    
    int main(int agrc, char** argv) {
    
        int sock, new_sd, adrlen;   //sock is this socket, new_sd is connection socket
    
        name.sa_family = AF_UNIX;
        strcpy(name.sa_data, "127.0.0.1");
        adrlen = strlen(name.sa_data) + sizeof(name.sa_family);
    
        //make socket
        sock = socket(AF_UNIX, SOCK_STREAM, 0);
    
        if (sock < 0) {
            printf("\nBind error %m", errno);
            exit(1);
        }
    
        //unlink and bind
        unlink("127.0.0.1");
        if(bind (sock, &name, adrlen) < 0)
            printf("\nBind error %m", errno);
    
        //listen
        if(listen(sock, 5) < 0)
            printf("\nListen error %m", errno);
    
        //accept
        new_sd = accept(sock, &name, (socklen_t*)&adrlen);
        if( new_sd < 0) {
            cout<<"\nserver accept failure "<<errno;
            exit(1);
        }
    
        //set nonblock
        set_nonblock(new_sd);
    
        char* in = new char[80];
        std::string out = "Got it";
        int numSent;
        int numRead;
    
        while( !(in[0] == 'q' && in[1] == 'u' && in[2] == 'i' && in[3] == 't') ) {
    
            //clear in buffer
            for(int i=0;i<80;i++)
                in[i] = ' ';
    
            cin>>out;
            cin.get();
    
            //if we typed something, send it
            if(strlen(out.c_str()) > 0) {
                numSent = send(new_sd, out.c_str(), strlen(out.c_str()), 0);
                cout<<"\n"<<numSent<<" bytes sent";
            }
    
            numRead = recv(new_sd, in, 80, 0);
            if(numRead > 0)
                cout<<"\nData read from client - "<<in;
    
         }   //end while
    
         cout<<"\nExiting normally\n";
         return 0;
    }
    

    client code -

    struct sockaddr name;
    
    void set_nonblock(int socket) {
        int flags;
        flags = fcntl(socket,F_GETFL,0);
        assert(flags != -1);
        fcntl(socket, F_SETFL, flags | O_NONBLOCK);
    }
    
    int main(int agrc, char** argv) {
    
        int sock, new_sd, adrlen;
    
        sock = socket(AF_UNIX, SOCK_STREAM, 0);
    
        if (sock < 0) {
            printf("\nserver socket failure %m", errno);
            exit(1);
        }
    
        //stuff for server socket
        name.sa_family = AF_UNIX;
        strcpy(name.sa_data, "127.0.0.1");
        adrlen = strlen(name.sa_data) + sizeof(name.sa_family);
    
        if(connect(sock, &name, adrlen) < 0) {
            printf("\nclient connection failure %m", errno);
            exit(1);
        }
    
        cout<<"\nSuccessful connection\n";
    
        //set nonblock
        set_nonblock(sock);
    
        std::string out;
        char* in = new char[80];
        int numRead;
        int numSent;
    
    
        while(out.compare("quit")) {
    
            //clear in
            for(int i=0;i<80;i++)
                in[i] = '\0';
    
    
            numRead = recv(sock, in, 80, 0);
    
            if(numRead > 0)
                cout<<"\nData read from server - "<<in;
    
    
            cout<<"\n";
            out.clear();
            cin>>out;
            cin.get();
    
            //if we typed something, send it
            if(strlen(out.c_str())) {
                numSent = send(sock, out.c_str(), strlen(out.c_str()), 0);
                cout<<"\n"<<numSent<<" bytes sent";
            }
    
        }   //end while
    
    
        cout<<"\nExiting normally\n";
        return 0;
    }
    

    Whenever I run it, the server still waits for me to send something before it will read and output what the client has sent. I want either the server or client to be able to send the message as soon as I type it, and have the other read and output the message at that time. I thought non-blocking sockets was the answer, but maybe I am just doing something wrong?

    Also, I was using a file instead of my 127.0.0.1 address as the sockaddr's data. If that is not how it should be properly used, feel free to say so (it worked how it worked previously with a file so I just kept it like that).

    Any help is appreciated.

  • Nemo
    Nemo almost 13 years
    +1 for "check that the fcntl to set it actually worked". Always check the return value of system calls. Always.
  • Sterling
    Sterling almost 13 years
    I set it to non-block right after I get it through the accept call. If I set the server socket, sock, to non block it gives me an error of "resource temporarily unavailable" when I try to call accept.
  • Sterling
    Sterling almost 13 years
    Thanks, I will look into fork() and select(). Whenever I change all the AF_UNIX's to AF_INET, I get an Invalid Argument error when the client tries to connect. Should I only be changing it on the server side?
  • Josh
    Josh almost 13 years
    Well, you will need to change both server and client to AF_INET. The difference between the two is large - one is a local (or unix) socket, the other is a network socket, specifically an IP socket. Definately take a look at the Beej's guide I posted, and then sanity check your code to compare. I hope that helps!
  • Sterling
    Sterling almost 13 years
    Well thats what I meant - I changed them both. I am reading the link now.