/* * MultiChat Server v1.0 * * Servidor de Chat Multiusuario usando select() como multiplexor de E/S de los clientes * * + Atiende como maximo MAX_USERS usuarios, asi que es posible modificar esta constante * + Puedes comunicarte con este servidor por medio de cualquier cliente TCP/IP universal (telnet) al puerto SERVER_PORT * + Para enviar un mensaje a todos los usuarios conectados solamente escribe en el canal * + Para ver una lista con los usuarios conectados envia /list * + Para enviar un mensaje a un usuario especifico pon una / seguida del nick, da un espacio, y luego el mensaje, ejemplo: * /nitr0us Este mensaje va solo para ti nitr0us... * + Para salir envia /exit * * * $gcc multichatserver.c -o multichatserver -Wall -O2 * * nitr0us [nitrousenador en gmail punto com] * Mexico - 19/Feb/07 */ #include #include #include #include #include #include #include #include #include #define SERVER_PORT 5000 #define MAX_BUFFER 256 #define MAX_NICK 10 #define MAX_USERS 5 #define PIDE_NICK "Introduce tu nick(max. 10 caracteres): " #define NICK_OCUPADO "Nick ocupado!\n" #define NICK_RESERVADO "Nick invalido. Palabra reservada por el sistema!\n" #define NICK_NOEXISTE "El usuario no existe!\n" #define NO_ATIMISMO "No puedes enviarte mensajes a ti mismo!\n" #define LISTA_NICKS "Usuarios conectados:\n" #define SERVER_LLENO "\nServidor lleno!\n" #define ADIOS "\nAdios!\n" #define BANNER "\n\n################################################\n"\ "## Bienvenido a MultiChat Server v1.0 ##\n"\ "################################################\n\n"\ "\t/list\t\tLista los usuarios conectados\n"\ "\t/exit\t\tSalir del sistema\n"\ #\t/nick msg\tEnvia un mensaje a un usuario especifico\n\n" typedef struct{ int fd; char nick[MAX_NICK + 1]; } Usuario; Usuario Users[MAX_USERS]; int sendall(int, char *, int *); int nickOcupado(char *); int ndxlibre(); int Userndx(int); int Busca_nick(char *); void Envia_a_todos_los_clientes(int, fd_set *, int, int, char *); int main(){ struct sockaddr_in server, client; socklen_t foo = sizeof(struct sockaddr); fd_set master, readfds; time_t fecha; int sfd, cfd, fd = 0, one = 1, nrecv, fdmax, k, z, nusers = 0, tmpndx; char nick[MAX_NICK + 1], buffer[MAX_BUFFER + MAX_NICK + 64], usermsgbuffer[MAX_BUFFER + 1], *ptr, *ptr2, *timeptr; if((sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1){ perror("socket"); exit(EXIT_FAILURE); } /*** Evitar el 'Address already in use' al arrancar el servicio nuevamente ***/ if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) == -1){ perror("setsockopt"); exit(EXIT_FAILURE); } bzero(&server, sizeof(server)); bzero(&client, sizeof(client)); server.sin_family = AF_INET; server.sin_port = htons(SERVER_PORT); server.sin_addr.s_addr = INADDR_ANY; if(bind(sfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1){ perror("bind"); exit(EXIT_FAILURE); } if(listen(sfd, 5) == -1){ perror("listen"); exit(EXIT_FAILURE); } bzero(&Users, sizeof(Users)); time(&fecha); printf("Servidor iniciado: %s", asctime(localtime(&fecha))); FD_ZERO(&master); FD_ZERO(&readfds); FD_SET(sfd, &master); fdmax = sfd; for(;;){ readfds = master; if(select(fdmax + 1, &readfds, NULL, NULL, NULL) == -1){ perror("select"); exit(EXIT_FAILURE); } for(k = 3; k <= fdmax; k++) if(FD_ISSET(k, &readfds)){ bzero(usermsgbuffer, sizeof(usermsgbuffer)); bzero(buffer, sizeof(buffer)); bzero(nick, sizeof(nick)); if(k == sfd){ /* Nuevas conexiones */ if((cfd = accept(sfd, (struct sockaddr *)&client, &foo)) == -1) perror("accept"); else{ send(cfd, BANNER, strlen(BANNER), 0); nusers++; if(nusers > MAX_USERS){ printf("Servidor lleno!\n"); send(cfd, SERVER_LLENO, strlen(SERVER_LLENO), 0); send(cfd, ADIOS, strlen(ADIOS), 0); close(cfd); nusers--; continue; } send(cfd, PIDE_NICK, strlen(PIDE_NICK), 0); if((nrecv = recv(cfd, nick, MAX_NICK, 0)) == -1){ perror("recv"); close(cfd); nusers--; continue; } ptr = nick; while(*ptr != '\r' && *ptr != '\n') ptr++; *ptr = '\0'; if(!strcmp(nick, "list") || !strcmp(nick, "exit")){ send(cfd, NICK_RESERVADO, strlen(NICK_RESERVADO), 0); send(cfd, ADIOS, strlen(ADIOS), 0); close(cfd); nusers--; continue; } if(nickOcupado(nick)){ printf("Nick ocupado: %s\n", nick); send(cfd, NICK_OCUPADO, strlen(NICK_OCUPADO), 0); send(cfd, ADIOS, strlen(ADIOS), 0); close(cfd); nusers--; continue; } else{ if((tmpndx = ndxlibre()) == -1){ fprintf(stderr, "ndxlibre: No hay indices libres!\n"); exit(-1); } Users[tmpndx].fd = cfd; strncpy(Users[tmpndx].nick, nick, MAX_NICK); } snprintf(buffer, MAX_BUFFER, "Hola %s, hay %d usuarios conectados\n\n", nick, nusers); send(cfd, buffer, strlen(buffer), 0); bzero(buffer, sizeof(buffer)); snprintf(buffer, MAX_BUFFER, "Usuario nuevo: %s\n", nick); FD_SET(cfd, &master); if(cfd > fdmax) fdmax = cfd; Envia_a_todos_los_clientes(fdmax, &master, sfd, cfd, buffer); printf("Nuevo Usuario: %s, conectado desde \"%s\" en el socket %d\n", nick, (char *) inet_ntoa(client.sin_addr), cfd); } } else /* Gestionar datos de los clientes */ if((nrecv = recv(k, usermsgbuffer, MAX_BUFFER, 0)) <= 0){ if(nrecv == 0) printf("socket %d hang up!\n", k); else perror("recv"); nusers--; close(k); FD_CLR(k, &master); } else{ time(&fecha); timeptr = asctime(localtime(&fecha)); ptr = timeptr; while(*ptr != '\r' && *ptr != '\n') ptr++; *ptr = '\0'; if((tmpndx = Userndx(k)) == -1){ fprintf(stderr, "Userndx: Indice de usuario con el socket %d no encontrado!\n", k); exit(-1); } ptr = usermsgbuffer; if(ptr[0] == '/'){ ptr++; if(ptr[0] == 'e' && ptr[1] == 'x' && ptr[2] == 'i' && ptr[3] == 't'){ send(k, ADIOS, strlen(ADIOS), 0); close(k); FD_CLR(k, &master); snprintf(buffer, MAX_BUFFER, "Usuario %s salio del sistema\n", Users[tmpndx].nick); Envia_a_todos_los_clientes(fdmax, &master, sfd, k, buffer); printf("Usuario %s salio del sistema\n", Users[tmpndx].nick); Users[tmpndx].fd = 0; bzero(&Users[tmpndx].nick, sizeof(Users[tmpndx].nick)); nusers--; continue; } if(ptr[0] == 'l' && ptr[1] == 'i' && ptr[2] == 's' && ptr[3] =='t'){ send(k, LISTA_NICKS, strlen(LISTA_NICKS), 0); for(z = 0; z < MAX_USERS; z++) if(Users[z].fd != 0 && Users[z].fd != k){ snprintf(buffer, sizeof(buffer) - 1, "%s\n", Users[z].nick); send(k, buffer, strlen(buffer), 0); bzero(buffer, sizeof(buffer)); } continue; } static char tmpbuff[sizeof(buffer)]; strncpy(tmpbuff, ptr, sizeof(tmpbuff) - 1); if((fd = Busca_nick(strtok(ptr, " ")))){ if(fd == k) send(fd, NO_ATIMISMO, strlen(NO_ATIMISMO), 0); else{ ptr2 = (strchr(tmpbuff, (int) ' ') + 1); snprintf(buffer, sizeof(buffer) - 1, "[%s] /%s: %s", timeptr, Users[tmpndx].nick, ptr2); send(fd, buffer, strlen(buffer), 0); } } else{ snprintf(buffer, sizeof(buffer) - 1, "%s: ", ptr); send(k, buffer, strlen(buffer), 0); send(k, NICK_NOEXISTE, strlen(NICK_NOEXISTE), 0); } bzero(tmpbuff, sizeof(tmpbuff)); continue; } snprintf(buffer, sizeof(buffer) - 1, "[%s] %s: %s", timeptr, Users[tmpndx].nick, usermsgbuffer); Envia_a_todos_los_clientes(fdmax, &master, sfd, k, buffer); } } } return 0; } int sendall(int s, char *buf, int *len) { int total = 0, bytesleft = *len, n = 0; while(total < *len){ if((n = send(s, buf + total, bytesleft, 0)) == -1) break; total += n; bytesleft -= n; } *len = total; return n == -1 ? -1 : 0; } int nickOcupado(char *nick) { int k; for(k = 0; k < MAX_USERS; k++) if(!Users[k].fd) continue; else if(strcmp(Users[k].nick, nick) == 0) return 1; return 0; } int ndxlibre() { int k; for(k = 0; k < MAX_USERS; k++) if(Users[k].fd == 0) return k; return -1; } int Userndx(int fd) { int k; for(k = 0; k < MAX_USERS; k++) if(Users[k].fd == fd) return k; return -1; } int Busca_nick(char *nick) { int k; if(nick == NULL) return 0; for(k = 0; k < MAX_USERS; k++) if(!strcmp(Users[k].nick, nick)) return Users[k].fd; return 0; } void Envia_a_todos_los_clientes(int fdmax, fd_set *master, int listener, int client, char *buffer) { int l, buflen; buflen = strlen(buffer); for(l = 3; l <= fdmax; l++) if(FD_ISSET(l, master)) if(l != listener && l != client) if(sendall(l, buffer, &buflen) == -1) fprintf(stderr, "sendall: Solo se enviaron %d bytes de %d [%s]\n", buflen, strlen(buffer), buffer); }