BSD Sockets - How to use non-blocking sockets?
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)
orpoll(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
Sterling
Updated on June 24, 2022Comments
-
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 almost 13 years+1 for "check that the fcntl to set it actually worked". Always check the return value of system calls. Always.
-
Sterling almost 13 yearsI 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 almost 13 yearsThanks, 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 almost 13 yearsWell, 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 almost 13 yearsWell thats what I meant - I changed them both. I am reading the link now.