简体   繁体   中英

BSD Sockets - How to use non-blocking sockets?

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.

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-blockingconnect(2) though.

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

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.

If you want non-blocking i/o, you want to use select. You can set it with stdin as one of the sockets it is listening on, along with the client sockets (just add file descriptor 1, which is stdin, to the fd_set).

http://beej.us/guide/bgnet/output/html/multipage/advanced.html

I would recommend reading through what beej has to say about select. It looks a little intimidating but is really useful and simple to use if you take a little time to wrap your head around it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM