C - Making UTP server/client chatroom with select() - c

I'm supposed to make the server broadcast the message it gets from a client to all the other clients connected.
The actual broadcast works, but I have no idea how to stop the clients from infinitely printing "[client]Received from friends:" when I CTRL+C the server,
OR
how to stop the Server from infinitely printing "[server]Message received..." when I CTRL+C any of the connected Clients. Or how to add a verification somewhere so that the Client will disconnect when trying to send the string "quit"
Maybe I'm asking for too much, but could someone please explain to me what exactly does select(..) do? I understand that it's monitoring the FDs, but I can't fully understand what's going on(step-by-step) after 1 second. Does it go through all FDs ,1 second for each then repeat? I kind of get the idea, but not entirely.
Thank you either way
SERVER
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#define PORT 3050 //The port used by Clients
extern int errno;
char * conv_addr (struct sockaddr_in address)
{
static char str[25];
char port[7];
strcpy (str, inet_ntoa (address.sin_addr)); /* client IP */
bzero (port, 7); /* PORT */
sprintf (port, ":%d", ntohs (address.sin_port));
strcat (str, port);
return (str);
}
void Msgs(int fd,int sd,fd_set fds,int nr);
int main ()
{
struct sockaddr_in server_addr; /* struct for Server */
struct sockaddr_in client_addr; /* struct for Clients */
fd_set readfds; /* ready-to-read File Descriptors */
fd_set actfds; /* active/existant File Descriptors */
struct timeval tv; /* time thing, for select() */
int ServerSocketFD, ClientSocketFD; /* Socket descriptors */
int optval=1; /* ????*/
int fd; /* FD used to pass through all FDs */
int nfds; /* max number of FDs */
(ServerSocketFD = socket (AF_INET, SOCK_STREAM, 0));
setsockopt(ServerSocketFD, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval) );
bzero (&server_addr, sizeof (server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl (INADDR_ANY);
server_addr.sin_port = htons (PORT);
bind (ServerSocketFD, (struct sockaddr *) &server_addr, sizeof(struct sockaddr) );
listen (ServerSocketFD, 5); //listen to maximum 5 clients,no more
FD_ZERO (&actfds);
FD_SET (ServerSocketFD, &actfds); /* Add the only existant one for now */
tv.tv_sec = 1; /* wait 1s */
tv.tv_usec = 0;
nfds = ServerSocketFD; /* max value of currently used FDs */
printf ("[server] Waiting at port :%d...\n", PORT);
fflush (stdout);
while (1) /* serve clients CONCURRENTLY */
{
bcopy ((char *) &actfds, (char *) &readfds, sizeof (readfds)); /* copy all existing FDs in actfds vector to the read-to-read-FDs vector */
select(nfds+1, &readfds, NULL, NULL, &tv);
if (FD_ISSET (ServerSocketFD, &readfds)) /* if ServerSocket is ready to read stuff */
{
bzero (&client_addr, sizeof (client_addr));
int len = sizeof (client_addr);
ClientSocketFD = accept (ServerSocketFD, (struct sockaddr *) &client_addr, &len);
if (nfds < ClientSocketFD) /* adjust max FD, for select */
nfds = ClientSocketFD;
/* Add this accepted sockets' FD to the existing FDs */
FD_SET (ClientSocketFD, &actfds);
printf("[server] Client connected, with FD %d, from this address %s.\n",ClientSocketFD, conv_addr (client_addr));
fflush (stdout);
}
for (fd = 0; fd <= nfds; fd++) /* all FDs*/
{
if (fd != ServerSocketFD && FD_ISSET (fd, &readfds)) /* is a client ready to send/get messages? */
{
Msgs(fd,ServerSocketFD,actfds,nfds);
}
}
}
}
void Msgs(int fd,int ServerSocketFD,fd_set fds,int nrFD)
{
char buffer[100];
int bytes;
char msg[100];
bytes = read (fd, msg, sizeof (buffer));
/*
if(strstr(msg,"quit")!=0)
{
FD_CLR(fd, &fds);
close(fd);
exit(1);
}
*/
printf ("[server]Message received...%s\n", msg);
for(int i=0;i<=nrFD;i++)
{
if(i!=fd && i!=ServerSocketFD)
{
write (i, msg, bytes);
}
}
}
CLIENT
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <arpa/inet.h>
extern int errno;
int port;
int main (int argc, char *argv[])
{
int ClientSocketFD;
struct sockaddr_in server_addr;
char msg[100];
char reply[100];
if (argc != 3)
{
printf ("[client] Sintax: %s <server_address> <port>\n", argv[0]);
return -1;
}
port = atoi (argv[2]);
ClientSocketFD = socket (AF_INET, SOCK_STREAM, 0);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons (port);
connect(ClientSocketFD, (struct sockaddr *) &server_addr,sizeof (struct sockaddr));
int pid;
if((pid=fork())==-1)
{
perror("Error fork()");
exit(10);
}
if(pid==0) //CHILD
{
while(1)
{
bzero(msg,100);
printf ("[client]Send something to other clients: ");
fflush (stdout);
read (0, msg, 100);
if(strstr(msg,"quit")!=0)
{
break;
}
write (ClientSocketFD, msg, 100);
}
exit(7);
}
else if(pid > 0) //PARENT
{
while(1)
{
bzero(reply,100);
read (ClientSocketFD, reply, 100);
printf ("[client]Received from friends: %s\n", reply);
}
}
close (ClientSocketFD);
}

I have no idea how to stop the clients from infinitely printing "[client]Received from friends:" when I CTRL+C the server
read() returns 0 if the server terminates, so replace the while(1) loop in the client PARENT by
while (bzero(reply, 100), read(ClientSocketFD, reply, 100) > 0)
printf("[client]Received from friends: %.100s\n", reply);
kill(pid, SIGTERM); // terminate also the child
(and #include <signal.h>).
how to stop the Server from infinitely printing "[server]Message received..." when I CTRL+C any of the connected Clients
read() returns 0 if the client terminates, so call
Msgs(fd, ServerSocketFD, &actfds, nfds);
change the Msgs() type to
void Msgs(int fd, int ServerSocketFD, fd_set *fds, int nrFD)
and in Msgs() after bytes = read (fd, msg, sizeof (buffer)); add
if (bytes <= 0) { FD_CLR(fd, fds); close(fd); return; }
could someone please explain to me what exactly does select(..) do? I understand that it's monitoring the FDs, but I can't fully understand what's going on(step-by-step) after 1 second. Does it go through all FDs ,1 second for each then repeat?
As wildplasser noted in his comment, select() possibly changes the timeout argument to indicate how much time was left, so after 1 second without FD activity we may well end up with a zero timeout and a useless busy loop. We need a select() timeout only if we want to do something when none of the monitored FDs gets ready within a certain time, so in your case it's better to specify no timeout at all:
select(nfds+1, &readfds, NULL, NULL, NULL);

Related

SIGIO never fires

ioctl() with changes a socket to asynchronous mode. By the definition on the man page, the kernel sends SIGIO when i/o is possible on the socket. I've run this with test clients and i/o is fine (packets arrive at source and destination), so why wouldn't the kernel call sigpoll?
To clarify, the problem is that despite having established the SIGIO signal and appropriating the socket to send the signal SIGIO, no signal ever fires or there is no indication that sigpoll() was called.
I've uploaded the code where I've found this issue, it will eventually be some watered down version of talk.
talkish.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <setjmp.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/in.h>
#define MAX_BUF 1000
#define CHAR_BUF 50
#define BASEPORT "10000"
void error(const char *msg){
perror(msg);
exit(1);
}
typedef struct tuple{
char HN [MAX_BUF];
char PN [MAX_BUF];
}tuple;
tuple storeCMD( char input[]){
tuple ret;
char* token = strtok(input, " ");
if (token != NULL) strcpy( ret.HN, token);
else ret.HN[0] = 0;
token = strtok(NULL, " ");
if (token != NULL) strcpy( ret.PN, token);
else ret.PN[0] = 0;
return ret;
}
void sigpoll(int sig){
printf("Signal fired!\n");
//eventual rcvfrom and other things...
}
int main(int argc, char * argv[]){
if (argc != 2){
printf("Proper usage requires 2 arguments: $talkish port-number\n");
exit(1);
}
int sd;
struct sockaddr_storage client;
socklen_t clientLen;
struct addrinfo server, *res;
struct addrinfo *serverinfo;
char buffer [MAX_BUF];
memset(&server, 0, sizeof(server));
bzero((char *) &server, sizeof(server));
server.ai_family = AF_INET;
server.ai_socktype = SOCK_DGRAM;
server.ai_flags = AI_PASSIVE;
//initially we'll use information from user, but move to partner and partnerl
//once solid connection is established.
struct sockaddr_storage partner;
socklen_t partnerl;
//Bind to argv[1]
tuple execute;
getaddrinfo(NULL, argv[1], &server, &res);
sd = socket(res -> ai_family ,res -> ai_socktype, res -> ai_protocol);
if (sd < 0) error("ERROR on socket!");
int n = bind(sd, res -> ai_addr, res -> ai_addrlen);
if (n < 0) error("ERROR on Bind!");
int flag;
flag= 1;
fcntl(sd, F_SETOWN, getpid());
signal(SIGPOLL, sigpoll); //establish sigpoll to get udp packets
ioctl(sd, FIOASYNC, &flag);
//establish timer to allow wait and see
struct timeval timer;
timer.tv_sec = 7;
//while connecting
char message[CHAR_BUF];
bzero((char *) message, CHAR_BUF);
int connecting = 1;
while(connecting){
printf ("? ");
scanf(" %[^\n]", message);
if (strlen(message) == 0);
else if ( 0 == strcmp( message, "q")){
exit (0);
}
else {
execute = storeCMD(message);
if (execute.HN[0] == 0 || execute.PN[0] == 0) printf("| Input should match \"Hostname Portname\" to connect and \"q\" to quit \n");
else {
struct sockaddr_storage dest_server;
socklen_t dest_serverl;
struct addrinfo dest_hints, *dest_res;
struct in_addr dest_addr;
memset(&dest_hints, 0, sizeof(dest_hints));
dest_hints.ai_family = AF_INET;
dest_hints.ai_socktype = SOCK_DGRAM;
dest_hints.ai_flags = AI_PASSIVE;
if (getaddrinfo( execute.HN, execute.PN, &dest_hints, &dest_res) < 0) printf("| Input should match \"Hostname Portname\" to connect and \"q\" to quit \n");
else {
bzero((char *) buffer, MAX_BUF);
sprintf(buffer, "wannachat");
sendto(sd, buffer, MAX_BUF, 0, (struct sockaddr *) dest_res -> ai_addr, dest_res -> ai_addrlen );
if (setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &timer, sizeof(timer)) < 0) error("ERROR on setsockopt");
partnerl = sizeof(partner);
bzero((char *) &partner, partnerl);
bzero((char *) buffer, MAX_BUF);
if (recvfrom(sd, buffer, MAX_BUF, 0, (struct sockaddr *)&partner, &partnerl ) < 0) printf("| No response received from %s. \n", execute.HN);
else{
if ( 0 == strcmp( buffer, "OK")){
printf("| Connected to %s. \n", execute.HN);
//chat();
}else printf("| %s does not want to talk. \n", execute.HN);
}
}
}
}
}
close(sd);
return 0;
}
To receive SIGIO notifications (also SIGURG for sockets, e.g: when receiving TCP URG data), you'll need to tell the kernel who to notify, using fcntl(fd, F_SETOWN, pid). As usual, a positive pid value refers to a process, while a negative pid refers to a process group.
On Linux, if you want to send the signal to a specific thread, you'll need to use F_SETOWN_EX. On other systems, you'll have to block the signal on other threads. using pthread_sigmask().

How does an incoming connection stop select from waiting?

As in the example, select monitors the socket of the server that listens for an incoming connection. I used telnet to test the program. In the program, select is supposed to stop waiting when there is something to read from the listener socket. I guessed telnet may send a message to the server and tried to read it, but I got nothing. Actually, the program stopped accepting new connections when I tried to read the message from telnet. I commented out the message reading code. Can someone explain why select stops waiting when there is a new connection?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include "../cus_header/cus_header.h"
#define PORT "30000" // the port users will be connecting to
#define MY_IP "127.0.0.1"
#define BACKLOG 10 // how many pending connections queue will hold
#define MAXLEN 1000
void *get_client_addr(struct sockaddr * sa){
if(sa->sa_family == AF_INET){
return &(((struct sockaddr_in *)(sa))->sin_addr);
}
return &(((struct sockaddr_in6 *)(sa))->sin6_addr);
}
int main(int argc, char *argv[]){
struct addrinfo hints, *res, *p;
struct sockaddr_storage client_addr;
int client_add_len;
char client_ip[INET6_ADDRSTRLEN];
int a;
int listener, new_fd;
int yes = 1;
socklen_t c_addr_size;
char msg [] = "Hello client\n"; // message to the client
char *msg_p;
int msg_len = strlen(msg);
// load data to struct addrinfo
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
//create socket
if((a = getaddrinfo(MY_IP, PORT, &hints, &res)) == -1){
fprintf(stderr, "Cannot get address info: %s", gai_strerror(a));
return 1;
}
p = res;
// loop through all the results
for(p = res; p != NULL; p = p->ai_next){
// create socket
if((listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
printf("cannot create the socket\n");
continue;
}
if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
error("cannot set reused for the socket");
}
// bind socket to port
if(bind(listener, p->ai_addr, p->ai_addrlen) == -1){
printf("cannot bind the socket\n");
continue;
}
break;
}
if(p == NULL){
error("Cannot create socket or bind the socket to the port");
}
freeaddrinfo(res);
// listen incoming connections
if(listen(listener, BACKLOG) == -1){
error("Cannot listen to connection");
}
// ready to communicate
puts("Waiting for connection ...");
fd_set master_set, copy_master_set;
int fd_max;
int client_fd[20]; // store all the new fd here
// accept connection and talk with clients
while(1){
FD_ZERO(&master_set);
FD_SET(listener, &master_set);
fd_max = listener;
copy_master_set = master_set;
if(select(fd_max + 1, &copy_master_set, NULL, NULL, NULL) == -1){
error("Select");
}
int i;
// set all the available client fd
for(i = 0;i <= fd_max; i++){
if(FD_ISSET(i, &copy_master_set)){
if(i == listener){
// got a new connection
client_add_len = sizeof client_addr;
if((new_fd = accept(listener, (struct sockaddr *)&client_addr, &client_add_len)) == -1){
error("New connection");
}
FD_SET(new_fd, &master_set);
if(new_fd > fd_max){
fd_max = new_fd;
}
printf("New connection from %s on socket %i\n",
inet_ntop(client_addr.ss_family, get_client_addr((struct sockaddr *)&client_addr), client_ip, INET6_ADDRSTRLEN),
new_fd);
/*
char buf[MAXLEN];
int b;
if((b=recv(listener, buf, MAXLEN, 0)) == -1){
error("read message");
}else if(b == 0){
printf("Message from client: %s", buf);
}
printf("Message from client: %s", buf);
*/
}else{
// handle clients
}
}
}
}
return 0;
}
From the man page for select:
DESCRIPTION
select() and pselect() allow a program to monitor multiple file
descriptors, waiting until one or more of the file descriptors
become "ready" for some class of I/O operation (e.g., input
possible). A file descriptor is considered ready if it is pos-
sible to perform the corresponding I/O operation (e.g.,
read(2)) without blocking.
In this case, the file descriptor becomes ready when the socket receives an incoming connection and it is possible to perform the corresponding I/O operation of accept(2).

writing to a close socket didn't raise a SIGPIPE as expected

I've already read about how to prevent SIGPIPE, then I write a small program to test it. Here is the code.
server.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
void hdl(int sig_num, siginfo_t *sig_info, void *context)
{
printf("got you, SIGPIPE!\n");
}
int main()
{
int sfd, cfd;
struct sockaddr_in saddr, caddr;
struct sigaction act;
memset (&act, '\0', sizeof(act));
act.sa_sigaction = hdl;
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGPIPE, &act, NULL) < 0) {
return 1;
}
sfd= socket(AF_INET, SOCK_STREAM, 0);
saddr.sin_family=AF_INET;
saddr.sin_addr.s_addr=inet_addr("192.168.22.91");
saddr.sin_port=htons(12345);
if(bind(sfd, (struct sockaddr *)&saddr, sizeof(saddr)) )
{
printf("bind error\n");
return -1;
}
if(listen(sfd, 1))
{
printf("error\n");
return -1;
}
char buf[1024] = {0};
while(1) {
printf("Server listening...\n");
cfd=accept(sfd, (struct sockaddr *)NULL, NULL);
fcntl(cfd, F_SETFL, O_NONBLOCK);
int size = read(cfd, buf, 1024);
if(size == -1)
printf("read error\n");
sleep(2); // sleep for a while to make sure the client closed the socket
int ret;
if((ret = write(cfd, buf, strlen(buf)))<0)
{
if(errno == EPIPE)
fprintf(stderr, "SIGPIPE");
}
ret = write(cfd, buf, strlen(buf)); // write again.
printf("write return %d\n", ret);
}
close(sfd);
}
client.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
int ret, fd;
struct sockaddr_in sa_dst;
char buffer[] = "hello, world";
char rcv_buf[128] = {0};
fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&sa_dst, 0, sizeof(struct sockaddr_in));
sa_dst.sin_family = AF_INET;
sa_dst.sin_port = htons(12345);
sa_dst.sin_addr.s_addr = inet_addr("192.168.22.91");
ret = connect(fd, (struct sockaddr *)&sa_dst, sizeof(struct sockaddr));
if(ret != -1)
{
send(fd, buffer, strlen(buffer), 0);
close(fd);
}
return 0;
}
When I run the server and the client on the same linux machine, on the server side, the first write() returns the number of bytes written while I expect a SIGPIPE signal because I closed the socket on the client side, the second write() does generate a SIGPIPE signal.
But when I ran the client on another linux machine or on a Windows machine(implement the same client with Winsock), I did't catch any SIGPIPE signal, and the second write() still returns the size of the buffer. Can someone tell me what's going on?
It can't happen on the first write, for two reasons:
The localhost doesn't know that the peer has closed the socket for reading. A FIN has been received but that could just be because the peer has shutdown for output. Only an RST will tell it that, and it doesn't get that util the next I/O at the earliest.
Buffering.
NB you're corrupting the value of errno by calling perror(), so testing it afterwards isn't valid.
Just Change this in SERVER and it will work
fcntl(cfd, F_SETFL, O_NONBLOCK);
int size = read(cfd, buf, 1024);
if(size == -1)
printf("read error\n");
sleep(2); // sleep for a while to make sure the client closed the socket
int ret;
ret = write(cfd, buf, strlen(buf));
sleep(2);
ret = write(cfd, buf, strlen(buf)); // write again.
printf("write return %d\n", ret);

Posix Sockets: Find out greatest number of file descriptors

How can I keep track of the greatest number of file descriptors each time instead of using FD_SETSIZE (which may be very large)? So far the code is (adapted from Beginning Linux Programming, 2nd Edition):
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define SERVER_PORT 9734
#define ALLOWED_CLIENTS INADDR_ANY
#define BACKLOG 5
#define DELAY 0
int main()
{
int server_sockfd, client_sockfd;
socklen_t server_len, client_len;
struct sockaddr_in server_address, client_address;
int result;
fd_set readfds, testfds;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(ALLOWED_CLIENTS);
server_address.sin_port = htons(SERVER_PORT);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, BACKLOG);
FD_ZERO(&readfds); /* Initialise readfds fd_set struct */
FD_SET(server_sockfd, &readfds); /* Initialise readfds to handle input from server_sockfd */
while(1) {
char ch;
int fd;
int nread;
testfds = readfds;
printf("Server waiting...\n");
/* Wait indefinitely for client request (input) using testfds */
result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0);
if(result < 1) {
perror("Server 5");
exit(1);
}
/* At this stage, activity of a client trying to connect has been found.
* We will find which descriptor it is on by checking each in turn. */
for(fd = 0; fd < FD_SETSIZE; fd++)
{
if(FD_ISSET(fd, &testfds)) { /* If activity occurs on the given file descriptor... */
if(fd == server_sockfd) { /* If activity occurs on server_sockfd, it must be
* a request for a new connection */
client_len = sizeof(client_address);
/* Extract connection request - set client_sockfd equal to this */
client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
/* Add client_sockfd to the descriptor set */
FD_SET(client_sockfd, &readfds);
printf(" -Added client (fd %d)\n", fd);
}
else
{
ioctl(fd, FIONREAD, &nread); /* Find out how much data needs to be read in */
if(nread == 0) { /* No data left - finished with this client */
close(fd);
FD_CLR(fd, &readfds);
printf(" -Removed client (fd %d)\n", fd);
}
else {
read(fd, &ch, 1); /* Carry out the server's actual function */
sleep(DELAY);
printf(" -Serving client (fd %d)\n", fd);
ch++;
write(fd, &ch, 1);
}
}
}
}
}
}
The book went on to say that this would make it much less efficient, which makes sense, and that a variable should be used to keep track of the largest fd number connected, but I just can't figure out how to implement this, have spent ages experimenting. Thanks in advance.
You should have a variable, e.g. int maxfd, which you adjust every time your code contains FD_SET() or FD_CLR(). The answer to this question contains an example of adjusting maxfd properly.
Unlike the comments suggest, I dont think you need to make "the" (which the?) variable static. The comments are right about poll and epoll, but knowing how to use select is useful as well.

How to know if the client has terminated in sockets

Suppose, I have a connected socket after writing this code..
if ((sd = accept(socket_d, (struct sockaddr *)&client_addr, &alen)) < 0)
{
perror("accept failed\n");
exit(1);
}
How can I know at the server side that client has exited.
My whole program actually does the following..
Accepts a connection from client
Starts a new thread that reads messages from that particular client and then broadcast this message to all the connected clients.
If you want to see the whole code... In this whole code. I am also struggling with one more problem that whenever I kill a client with Ctrl+C, my server terminates abruptly.. It would be nice if anyone could suggest what the problem is..
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>
/*CONSTANTS*/
#define DEFAULT_PORT 10000
#define LISTEN_QUEUE_LIMIT 6
#define TOTAL_CLIENTS 10
#define CHAR_BUFFER 256
/*GLOBAL VARIABLE*/
int current_client = 0;
int connected_clients[TOTAL_CLIENTS];
extern int errno;
void *client_handler(void * socket_d);
int main(int argc, char *argv[])
{
struct sockaddr_in server_addr;/* structure to hold server's address*/
int socket_d; /* listening socket descriptor */
int port; /* protocol port number */
int option_value; /* needed for setsockopt */
pthread_t tid[TOTAL_CLIENTS];
port = (argc > 1)?atoi(argv[1]):DEFAULT_PORT;
/* Socket Server address structure */
memset((char *)&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; /* set family to Internet */
server_addr.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */
server_addr.sin_port = htons((u_short)port); /* Set port */
/* Create socket */
if ( (socket_d = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
fprintf(stderr, "socket creation failed\n");
exit(1);
}
/* Make listening socket's port reusable */
if (setsockopt(socket_d, SOL_SOCKET, SO_REUSEADDR, (char *)&option_value,
sizeof(option_value)) < 0) {
fprintf(stderr, "setsockopt failure\n");
exit(1);
}
/* Bind a local address to the socket */
if (bind(socket_d, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
fprintf(stderr, "bind failed\n");
exit(1);
}
/* Specify size of request queue */
if (listen(socket_d, LISTEN_QUEUE_LIMIT) < 0) {
fprintf(stderr, "listen failed\n");
exit(1);
}
memset(connected_clients,0,sizeof(int)*TOTAL_CLIENTS);
for (;;)
{
struct sockaddr_in client_addr; /* structure to hold client's address*/
int alen = sizeof(client_addr); /* length of address */
int sd; /* connected socket descriptor */
if ((sd = accept(socket_d, (struct sockaddr *)&client_addr, &alen)) < 0)
{
perror("accept failed\n");
exit(1);
}
else printf("\n I got a connection from (%s , %d)\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
if (pthread_create(&tid[current_client],NULL,(void *)client_handler,(void *)sd) != 0)
{
perror("pthread_create error");
continue;
}
connected_clients[current_client]=sd;
current_client++; /*Incrementing Client number*/
}
return 0;
}
void *client_handler(void *connected_socket)
{
int sd;
sd = (int)connected_socket;
for ( ; ; )
{
ssize_t n;
char buffer[CHAR_BUFFER];
for ( ; ; )
{
if (n = read(sd, buffer, sizeof(char)*CHAR_BUFFER) == -1)
{
perror("Error reading from client");
pthread_exit(1);
}
int i=0;
for (i=0;i<current_client;i++)
{
if (write(connected_clients[i],buffer,sizeof(char)*CHAR_BUFFER) == -1)
perror("Error sending messages to a client while multicasting");
}
}
}
}
My client side is this (Maye be irrelevant while answering my question)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
void error(char *msg)
{
perror(msg);
exit(0);
}
void *listen_for_message(void * fd)
{
int sockfd = (int)fd;
int n;
char buffer[256];
bzero(buffer,256);
printf("YOUR MESSAGE: ");
fflush(stdout);
while (1)
{
n = read(sockfd,buffer,256);
if (n < 0)
error("ERROR reading from socket");
if (n == 0) pthread_exit(1);
printf("\nMESSAGE BROADCAST: %sYOUR MESSAGE: ",buffer);
fflush(stdout);
}
}
int main(int argc, char *argv[])
{
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
pthread_t read_message;
char buffer[256];
if (argc < 3) {
fprintf(stderr,"usage %s hostname port\n", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(portno);
if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
bzero(buffer,256);
if (pthread_create(&read_message,NULL,(void *)listen_for_message,(void *)sockfd) !=0 )
{
perror("error creating thread");
}
while (1)
{
fgets(buffer,255,stdin);
n = write(sockfd,buffer,256);
if (n < 0)
error("ERROR writing to socket");
bzero(buffer,256);
}
return 0;
}
After accepting the connection, your recv() on the socket will return 0 or -1 in special cases.
Excerpt from recv(3) man page:
Upon successful completion, recv()
shall return the length of the message
in bytes. If no messages are available
to be received and the peer has
performed an orderly shutdown, recv()
shall return 0. Otherwise, -1 shall be
returned and errno set to indicate the
error.
So, if your client exited gracefully, you will get 0 from recv() at some point. If the connection was somehow lost, you may also get -1 and checking for appropriate errno would tell you if the connection was lost of some other error occured. See more details at recv(3) man page.
Edit:
I see that you are using read(). Still, the same rules as with recv() apply.
Your server can also fail when trying to write() to your clients. If your client disconnects write() will return -1 and the errno would probably be set to EPIPE. Also, SIGPIPE signal will be send to you process and kill him if you do not block/ignore this signal. And you don't as I see and this is why your server terminates when client presses Ctrl-C. Ctrl-C terminates client, therefore closes client socket and makes your server's write() fail.
See mark4o's answer for nice detailed explanation of what else might go wrong.
If the client program exits, then the OS on the client will close its end of the socket. When you call recv() it will return 0, or -1 with errno ECONNRESET if a TCP RST has been received (e.g. because you attempted to send data after the client had closed). If the whole client machine goes down, or the network becomes disconnected, then in that case you may not receive anything if the server is not trying to send anything; if that is important to detect, you can either send some data periodically, or set the SO_KEEPALIVE socket option using setsockopt() to force it to send a packet with no data after long periods (hours) of inactivity. When no acknowledgment is received, recv() will then return -1 with errno ETIMEDOUT or another error if more specific information is available.
In addition, if you attempt to send data on a socket that has been disconnected, by default the SIGPIPE signal will terminate your program. This can be avoided by setting the SIGPIPE signal action to SIG_IGN (ignore), or by using send() with the MSG_NOSIGNAL flag on systems that support it (Linux).

Resources