At some point you will run into a need for more flexibility than a simple read or write operation on a socket. The problem with read and write is that these 2 calls block until the operation is done. Especially the read operation may wait for a while. The solution to this is the unique and flexible select command.
select allows you to make one call that simulataneously wait for:
select steps:
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
main (int argc, char **argv)
{
struct timeval stv;
int nread, iMaxFd, iNumInputs, nContinue = 1;
fd_set fdCheck;
char buffer[10000];
for(;nContinue;)
{
// creating a 2 and 1/2 second timeout
stv.tv_sec = 2;
stv.tv_usec = 500000; // 1/2 second (units = microseconds)
FD_ZERO(&fdCheck);
iMaxFd = STDIN_FILENO+1;
FD_SET(STDIN_FILENO , &fdCheck);
// This select will return when the 2 1/2 second timeout expires
// or STDIN returns some input.
iNumInputs = select(iMaxFd, &fdCheck, 0, 0, &stv);
if (iNumInputs == -1)
{
/* Error*/
printf("\n Bad return from select ==> die\n");
nContinue = 0;
}
else if (iNumInputs == 0)
{
/* Timeout */
printf("\n Timeout from select\n");
}
else if (FD_ISSET(STDIN_FILENO , &fdCheck))
{
// This read won't be blocking
nread = read(STDIN_FILENO, buffer, sizeof(buffer));
if (nread == 0)
{
// Probably Control-D on your Keyboard
printf("keyboard done\n");
exit(0);
}
if (nread > 0)
{
buffer[nread] = 0;
printf("Received from keyboard: %s\n", buffer);
}
}
}
}
The following program is going to be used as a client to a chat program. The program is close to what telnet functionally does for you. It doesn't quite work for telnet because it doesn't know how to negotiate the login sequence.
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void chatUser(char *hostname, int aPort);
main (int argc, char *argv[])
{
int i, aPort=7777;
char hostname[64];
printf("usage:%s [-p port] [-h hostname] \n" , argv[0]);
gethostname(hostname, sizeof(hostname));
for (i = 1; i < argc; i++)
{
if (strcmp(argv[i],"-h") == 0)
if (i < argc-1)
strcpy(hostname, argv[i+1]);
if (strcmp(argv[i],"-p") == 0)
if (i < argc-1)
aPort = atoi(argv[i+1]);
}
printf("\nStarting chat session: host(%s) port(%d)\n",
hostname, aPort);
chatUser(hostname, aPort);
exit(0);
}
#define BUFF_SIZE 10000
void chatUser(char *hostname, int aPort)
{
unsigned char buffer[BUFF_SIZE];
struct sockaddr_in sin;
struct hostent *hp;
struct timeval stv;
int i, nread;
int fd_chat = -1;
int iMaxFd, iNumInputs, nContinue = 1;
fd_set fdCheck;
// Now we need to create a socket connection with the chat server
//
// Get the IP address of the hostname
hp = gethostbyname(hostname);
if (hp == NULL)
{
printf("\n%s: unknown host.\n", hostname);
nContinue = 0;
}
else
{
// Create a socket and connect to the chat server
if ((fd_chat = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("client: socket()");
nContinue = 0;
}
else
{
sin.sin_family = AF_INET;
sin.sin_port = htons(aPort);
memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
if (connect(fd_chat , (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
perror("client: connect()");
nContinue = 0;
}
}
}
// Now that we have connected to the other end, we will sit waiting for
// input from either the fd_chat socket or from STDIN.
// When we read from fd_chat, we write the received buffer to STDOUT.
// When we receive from the keyboard (STDIN), we send the contents to
// the chat server.
// The select statement is the key call in this routine.
for(;nContinue;)
{
stv.tv_sec = 0;
stv.tv_usec = 0;
FD_ZERO(&fdCheck);
iMaxFd = STDIN_FILENO;
if (fd_chat > STDIN_FILENO ) iMaxFd = fd_chat ;
iMaxFd += 1;
// Set up mask to look for input from fd_chat or STDIN
FD_SET(STDIN_FILENO , &fdCheck);
FD_SET(fd_chat , &fdCheck);
iNumInputs = select(iMaxFd, &fdCheck, 0, 0, NULL /* &stv*/);
if (iNumInputs == -1)
{
/* Error*/
printf("\n Bad return from select ==> die\n");
nContinue = 0;
}
else if (iNumInputs == 0)
{
/* Timeout */
printf("\n Timeout from select\n");
nContinue = 0;
}
else if (FD_ISSET(fd_chat , &fdCheck))
{
// Can safely read without blocking
nread = read(fd_chat, buffer, BUFF_SIZE);
if (nread > 0)
{
// Received from chat server
buffer[nread] = 0;
write(STDOUT_FILENO, buffer, nread);
}
else
{
// chat server socket seems to have disconnected
if (nread == 0) nContinue = 0;
if (nread < 0)
fprintf(stderr,"fd_chat err:%s\n",
strerror(errno));
}
}
else if (FD_ISSET(STDIN_FILENO , &fdCheck))
{
// can safely read without blocking
nread = read(STDIN_FILENO, buffer, BUFF_SIZE);
if (nread > 0)
{
// Keyboard input that needs to be sent to the chat server
buffer[nread] = 0;
write(fd_chat, buffer, nread);
}
}
}
if (fd_chat >= 0)
close(fd_chat );
exit(0);
}
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int all_flag = 0;
void server(char *hostname, int aPort, int bPort);
void myChild(int fd_client , int aPort, int bPort, char *hostname);
int readWrite(int fdRead, int fdWrite, char * readId, char * writeId);
void dumpAsciiOnly(unsigned char * buffer, int num);
// Signal handler to process terminated children
void mysig(int nsig)
{
int nStatus, nPid;
nPid = waitpid(-1, &nStatus, WNOHANG);
printf("\nSignal(%d) received\n****************************\n\n\n\n\n\n\n",
nsig);
signal(SIGCHLD, mysig);
}
main (int argc, char **argv)
{
int i, aPort, bPort;
char hostname[64];
if (argc < 3)
{
fprintf(stderr,
"usage:%s porta portb [-h hostname] [-a]\n" , argv[0]);
exit(1);
}
aPort = atoi(argv[1]);
bPort = atoi(argv[2]);
gethostname(hostname, sizeof(hostname));
for (i = 3; i < argc; i++)
{
if (strcmp(argv[i],"-h") == 0)
if (i < argc-1)
strcpy(hostname, argv[i+1]);
if (strcmp(argv[i],"-a") == 0)
all_flag = 1;
}
printf("\nI will be an intermediary between ports %d and %d host(%s)\n",
aPort, bPort, hostname);
signal(SIGCHLD, mysig); // To clean up terminated children
server(hostname, aPort, bPort);
exit(0);
}
// Server Loop for processing Snoop Requests
// Any new connect to snoop results in spawning a child
// process to baby sit the connection
void server(char *hostname, int aPort, int bPort)
{
struct sockaddr_in sin, fsin;
int nChildCnt = 0;
int fd_connect, fd_accept;
int fromlen, nOpt =1;
long pid;
// First of all snoop needs to present a Server interface.
// Nothing new here
if ((fd_accept = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("server: socket()");
exit(1);
}
setsockopt(fd_accept, SOL_SOCKET, SO_REUSEADDR, &nOpt, sizeof(int));
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(aPort);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd_accept, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
perror("server: bind()");
exit(1);
}
if (listen(fd_accept, 5) < 0)
{
perror("server: listen()");
exit(1);
}
fromlen = sizeof(fsin);
for (;;)
{
if ((fd_connect = accept(fd_accept, (struct sockaddr *)&fsin, &fromlen)) < 0)
{
perror("server:accept()");
continue; /* hope this is normally due to a child death*/
}
nChildCnt += 1;
printf("\n#_#_#_#_#_#_#_#_#_#_#_#_\nCHILD(%d)\n",
nChildCnt);
fflush(stdout);
if ((pid = fork()) > 0)
{
/* Parent */
close(fd_connect);
continue;
}
if (pid == 0)
{
/* Must be the child */
myChild(fd_connect, aPort, bPort, hostname); /* Never returns */
}
if (pid < 0)
printf("**** Can't fork CHILD\n");
}
close(fd_accept);
}
// Child process to manager one connect requests reads/writes
// When the connection seems to go away, the child terminates
void myChild(int fd_client , int aPort, int bPort, char *hostname)
{
struct sockaddr_in sin;
struct hostent *hp;
struct timeval stv;
int i;
int fd_server = -1;
int iMaxFd, iNumInputs, nContinue = 1;
fd_set fdCheck;
// Client has connected to snoop. Now snoop needs to connect to the
// desired server.
//
hp = gethostbyname(hostname);
if (hp == NULL)
{
printf("\n%s: unknown host.\n", hostname);
nContinue = 0;
}
else
{
if ((fd_server = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("client: socket()");
nContinue = 0;
}
else
{
sin.sin_family = AF_INET;
sin.sin_port = htons(bPort);
memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
if (connect(fd_server , (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
perror("client: connect()");
nContinue = 0;
}
}
}
// Now that we have connected to the other end, we will sit waiting for
// input from either of the 2 sockets. When we read from one socket,
// we write to the other socket. We also dump out the buffer read if its
// ascii.
// The select statement is the key call in this routine.
for(;nContinue;)
{
stv.tv_sec = 0;
stv.tv_usec = 0;
FD_ZERO(&fdCheck);
iMaxFd = fd_client ;
if (fd_server > fd_client ) iMaxFd = fd_server ;
else iMaxFd = fd_client ;
iMaxFd += 1;
FD_SET(fd_client , &fdCheck);
FD_SET(fd_server , &fdCheck);
iNumInputs = select(iMaxFd, &fdCheck, 0, 0, NULL /* &stv*/);
if (iNumInputs == -1)
{
/* Error*/
printf("\n Bad return from select ==> die\n");
nContinue = 0;
}
else if (iNumInputs == 0)
{
/* Timeout */
printf("\n Timeout from select\n");
nContinue = 0;
}
else if (FD_ISSET(fd_client , &fdCheck))
{
// read from fd_client and write to fd_server . If nContinue is returned
// as a zero then one of our sockets has closed.
nContinue = readWrite(fd_client , fd_server , "Client","Server");
}
else if (FD_ISSET(fd_server , &fdCheck))
{
// read from fd_server and write to fd_client . If nContinue is returned
// as a zero then one of our sockets has closed.
nContinue = readWrite(fd_server , fd_client , "Server", "Client");
}
}
if (fd_server >= 0)
close(fd_server );
close(fd_client );
exit(0);
}
// The readWrite routine reads from one file descriptor and
// writes to another file descriptor. The buffer read is then
// dumped to the screen
#define BUFF_SIZE 10000
int readWrite(int fdRead, int fdWrite, char * readId, char * writeId)
{
int nBytes;
int nCont =1;
unsigned char buffer[2*(BUFF_SIZE+1)];
nBytes = read(fdRead, buffer, BUFF_SIZE);
printf("\nRead %d bytes from %s\n", nBytes, readId);
if (nBytes > 0)
{
if (write(fdWrite, buffer, nBytes) <= 0)
{
printf("\nWrite Error on %s ==> "
"Broken Connection\n", writeId);
nCont = 0;
}
dumpAsciiOnly(buffer, nBytes); // print out buffer
}
else
{
printf("\n%s read (%d) error ==> "
"Connection Broken\n",readId, nBytes);
nCont = 0;
}
fflush(stdout);
return nCont;
}
// The following routine carefully copies a read buffer to
// the screen. If a binary file is passed like a gif or jpeg
// then we stop trying to print to the screen once we see
// non-ascii characters.
void dumpAsciiOnly(unsigned char * buffer, int num)
{
int i;
for (i = 0; i < num; i++)
{
if (buffer[i] > 0x7f)
{
printf("%2x", buffer[i]);
if (!all_flag)break; // Non Ascii char found
}
if (buffer[i] < ' ')
{
if ((buffer[i] != '\n') && (buffer[i] != '\r') &&
(buffer[i] != '\t') && (buffer[i] != '\b'))
{
printf("%2x", buffer[i]);
if (!all_flag)break; // Non Ascii char found
}
}
printf("%c", buffer[i]); // Good Ascii character
}
}
#include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/time.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #define NAME_SIZE 40 #define MAX_CHATS 40 #define BUFF_SIZE 10000 // Message formats #define chatFull "Max Chats ... Sorry" #define welcome_msg "Welcome to chatter:\n Enter your name with a command like:\n-name=Clem\n" #define change_name "-name=" // Array to contain the sockets for all of the chat users int fd_chats[MAX_CHATS]; struct name_rec { char name[NAME_SIZE+1]; }; typedef struct name_rec NAME_REC; // Array to hold the names for all of the chat users NAME_REC name_chats[MAX_CHATS]; int numChats = 0; void server( int aPort); void chatLoop(int fd_accept); void processChat(int instance, char * buffer, int nread); void addChatter(int newConnect); void remove_chatter(int fd_toRemove); main (int argc, char **argv) { int i, aPort=7777; char hostname[64]; fprintf(stderr, "usage:%s [port] \n" , argv[0]); if (argc > 1) aPort = atoi(argv[1]); gethostname(hostname, sizeof(hostname)); printf("\nI will be your chat server(%s) on port %d\n\n", hostname, aPort); // real work happens here server(aPort); exit(0); } void server(int aPort) { struct sockaddr_in sin, fsin; int nChildCnt = 0; int fd_connect, fd_accept; int fromlen, nOpt =1; long pid; if ((fd_accept = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("server: socket()"); exit(1); } // The following option allows a port to be reused after exitting this // server. Otherwise, we end up waiting for a while setsockopt(fd_accept, SOL_SOCKET, SO_REUSEADDR, &nOpt, sizeof(int)); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(aPort); sin.sin_addr.s_addr = htonl(INADDR_ANY); // Bind socket to port number if (bind(fd_accept, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("server: bind()"); exit(1); } // Set up Listen queue of 5 simultaneous clients awaiting connect if (listen(fd_accept, 5) < 0) { perror("server: listen()"); exit(1); } // Notice we don't block on an accept call like all the other servers // Accept loop in the following routine chatLoop(fd_accept); } void chatLoop(int fd_accept) { unsigned char buffer[BUFF_SIZE]; struct sockaddr_in fsin; int fromlen; struct timeval stv; int i; int fd_server = -1; int iMaxFd, iNumInputs, nContinue = 1; int newConnect, nread; fd_set fdCheck; // The select statement is the key call in this routine. for(;nContinue;) { stv.tv_sec = 0; stv.tv_usec = 0; FD_ZERO(&fdCheck); // Select mask contains accept socket number FD_SET(fd_accept , &fdCheck); iMaxFd = fd_accept; // Select mask contains socket number of each chat user's socket for (i = 0; i < numChats; i++) { FD_SET(fd_chats[i], &fdCheck); if (fd_chats[i] > iMaxFd) iMaxFd = fd_chats[i]; } iMaxFd += 1; // No timeout on the select iNumInputs = select(iMaxFd, &fdCheck, 0, 0, NULL /* &stv*/); if (iNumInputs == -1) { /* Error*/ printf("\n Bad return from select ==> die\n"); nContinue = 0; } else if (iNumInputs == 0) { /* Timeout -- won't occur for us.*/ printf("\n Timeout from select\n"); nContinue = 0; } else { // Pending connect request of new chat user if (FD_ISSET(fd_accept , &fdCheck)) { // Now we can make an accept without blocking fromlen = sizeof(fsin); newConnect = accept(fd_accept, (struct sockaddr *)&fsin, &fromlen); printf("New Connect : %d\n", newConnect); if (newConnect < 0) { fprintf(stderr,"accept err: %s\n", strerror(errno)); continue; } if (numChats >= MAX_CHATS) { // We are full message sent printf("%s\n", chatFull); write(newConnect, chatFull, strlen(chatFull)); close(newConnect); } else addChatter(newConnect); // add new user to tables } // end of fd_accept handling for (i =0; i < numChats; i++) { if (FD_ISSET(fd_chats[i], &fdCheck)) { // Received message from one of the chat clients nread = read(fd_chats[i], buffer, BUFF_SIZE); if (nread > 0) { if (buffer[nread-1] == '\n') buffer[nread-1] = 0; else buffer[nread] = 0; // processChat sends out information to all // chatters processChat(i, buffer, nread); } else { printf("Time to close out chat file descriptor: %d\n", fd_chats[i]); if (nread < 0) fprintf(stderr, "Read err:%s\n", strerror(errno)); // close out terminated chat user and compress the // chat tables remove_chatter(i); } } } // end of for loop on numChats } } // End of the select for loop } //************************************************ // processChat called to deal with each chat message void processChat(int instance, char * buffer, int nread) { unsigned char outbuff[BUFF_SIZE]; int i, len; len = strlen(change_name); if (strncmp(change_name, buffer, len)== 0) { // Special message of chatter requesting a name something like: // -name=Clem // In this case we copy Clem into the name_chats table strcpy((char *)&name_chats[instance], &buffer[len]); return; } // Prefix buffer with name of user who sent it len = sprintf(outbuff, "%s:\n%s\n", (char *)&name_chats[instance], buffer); for (i=0; i < numChats; i++) { // Send buffer to all chatters write(fd_chats[i], outbuff, len); } } // ******************************************************* // addChatter called to deal with a new Chatter connect void addChatter(int newConnect) { unsigned char buffer[BUFF_SIZE]; int i,total = 0; sprintf(buffer,"unknown:%d", numChats); strcpy((char *)&name_chats[numChats], buffer); // Create welcome message along with a list of who is currently // connected to the chat server total = sprintf(buffer,"%s\n", welcome_msg); if (numChats <= 0) total +=sprintf(&buffer[total], "You are the first chatter\n"); else { total += sprintf(&buffer[total], " Current Chatters\n"); for ( i=0; i < numChats; i++) { total += sprintf(&buffer[total], "%s\n", (char *) &name_chats[i]); } } // Send message back to new connecting chatter write(newConnect, buffer, total); // Add Chatter to fd_chat table fd_chats[numChats++] = newConnect; } void remove_chatter(int fd_toRemove) { int k, fd_save; // close out terminated chat user and compress the // chat tables fd_save = fd_chats[fd_toRemove]; for (k=fd_toRemove; k < numChats -1; k++) { fd_chats[k] = fd_chats[k+1]; strcpy((char *)&name_chats[k], (char *)&name_chats[k+1]); } numChats -= 1; printf("Closing %d\n", fd_save); close(fd_save); }
I don't want to spend much time on this, but I once had to create a method for issuing commands from host that executed on many other hosts. The following web page has a version of the code developed for this project: