I work on a Wi-Fi multi-user chat program. I'd appreciate some help with the following.
- Due to the dhcp nature of routers, it's not clear what address the server machine would get each day. Client goes through the list of all possible hosts, starting with 192.168.1.1. When it encounters a non-server device it waits for a random amount of time, up to a minute. How can I make it stop waiting after a second? I tried stopping it with alarm() before I read alarm manual so it was a waste of time.
- Server function
printUsers()is the only function whose output is sometimes delivered, sometimes not. I'd love to have someone explain why. - I'd like to have some feedback on general code quality. By that I mean things like code readability and maintainability, program design, style, error handling, etc.
Server Code
#include <arpa/inet.h>
#include <ctype.h>
#include <ifaddrs.h>
#include <netdb.h> //adds symbolic network constants
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define BYE 0
#define ERROR -1
#define HOST_SIZE 4
#define MAX_CONN 11
#define MSG_LEN 255
#define NAME_LEN 30
#define SYS_MSG_LEN 18
int debug; //command-line argument
int mySock; //sustains value from assignment till the end
typedef struct pollfd pollfd;
void addName2msg(char name[], char msg[], char msgOut[]);
void addUser(char name[], int i, char msg[], pollfd *fds, int fdsInd);
void findMyIP(char myIP[]);
void handleArgs(int argc, char *argv[]);
void handleLeaver(int i, char names[][NAME_LEN], pollfd *fds, int*fdI);
void handleNewClient(pollfd *fds, int *fdsInd);
void printUsers(char names[][NAME_LEN], int i, int fd);
void sendAll(int i, char msgOut[], pollfd *fds, int fdsInd);
void sendErr(char *msg);
void sendMsg(int fd, char msg[], int len);
void startHost(char myIP[]);
void workLoop()
{
static pollfd fds[MAX_CONN];
int status, fdsInd = 0;
static char names[MAX_CONN+1][NAME_LEN];
char msg[MSG_LEN], msgOut[MSG_LEN+NAME_LEN];
fds[fdsInd].fd = mySock;
fds[fdsInd++].events = POLLIN;
while(1) {
if (debug)
puts("Waiting for an event to happen...");
if (poll(fds, fdsInd, -1) == -1)
sendErr("poll() failed");
if (debug)
puts("An event happened");
for (int i = 0; i < fdsInd; i++) {
if ((fds[i].fd == mySock) && (fds[i].revents & POLLIN)) {
handleNewClient(fds, &fdsInd);
} else if (fds[i].revents & POLLIN) { //handle message
status = recv(fds[i].fd, msg, MSG_LEN, 0);
if (debug)
printf("Recieved msg from %s, socket %d\n",
names[i], fds[i].fd);
if ((status == BYE) || (status == ERROR)) {
printf("Status is %d\n", status == BYE? BYE: ERROR);
handleLeaver(i, names, fds, &fdsInd);
continue;
}
if (*names[i] == '\0') { //no name? Save msg as name
addUser(names[i], i, msg, fds, fdsInd);
printUsers(names, i, fds[i].fd);
continue;
}
addName2msg(names[i], msg, msgOut);
sendAll(i, msgOut, fds, fdsInd);
}
}
}
}
int main(int argc, char *argv[])
{
char myIP[INET_ADDRSTRLEN];
handleArgs(argc, argv);
findMyIP(myIP);
startHost(myIP);
workLoop();
return 0;
}
//
void addName2msg(char name[], char msg[], char msgOut[]) {
int len;
strncpy(msgOut, name, NAME_LEN );
len = strlen(msgOut);
msgOut[len] = ':';
msgOut[len+1] = ' ';
strcat(msgOut, msg);
}
//
void addUser(char name[], int i, char msg[], pollfd *fds, int fdsInd)
{
int j;
char msgWelc[SYS_MSG_LEN + NAME_LEN] = "Welcome to chat, ";
char msgJoin[SYS_MSG_LEN] = " joined the chat\n";
for (j = 0; j < NAME_LEN-3; j++) {
if (msg[j] == '\n' || isspace(msg[j]))
break;
name[j] = msg[j];
}
name[j] = '\n';
name[j+1] = '\0';
strcat(msgWelc, name);
sendMsg(fds[i].fd, msgWelc, SYS_MSG_LEN+NAME_LEN);
name[j] = '\0';
strncpy(msgWelc, name, NAME_LEN);
strcat(msgWelc, msgJoin);
sendAll(i, msgWelc, fds, fdsInd);
}
//
void findMyIP(char myIP[])
{
struct ifaddrs *addrs;
struct sockaddr_in *pAddr;
getifaddrs(&addrs);
for (; addrs != NULL; addrs = addrs->ifa_next) {
pAddr = (struct sockaddr_in *) addrs->ifa_addr;
if (strstr(inet_ntoa(pAddr->sin_addr), "192.168.1") != NULL)
strncpy(myIP, inet_ntoa(pAddr->sin_addr), INET_ADDRSTRLEN);
}
freeifaddrs(addrs);
if (debug)
printf("Local IP is %s\n", myIP);
}
//
void handleArgs(int argc, char *argv[])
{
if (argc > 2) {
puts("error: one arg max");
exit(1);
}
if ((argc == 2) && (strcmp("-d", argv[argc-1]) != 0)) {
puts("error: Call with -d for debugging");
exit(1);
} else if (argc == 2)
debug = 1;
}
//
void handleLeaver(
int i, char names[][NAME_LEN], pollfd *fds, int *fdsInd)
{
int lastElem = *fdsInd - 1;
char trash[MSG_LEN];
char msgLeft[SYS_MSG_LEN] = " left the chat\n";
//notify all users
strncpy(trash, names[i], strlen(names[i])+1);
strcat(trash, msgLeft);
sendAll(i, trash, fds, *fdsInd);
if (debug) {
printf("Had: fds0:%d fds1:%d fds2:%d; fdsInd: %d\n",
fds[0].fd, fds[1].fd, fds[2].fd, *fdsInd);
printf("\tNames1: %s, Names2: %s\n", names[1], names[2]);
}
recv(fds[i].fd, trash, MSG_LEN, 0); //receive garbage
if (lastElem == i) {
fds[i].fd = 0;
fds[i].events = 0;
(*fdsInd)--;
names[i][0] = 0;
} else {
fds[i].fd = fds[lastElem].fd;
fds[i].events = fds[lastElem].events;
strncpy(names[i], names[lastElem], NAME_LEN);
fds[lastElem].fd = 0;
fds[lastElem].events = 0;
(*fdsInd)--;
names[lastElem][0] = 0;
}
if (debug) {
printf("Removed user\n");
printf("Have: fds0:%d fds1:%d fds2:%d; fdsInd: %d\n",
fds[0].fd, fds[1].fd, fds[2].fd, *fdsInd);
printf("\tNames1: %s, Names2: %s\n", names[1], names[2]);
}
}
//
void handleNewClient(pollfd *fds, int *fdsInd)
{
struct sockaddr clientInfo;
socklen_t addrSz;
int newFD;
char nameReq[SYS_MSG_LEN] = "Enter your name: ";
addrSz = sizeof(clientInfo);
newFD = accept(mySock, &clientInfo, &addrSz);
if (*fdsInd > MAX_CONN) { //kill extra users
close(newFD);
return;
}
fds[*fdsInd].fd = newFD;
fds[(*fdsInd)++].events = POLLIN;
if (debug)
puts("Accepted a client");
sendMsg(newFD, nameReq, SYS_MSG_LEN);
}
//
void printUsers(char names[][NAME_LEN], int i, int fd)
{
int len;
char msgStart[SYS_MSG_LEN] = "Online Users:\n";
sendMsg(fd, msgStart, SYS_MSG_LEN);
if (debug)
printf("Online users printed for %s, %d\n", names[i], fd);
for (int j = 0; j < MAX_CONN; j++) {
if ((names[j][0] != 0) && (strcmp(names[j], names[i]) != 0)) {
len = strlen(names[j]);
names[j][len] = '\n';
names[j][len+1] = '\0';
sendMsg(fd, names[j], NAME_LEN);
names[j][len] = '\0';
}
}
}
//
void sendAll(int i, char msgOut[], pollfd *fds, int fdsInd)
{
pollfd *author = &fds[i];
for (int j = 0; j < fdsInd; j++) {
if (fds[j].fd == author->fd)
continue;
if (fds[j].fd == mySock)
continue;
sendMsg(fds[j].fd, msgOut, MSG_LEN);
}
}
//
void sendErr(char *msg)
{
perror(msg);
exit(1);
}
//
void sendMsg(int fd, char msg[], int len)
{
int sent = 0;
if ((len == MSG_LEN) && (msg[len-1]!= '\0'))
msg[len-1] = '\0';
if ((len == MSG_LEN) && (msg[len-2]!= '\n'))
msg[len-2] = '\n';
while (sent < len) {
sent = send(fd, msg, len, 0);
if (sent == -1)
sendErr("Sending message failed");
else if (sent == len)
return;
}
}
//
void startHost(char myIP[])
{
struct addrinfo hints, *servinfo;
int yes = 1;
char port[] = "39071";
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // any IPv*
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(myIP, port, &hints, &servinfo) != 0)
sendErr("Getaddinfo() failed");
if ((mySock = socket(servinfo->ai_family,
servinfo->ai_socktype, servinfo->ai_protocol)) == -1)
sendErr("Socket() failed");
if (-1 == setsockopt(
mySock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)))
sendErr("setsockopt() failed");
if (bind(mySock, servinfo->ai_addr, servinfo->ai_addrlen) == -1)
sendErr("Bind() failed");
if (listen(mySock, MAX_CONN) == -1)
sendErr("listen() failed");
if (debug) {
printf("Host socket is %d\n", mySock);
printf("Listening to port %s...\n", port);
}
}
Client Code
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netdb.h> //adds symbolic network constants
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define FDS_NUM 2
#define HOST_SIZE 4
#define MSG_LEN 255
#define MAX_CONN 11
enum { ERROR = -1, OFF = 0, SERV_DOWN = 0 , FAIL = 0, SUCCESS = 1 };
int debug; //command-line argument
int mySock; //received in prepareNet() and kept till the end
//int catchSignal(int sig, void (*handler)(int));
void chat();
//void doNoth(int sig) { sig = sig; return; }
void exit_closedConn();
int findHost(struct addrinfo *servinfo[]);
void handleArgs(int argc, char *argv[]);
void prepareNet(struct addrinfo *servinfo[]);
void sendErr(char *msg);
int main(int argc, char *argv[])
{
struct addrinfo *servinfo[MAX_CONN];
handleArgs(argc, argv);
prepareNet(servinfo);
//if (catchSignal(SIGALRM, doNoth) == -1)
// sendErr("Sigaction failed");
if (!findHost(servinfo))
sendErr("No hosts found");
chat();
return 0;
}
//
int catchSignal(int sig, void (*handler)(int))
{
struct sigaction new;
new.sa_handler = handler;
sigemptyset(&new.sa_mask);
new.sa_flags = 0;
return sigaction(sig, &new, NULL);
}
//
void chat()
{
struct pollfd pds[FDS_NUM];
int status;
char msgIn[MSG_LEN];
char usrMsg[MSG_LEN];
pds[0].fd = 0; //stdin
pds[1].fd = mySock;
pds[0].events = pds[1].events = POLLIN;
while (1) {
if (poll(pds, FDS_NUM, -1) == -1)
sendErr("Poll failed");
if (pds[0].revents & POLLIN) {
fgets(usrMsg, MSG_LEN, stdin);
if (send(pds[1].fd, usrMsg, MSG_LEN, 0) == -1)
sendErr("Sent failed");
} else if (pds[1].revents & POLLIN) {
if ((status = recv(pds[1].fd, msgIn, MSG_LEN, 0)) == ERROR)
sendErr("recv failed");
else if (status == SERV_DOWN)
exit_closedConn();
printf("%s", msgIn);
fflush(stdout);
}
}
}
//
void handleArgs(int argc, char *argv[])
{
if (argc > 2)
sendErr("1 argument max");
if ((argc == 2) && (strcmp("-d", argv[argc-1]) != 0))
sendErr("input -d for debugging");
else if (argc == 2)
debug = 1;
}
//
void exit_closedConn()
{
puts("Server closed connection. Exiting...");
exit(1);
}
//
int findHost(struct addrinfo *servinfo[])
{
puts("Searching for a server...");
for (int i = 1; i < MAX_CONN; i++) {
if (debug)
printf("Trying for 192.168.1.%d...\n", i);
//alarm(1);
if (connect(mySock, servinfo[i]->ai_addr,
servinfo[i]->ai_addrlen) != -1) {
alarm(OFF);
printf("Connected to 192.168.1.%d\n",i);
return SUCCESS;
}
}
return FAIL;
}
//
void prepareNet(struct addrinfo *servinfo[])
{
struct addrinfo hints[MAX_CONN];
char port[] = "39071";
for (int i = 0; i < MAX_CONN; i++) {
char host[HOST_SIZE];
char ip[INET_ADDRSTRLEN] = "192.168.1.";
memset(&hints[i], 0, sizeof(hints[i]));
hints[i].ai_family = AF_UNSPEC; // any IPv*
hints[i].ai_socktype = SOCK_STREAM;
sprintf(host, "%d", i);
host[3] = '\0';
strncat(ip, host, HOST_SIZE-1);
if (getaddrinfo(ip, port, &hints[i], &servinfo[i]) != 0)
sendErr("Getaddinfo() failed");
}
if ((mySock = socket(servinfo[0]->ai_family,
servinfo[0]->ai_socktype, servinfo[0]->ai_protocol)) == -1)
sendErr("Socket() failed");
if (debug)
printf("Trying port %s using socket %d\n", port, mySock);
}
//
void sendErr(char *msg)
{
perror(msg);
exit(1);
}
UPDATE:
- Following Cix's and bruno's advices I was able to solve the first problem. Here are the details:
- program creates a socket and tries to connect
- after a second alarm is set
- alarm()'s signal handler is a dummy function
- if no connection is made program closes the socket
The question is how good is this approach?
int findHost(struct addrinfo *servinfo[])
{
puts("Searching for a server...");
for (int i = 1; i < MAX_CONN; i++) {
if ((mySock = socket(servinfo[0]->ai_family,
servinfo[0]->ai_socktype, servinfo[0]->ai_protocol)) == -1)
sendErr("Socket() failed");
if (debug)
printf("Trying for 192.168.1.%d...\n", i);
alarm(1);
if (connect(mySock, servinfo[i]->ai_addr,
servinfo[i]->ai_addrlen) != -1) {
alarm(OFF);
printf("Connected to 192.168.1.%d\n",i);
//remove non-blocking
return SUCCESS;
}
//sleep(1);
close(mySock);
}
return FAIL;
}
Here a first version when the connections are tried one by one, using a non blocking socket and a
selectwith a timeout of 1 second. I also changed few other things in the client to receive in argument :-dto be verboseFull program of the client including not modified parts
However if the max host number is high even limiting to 1 second per host that can make a too long time.
Here a second proposal reducing the needed time doing the connect attempts for several addresses rather than only one, still using non blocking sockets and a
selectwith a timeout of 1 sec.The client is also modified to receive in argument :
-dto be verbose, to be given the number of attempts in parallel must be given tooFull program of the client including not modified parts :
The other part of your program are unchanged, note in
chatyou send a fixed size array so potentially including non initialized bytes.