/** * server.cpp * Benjamin Bertka - 23306103 * bbertka@mss.icics.ubc.ca * CICS505 OS9 Chat Server * * This is a chat server using BSD sockets, PThreads, and Shared Memory * It is designed to listen on a local port for chat communucations between clients * * The maximum number of users is 6, and message length 512 * Note that MAXUSERS is set to 6+1=7, but max user is still 6. * Clients will have to wait for an available slot before accessing. * Requests to join will be stalled until a free slot becomes available. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_USERS 7 //The maximum number of users plus 1 #define MAX 512 //the largest message length using namespace std; int* shm_ptr; //The shared memory pointer each thread shares /** * This is data for each thread. * Includes port numbers and file descriptor */ struct thread_data{ int new_fd; uint16_t sin_port; char* sin_addr; int port; }; /* This creates a block of shared memory to hold each file descriptor */ void makeSharedMemory(); /* Spawns a new thread*/ void spawnThread(int numUsers, int new_fd, int port, uint16_t sin_port, char* sin_addr); /* Thread work for chat clients */ void* chatFunc(void * param); /* Notifys the group that a client has joined */ void notifyJoin(int port, uint16_t sin_port); /* When a client leaves the shared file decsrtiptor list is decremented */ void decrementFDList(int new_fd); /* Notifies the group that a client has left */ void notifyClose(int port, char* sin_addr, uint16_t sin_port); /* A handy sleep function */ void ptsleep(unsigned int mseconds){ if(mseconds > 0){ clock_t goal = mseconds + clock(); while (goal > clock()); } } /** * Opens a socket for listening to clients, and reports new activity to the * current group of clients. Creates a location of shared memory to hold * file descriptors. Accepts connections and creates new threads for listening * to client activity. Maintains a semaphore mutex on the shared list of FD's. * @param argc * @param argv - port number * @return */ int main(int argc, char *argv[]){ sem_t *mutex; //semaphore mutex lock for the shared memory int port = 0; //Stores the port used for this service int new_fd = 0; //Stores the latest file descriptor int numUsers = 0; //The running total number of simultaneous users int sockfd = 0; //The base socket file descriptor struct sockaddr_in my_addr; //Server socket address struct sockaddr_in client_addr; //Client socket address socklen_t sin_size; //Socket size /* Check for the proper arguments, must be a vaild port number greater than 1024 */ if(argv[1] == NULL){ cout << "Usage: ./chatserver [port number]" << endl; exit(1); }else if( atoi(argv[1]) <= 1024 ){ cout << "Error, must use a port above 1024." << endl; exit(1); } port = atoi(argv[1]); /* Create a socket for listening */ sockfd = socket(PF_INET, SOCK_STREAM, 0); if( sockfd < 0 ){ cout << "Error can't create socket." << endl; exit(1); } int optval = 1; if( setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR, &optval, sizeof(optval)) == -1 ){ cout << "Can't do setsockopt function, terminate." << endl; exit(1); } my_addr.sin_family = AF_INET; my_addr.sin_port = htons(port); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero); /* Bind and begin listening for traffick */ if( bind(sockfd, (struct sockaddr *) &my_addr, sizeof(my_addr)) == -1 ){ cout << "Error, can't bind."<new_fd = new_fd; myThreadData->sin_port = sin_port; myThreadData->sin_addr = sin_addr; myThreadData->port = port; pthread_create(&newThread, NULL, chatFunc, (void *)myThreadData); } /** * Thread work for a client listener thread * Listns to the group, and sends essages ot the group * When finished, access shared memory to decrement the number of * client file descriptors * @param param The thread structure for holding thread specific data * @return */ void *chatFunc(void *param) { sem_t *mutex; //mutex for accessing shared memory //thread data struct thread_data *myThreadData = (struct thread_data*)param; int serverPort = (int)myThreadData->port; int new_fd = (int)myThreadData->new_fd; uint16_t sin_port = (uint16_t)myThreadData->sin_port; char* sin_addr = (char*)myThreadData->sin_addr; char buf[MAX]; char message[MAX]; while (1){ bzero(buf, MAX); bzero(message, MAX); //Receive and send if(recv(new_fd, buf, sizeof(buf), 0) > 0){ sprintf(message, "[%d] %s", (int)sin_port, buf ); for(int i = 0; i < shm_ptr[MAX_USERS]; ++i){ if (send(shm_ptr[i], message, sizeof(buf), 0) == -1){ cout << strerror(errno) <<"Error sending to: " << shm_ptr[i] << endl; } } }else{ break; } } /* when done access the shared memory to update the number of valid FDs*/ if((mutex = sem_open("chat_semaphore", O_CREAT, 0644, 1)) == SEM_FAILED){ cout << "Error openeing semaphore" << endl; } sem_wait(mutex); decrementFDList(new_fd); close(new_fd); sem_post(mutex); free(myThreadData); /* Notify the group of your departure */ notifyClose(serverPort, sin_addr, sin_port); pthread_exit(NULL); } /** * Notifys the group of join for a specific client * * @param port - the server port * @param sin_port - the client address */ void notifyJoin(int port, uint16_t sin_port){ char notify[MAX]; //notify everyone that a new member has joined bzero(notify, MAX); sprintf(notify, "[%d] has joined the group!\n", (int)sin_port ); for(int i = 0; i < shm_ptr[MAX_USERS]; ++i){ if (send(shm_ptr[i], notify, sizeof(notify), 0) == -1){ cout << strerror(errno) <<"Error sending to: " << shm_ptr[i] << endl; } } //notify everyone the current number of members bzero(notify, MAX); sprintf(notify, "[%d] currently [%d] users are connected.\n", port, shm_ptr[MAX_USERS] ); for(int i = 0; i < shm_ptr[MAX_USERS]; ++i){ if (send(shm_ptr[i], notify, sizeof(notify), 0) == -1){ cout << strerror(errno) <<"Error sending to: " << shm_ptr[i] << endl; } } } /** * This function removes the specified file descriptor from the list of file * descriptors. A linear search is performed matching each file descriptor. When * the file descriptor is found, it is removes and the locations of every other * element are shifted over one slot. * @param new_fd - The file descriptor for removal * @pre The file descriptor must be valid and exist * @post The list of file descriptors allhave their locaitons shiften one slot */ void decrementFDList(int new_fd){ int i = 0; for(i = 0; i < MAX_USERS; ++i){ if(new_fd == shm_ptr[i]){ for(int j = i; j < MAX_USERS - 1; ++j){ shm_ptr[j] = shm_ptr[j+1]; i = j; } shm_ptr[i] = -1; --shm_ptr[MAX_USERS]; break; } } } /** * Notifies the group that a client has left * @param port * @param sin_addr * @param sin_port */ void notifyClose(int port, char* sin_addr, uint16_t sin_port){ char notify[MAX]; //notify everyone that a new member has joined bzero(notify, MAX); sprintf(notify, "[%d] has left the group!\n", (int)sin_port ); for(int i = 0; i < shm_ptr[MAX_USERS]; ++i){ if (send(shm_ptr[i], notify, sizeof(notify), 0) == -1){ cout << strerror(errno) <<"Error sending to: " << shm_ptr[i] << endl; } } //Server notify console socket is closed ans client left cout << "Closing socket [Socket[addr=/" << sin_addr; cout <<",port=" << sin_port << ",localport ="<< port << "]]"<