Multiplexed Chat Server – using select
This example presents an implementation of a client-server chat application working in the command line in an IRC style application with only one room. The server accepts multiple TCP clients and relays messages passed by each client to all other connected clients. The client needs to be able to:
· read messages from standard input and pass them to the server;
· read messages relayed from the server and display them to the user;
The motivation of using select lies here in the fact that there are multiple descriptors that need to be watched by both the client and the server. The server also needs a shared state approach where creating a new process to handle each client would only make things complicated.
Multiplexed chat SERVER |
Chat CLIENT |
/* // Multiperson chat server using select // Server part */
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>
//#define PORT 9034 // port we're listening on fd_set master; // master file descriptor list fd_set read_fds; // temp file descriptor list for select() struct sockaddr_in myaddr; // server address struct sockaddr_in remoteaddr; // client address int fdmax; // maximum file descriptor number int listener; // listening socket descriptor int newfd; // newly accept()ed socket descriptor char buf[256], tmpbuf[256]; // buffer for client data int nbytes, ret; int yes=1; // for setsockopt() SO_REUSEADDR, below int addrlen; int i, j, crt, int_port,client_count=0;
struct sockaddr_in getSocketName(int s, bool local_or_remote) { struct sockaddr_in addr; int addrlen = sizeof(addr); int ret;
memset(&addr, 0, sizeof(addr)); ret = (local_or_remote==true?getsockname(s,(struct sockaddr *)&addr,(socklen_t*)&addrlen): getpeername(s,(struct sockaddr *)&addr, (socklen_t*)&addrlen) ); if (ret < 0) perror("getsock(peer)name"); return addr; }
char * getIPAddress(int s, bool local_or_remote) { struct sockaddr_in addr; addr = getSocketName(s, local_or_remote); return inet_ntoa(addr.sin_addr); }
int getPort(int s, bool local_or_remote) { struct sockaddr_in addr; addr = getSocketName(s, local_or_remote); return addr.sin_port; }
// send to everyone void sendToALL(char * buf, int nbytes) { int j, ret; for(j = 0; j <= fdmax; j++) { if (FD_ISSET(j, &master)) // except the listener and ourselves if (j != listener && j != crt) if ( send(j, buf, nbytes, 0) == -1) perror("send"); } return; }
int main(int argc, char **argv) { if (argc < 2 ) { printf("Usage:\n%s <portno>\n",argv[0]); exit(1); } int_port = atoi(argv[1]); FD_ZERO(&master); // clear the master and temp sets FD_ZERO(&read_fds);
// get the listener if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); }
// lose the pesky "address already in use" error message if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int) ) == -1) { perror("setsockopt:"); exit(1); }
// bind memset(&myaddr, 0, sizeof(myaddr)); myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = INADDR_ANY; myaddr.sin_port = htons(int_port); // memset(&(myaddr.sin_zero), '\0', 8); if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) { perror("bind:"); exit(1); } // listen if (listen(listener, 10) == -1) { perror("listen"); exit(1); } // add the listener to the master set FD_SET(listener, &master); // keep track of the biggest file descriptor fdmax = listener; // so far, it's this one // main loop for(;;) { read_fds = master; // copy it if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) { perror("select"); exit(1); } // run through the existing connections looking for data to read for(i = 0; i <= fdmax; i++) { if (FD_ISSET(i, &read_fds)) { // we got one!! crt = i; if (i == listener) { // handle new connections addrlen = sizeof(remoteaddr); if ((newfd = accept(listener, (struct sockaddr *)&remoteaddr,(socklen_t*)&addrlen)) == -1){ perror("accept"); } else { FD_SET(newfd, &master); // add to master set if (newfd > fdmax) { // keep track of the maximum fdmax = newfd; } printf("selectserver: new connection from %s on " "socket %d\n", getIPAddress(newfd, false),newfd); client_count++; sprintf(buf,"Hi-you are client :[%d] (%s:%d) connected to server %s\nThere are %d clients connected\n", newfd, getIPAddress(newfd,false), getPort(newfd, false), getIPAddress(listener, true), client_count); send(newfd,buf,strlen(buf)+1,0); } } else { // handle data from a client if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0) { // got error or connection closed by client if (nbytes == 0) { // connection closed printf("<selectserver>: client %d forcibly hung up\n", i); } else perror("recv"); client_count--; close(i); // bye! FD_CLR(i, &master); // remove from master set } else { // we got some data from a client // check for connection close request buf[nbytes]=0; //printf("%s\n",buf); if ( (strncasecmp("QUIT\n",buf,4) == 0)) { sprintf(buf,"Request granted [%d] - %s. Disconnecting...\n",i,getIPAddress(i,false)); send(i,buf, strlen(buf)+1,0); nbytes = sprintf(tmpbuf,"<%s - [%d]> disconnected\n",getIPAddress(i,false), i); sendToALL(tmpbuf,nbytes); client_count--; close(i); FD_CLR(i,&master); } else { nbytes = sprintf(tmpbuf, "<%s - [%d]> %s",getIPAddress(crt, false),crt, buf); sendToALL(tmpbuf, nbytes); } } } } } } return 0; }
|
/* // Multiperson chat using select // Client part */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h>
fd_set read_fds,master; // temp file descriptor list for select() int sock; //socket struct sockaddr_in servaddr; char buf[256]; // buffer for client data int nbytes, ret, int_port;
int main(int argc, char **argv) { if (argc < 3 ) { printf("Usage:\n%s <hostname or IP address> <portno>\n",argv[0]); exit(1); } int_port = atoi(argv[2]); int ipaddr = inet_addr(argv[1]); // check if address is a hostname printf("%s => %d ip address\n",argv[1],ipaddr); if (ipaddr == -1 ) { struct in_addr inaddr; struct hostent * host = gethostbyname( argv[1] ); if (host == NULL ) { printf("Error getting the host address\n"); exit(1); } memcpy(&inaddr.s_addr, host->h_addr_list[0],sizeof(inaddr)); printf("Connecting to %s ...\n",inet_ntoa( inaddr) ); memcpy(&ipaddr, host->h_addr_list[0],sizeof(unsigned long int)) ; }
// get the socket if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } memset(&servaddr,0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = ipaddr; servaddr.sin_port = htons( int_port ); // connect to server if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 ) { perror("connect"); exit(1); } // add the listener to the master set FD_ZERO(&read_fds); // clear the set FD_ZERO(&master); FD_SET(0, &master); FD_SET(sock, &master);
for(;;) { read_fds = master; if (select(sock+1, &read_fds, NULL, NULL, NULL) == -1) { perror("select"); exit(1); } // check if read from keyboard if ( FD_ISSET(0, &read_fds) ) { nbytes = read(0, buf,sizeof(buf)-1); ret = send(sock, buf, nbytes,0); if (ret <= 0 ){ perror("send"); exit(1); } //else printf("WARNING Not all data has been sent: %d bytes out of %d\n", ret, nbytes); } // check if read from server if ( FD_ISSET(sock, &read_fds) ) { nbytes = read(sock, buf, sizeof(buf)-1); if (nbytes <= 0) { printf("Server has closed connection... closing...\n"); exit(2); } write(1,buf, nbytes); } } return 0; }
|