Reputation: 5
Im trying to make a basic non blocking chat client, but i cant really understand select()
and FD_ISSET()
. im trying to listen to the socket with the code below, but it wont work, it doesn't print anything, why not?
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
int main(int argc, char const* argv[])
{
fd_set readfs;
char sendline[100];
char str[100];
char *some_addr;
int listen_fd, comm_fd;
struct sockaddr_in servaddr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
//Socket error
if (listen_fd == -1) {
printf("Error on getting socket, Exiting!\n");
return 1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htons(INADDR_ANY);
servaddr.sin_port=htons(22000);
bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listen_fd, 10);
comm_fd = accept(listen_fd, (struct sockaddr *) NULL, NULL);
FD_ZERO(&readfs);
FD_SET(comm_fd, &readfs);
while (1)
{
select(listen_fd,&readfs, NULL, NULL, NULL);
if(FD_ISSET(listen_fd,&readfs))
{
bzero(str,100);
read(listen_fd,str,100);
printf("%s", str);
/* write(listen_fd, "read!", strlen(str)+1); */
}
}
return 0;
}
EDIT: My code trying to Connect to the server:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include<string.h>
int main(int argc,char **argv)
{
int sockfd,n;
char sendline[100];
char recvline[100];
struct sockaddr_in servaddr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof servaddr);
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(22000);
inet_pton(AF_INET,"127.0.0.1",&(servaddr.sin_addr));
connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
while(1)
{
bzero( sendline, 100);
bzero( recvline, 100);
fgets(sendline,100,stdin); /*stdin = 0 , for standard input */
write(sockfd,sendline,strlen(sendline)+1);
read(sockfd,recvline,100);
printf("%s\n",recvline);
}
return 0;
}
Upvotes: 0
Views: 4176
Reputation: 1
Came across this ... better late than never, I guess.
If I could add to 'mrjoltcola's responses ... perhaps a good way to describe the 'select()' statement is that it's a DE-MULTIPLEXER of I/O devices. You can ask the O/S to simultaneously watch socket-descriptors and file-descriptors from multiple sources. When they're interrupted, the O/S will notify the application which devices were triggered/interrupted.
In the case of sockets, an application can have the O/S watch a 'listening' socket as well as several already-connected client sockets. The application can respond to message traffic from clients ... accept new client connections ... or drop/disconnect clients that have terminated.
That said, here's the SERVER code with 'mrjoltcola's fixes for:
My bad, but I did not implement
Also, I replaced the 'read()' and 'write()' operations with 'recv()' and 'send()', respectively. They're semantically the same because the 'flags' option is set to 0. But in some applications the 'flags' parameters might be helpful.
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc, char const* argv[])
{
fd_set readfs;
int nfds = 0;
char str[100];
int listen_fd, comm_fd;
struct sockaddr_in servaddr;
printf("---> Begin Server:\n");
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
//Socket error
if (listen_fd == -1) {
printf("Error on getting socket, Exiting!\n");
return 1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port=htons(22000);
printf("---> Bind listening socket to port: %hd\n",
ntohs(servaddr.sin_port));
bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
printf("---> Listen for connection request\n");
listen(listen_fd, 10);
printf("---> Accept connection request\n");
comm_fd = accept(listen_fd, (struct sockaddr *) NULL, NULL);
while (1)
{
FD_ZERO(&readfs);
FD_SET(comm_fd, &readfs);
nfds = comm_fd + 1;
printf("---> Wait for message\n");
select(nfds, &readfs, NULL, NULL, NULL);
if(FD_ISSET(comm_fd,&readfs))
{
printf("---> Received message\n");
bzero(str,100);
recv(comm_fd,str,100, 0);
printf("---> Received Msg: %s", str);
printf("---> Send reply\n");
send(comm_fd, "read!", strlen(str)+1, 0);
}
}
return 0;
}
/* EOF */
And the CLIENT code is:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc,char **argv)
{
int sockfd;
char sendline[100];
char recvline[100];
struct sockaddr_in servaddr;
printf("---> Begin Client\n");
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof servaddr);
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(22000);
inet_pton(AF_INET,"127.0.0.1",&(servaddr.sin_addr));
printf("---> Connect to svr:\n"
"---> port...: %hd\n",
ntohs(servaddr.sin_port));
connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
while(1)
{
bzero( sendline, 100);
bzero( recvline, 100);
printf("---> Enter message:\n");
fgets(sendline,100,stdin); /*stdin = 0 , for standard input */
printf("---> Sending: %s\n",
sendline);
send(sockfd, sendline, strlen(sendline)+1, 0);
printf("---> Receive reply\n");
recv(sockfd,recvline,100, 0);
printf("---> Reply: %s\n",recvline);
}
return 0;
}
/* EOF */
These compiled/linked/ran on my little CentOS laptop ... gcc ... etc.
By the way, if you ever find yourself using 'select()' on a Windows node, the 'nfds' argument is 0. Microsoft didn't fully comply with the original BSD sockets interface. Their implementation ('under-the-hood') is different, but the behavior is the same ... to de-multiplex I/O from multiple devices for reading/writing/errors.
Upvotes: 0
Reputation: 20842
but i cant really understand select() and FD_ISSET()
An fd_set is like a bit array. Each bit in the array represents a socket or file descriptor.
FD_ISSET() is a macro or function that tells you whether a given socket descriptor (4, for example) is set in the bit array (fd_set). FD_SET() allows you to set a bit yourself, and FD_CLR() lets you clear a bit.
The bits don't just get set magically, you use select()
to ask the OS kernel to set or clear each bit in the fd_set accordingly, then you check each bit with FD_ISSET() and act accordingly. Before calling select()
you must setup the sets to tell the kernel which descriptors you are interested in polling by setting the bits in the fd_set
using FD_SET() or if you have lots of sockets/bits to set, using a master fd_set and copying the whole thing to your read, write or error set. I usually did the latter for efficiency. These are integers typically from 0 to N (first 3 are usually not sockets so you normally poll 3 .. N). After select returns, you must check the bits. If a bit is set in readfds
it is ready for reading.
select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
The supported statuses are "ready for read", "ready for write", and "error condition"
If you don't set a particular bit in the set, the kernel won't report its status to you.
As well, if you don't set the nfds
param (max descriptor value) high enough, any descriptors above the max will be ignored. The descriptors do not have to be contiguous, just within the range of nfds
.
All of this logic assumes successful return values on the system calls. If a system call returns an error status, you don't even regard the data structures for that call and must recover or process appropriately.
The primary problem that jumps out at me in your code is your select call's first argument. It isn't going to check comm_fd is comm_fd is lower than listen_fd.
I recommend you keep an int value of max_desc
and each each time you accept a new socket, set max_desc = MAX(max_desc, new_fd+1)
, as well, you'll need to adjust it downward when closing out sockets. I always prefer to keep a separate fd_set just to track the descriptors my process has open (never pass it to select() just use it for bookkeeping).
Upvotes: 0
Reputation: 25908
Four major problems, here:
Your select()
call and the read/write loop should be using comm_fd
, not listen_fd
. If you call select()
on listen_fd
it'll return when there is an accept()
able connection available, but you want to wait on the connected socket you already have for input, so use comm_fd
.
The first argument to select()
should be the highest file descriptor in the sets plus one. Since you only have one file descriptor, here, it should be comm_fd + 1
.
You should move your FD_ZERO
and FD_SET
macros inside the while
loop, and execute them prior to every select()
call, because select()
is going to modify those fd sets you pass to it.
You don't check the return from your system calls for errors. You should.
Other points:
bzero()
has been removed from POSIX for quite some time, now, you should be using the standard memset()
instead.
You shouldn't pass INADDR_ANY
though htons()
, just use it as it is.
It's only a comment in your program, but while STDIN_FILENO
may be 0
, stdin
is a FILE
pointer, and is not 0
.
Upvotes: 1