#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int port = 2345; int backlog = 20; #define max_line 1024 #define max_client_fd 1024 #define max_who_len 65536 int ear; fd_set fds_read, fds_write, fds_err; int i, fd_max; FILE *io[max_client_fd]; enum { CLOSED, HEADER, CHATTING } state[max_client_fd]; char *nick[max_client_fd]; void error(const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); fputc('\n', stderr); va_end(ap); exit(1); } void failed(const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, ": "); perror(""); exit(1); } void nonblock(int fd, int nb) { int flags = fcntl(fd, F_GETFL); flags &= ~O_NONBLOCK; if (nb) flags |= O_NONBLOCK; fcntl(fd, F_SETFL, flags); } void *get_in_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 server(char *host, int port) { struct addrinfo hints, *servinfo, *p; int ear, rv, yes = 1; char port_s[16]; snprintf(port_s, sizeof(port_s), "%d", port); memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // TODO listen on both ipv4 and ipv6 hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; if ((rv = getaddrinfo(host, port_s, &hints, &servinfo)) != 0) error("getaddrinfo: %s\n", gai_strerror(rv)); for (p = servinfo; p != NULL; p = p->ai_next) { ear = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if (ear == -1) { perror("server: socket"); continue; } if (setsockopt (ear, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) failed("setsockopt"); if (bind(ear, p->ai_addr, p->ai_addrlen) == -1) { close(ear); perror("server: bind"); continue; } nonblock(ear, 1); break; } if (p == NULL) error("server: failed to bind"); freeaddrinfo(servinfo); if (listen(ear, backlog) == -1) failed("listen"); return ear; } int accept_new_client(void) { struct sockaddr_storage client_addr; socklen_t sin_size; char s[INET6_ADDRSTRLEN]; int client; sin_size = sizeof client_addr; client = accept(ear, (struct sockaddr *) &client_addr, &sin_size); if (client == -1) { if (errno == EAGAIN || errno == EINTR) return 1; perror("accept"); return 1; } nonblock(client, 1); if (client >= max_client_fd) { fprintf(stderr, "too many clients\n"); close(client); return 1; } inet_ntop(client_addr.ss_family, get_in_addr((struct sockaddr *) &client_addr), s, sizeof s); fprintf(stderr, "server: got connection %d from %s\n", client, s); FD_SET(client, &fds_read); FD_SET(client, &fds_err); io[client] = fdopen(client, "r+b"); if (!io[client]) failed("fdopen"); if (client >= fd_max) fd_max = client + 1; state[client] = HEADER; return 0; } void send_msg(int client, char *from, char *msg) { fprintf(io[client], "%s: %s\n", from, msg); fflush(io[client]); } void broadcast(char *from, char *msg) { int client; for (client = 0; client < fd_max; ++client) { if (state[client] == CHATTING) send_msg(client, from, msg); } } void close_client(int client) { fclose(io[client]); io[client] = NULL; FD_CLR(client, &fds_read); FD_CLR(client, &fds_err); state[client] = CLOSED; broadcast("exit", nick[client]); nick[client] = NULL; } void header(int client, char *line) { char *key, *value; char *delim = strstr(line, ": "); if (!delim) { fprintf(stderr, "bad header from client %d: %s\n", client, line); return; } *delim = '\0'; key = line; value = delim + 2; fprintf(stderr, "header from client %d: %s: %s\n", client, key, value); if (!strcmp(key, "nick")) { nick[client] = strdup(value); } } char *who(void) { static char who[max_who_len]; int client, len = 0; char *p = who; *p = '\0'; for (client = 0; client < fd_max; ++client) { if (state[client] != CHATTING) continue; int nick_len = strlen(nick[client]); if (len + nick_len + 1 >= max_who_len - 5) { strcat(p, "..."); break; } strcat(p, nick[client]); p += len; strcat(p, " "); ++p; } return who; } void start_chatting(int client) { if (!nick[client]) { close_client(client); return; } fprintf(stderr, "client %d is chatting\n", client); broadcast("enter", nick[client]); state[client] = CHATTING; send_msg(client, "who", who()); } int received_msg(int client) { char line[max_line]; int len; fprintf(stderr, "can read: %d\n", client); errno = 0; if (!fgets(line, sizeof(line), io[client])) { if (errno == EAGAIN) return 0; fprintf(stderr, "client %d closed connection\n", client); close_client(client); return 0; } len = strcspn(line, "\n\r"); line[len] = '\0'; if (state[client] == HEADER) { if (len) header(client, line); else start_chatting(client); } else broadcast(nick[client], line); return 1; } void server_step(void) { fd_set fds_read_r, fds_write_r, fds_err_r; int count, client; fds_read_r = fds_read; fds_write_r = fds_write; fds_err_r = fds_err; fprintf(stderr, "selecting...\n"); count = select(fd_max, &fds_read_r, &fds_write_r, &fds_err_r, NULL); if (count < 0 && errno != EINTR) failed("select"); if (FD_ISSET(ear, &fds_read_r)) accept_new_client(); for (client = ear + 1; client < fd_max; ++client) { if (FD_ISSET(client, &fds_read_r)) while (received_msg(client)) { } } } int main(int argc, char **argv) { char *host_address; if (argc >= 2 && argv[1][0] == '-') { printf("Usage: chatd [listen-address]\n"); exit(EXIT_FAILURE); } if (argc < 2) host_address = "0.0.0.0"; else host_address = argv[1]; for (i = 0; i < max_client_fd; ++i) { io[i] = NULL; state[i] = CLOSED; } FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_err); ear = server(host_address, port); FD_SET(ear, &fds_read); FD_SET(ear, &fds_err); fd_max = ear + 1; while (1) server_step(); return 0; }