diff options
author | Väinö Mäkelä <vaino.o.makela@gmail.com> | 2020-09-12 09:00:59 +0300 |
---|---|---|
committer | Väinö Mäkelä <vaino.o.makela@gmail.com> | 2020-09-12 13:15:38 +0300 |
commit | dd3410e61e303bd13e0e65b102f1fb86b09d0104 (patch) | |
tree | 4a171c79b8643503ebeed6cd7685d28896ed18e1 /kirc.c | |
parent | refactoring (diff) | |
download | kirc-dd3410e61e303bd13e0e65b102f1fb86b09d0104.tar.gz kirc-dd3410e61e303bd13e0e65b102f1fb86b09d0104.tar.bz2 kirc-dd3410e61e303bd13e0e65b102f1fb86b09d0104.tar.xz kirc-dd3410e61e303bd13e0e65b102f1fb86b09d0104.tar.zst kirc-dd3410e61e303bd13e0e65b102f1fb86b09d0104.zip |
Use poll instead of multiple processes
The old approach used 100% CPU on two processes. Poll can be used to
wait stdin and the socket at the same time. This commit also reduces
system call usage by not calling read for every byte.
Diffstat (limited to '')
-rw-r--r-- | kirc.c | 193 |
1 files changed, 115 insertions, 78 deletions
@@ -8,9 +8,9 @@ #include <stdlib.h> #include <string.h> #include <fcntl.h> -#include <sys/select.h> -#include <sys/wait.h> #include <termios.h> +#include <poll.h> +#include <errno.h> #define MSG_MAX 512 /* guaranteed max message length */ #define CHA_MAX 200 /* gauranteed max channel length */ @@ -42,20 +42,6 @@ log_append(char *str, char *path) { fclose(out); } -static int -kbhit(void) { - - int byteswaiting; - struct timespec ts = {0}; - fd_set fds; - - FD_ZERO(&fds); - FD_SET(0, &fds); - byteswaiting = pselect(1, &fds, NULL, NULL, &ts, NULL); - - return byteswaiting > 0; -} - static void raw(char *fmt, ...) { @@ -154,73 +140,84 @@ raw_parser(char *usrin) { } else printw("%*s\x1b[33;1m%-.*s\x1b[0m %s", s, "", g, nickname, message); } -static void -parent_process(int fd[], pid_t pid) { - char usrin[MSG_MAX], v1[MSG_MAX - CHA_MAX], v2[CHA_MAX], c1; - struct termios tp, save; +static char message_buffer[MSG_MAX + 1]; +/* The first character after the end */ +static size_t message_end = 0; - tcgetattr(STDIN_FILENO, &tp); - save = tp; - tp.c_cc[VERASE] = 127; - - if (tcsetattr(STDIN_FILENO, TCSANOW, &tp) < 0) exit(2); - - while (waitpid(pid, NULL, WNOHANG) == 0) { - if (!kbhit()) dprintf(fd[1], "/\n"); - else if (fgets(usrin, MSG_MAX, stdin) != NULL && - (sscanf(usrin, "/%[m] %s %[^\n]\n", &c1, v2, v1) > 2 || - sscanf(usrin, "/%[xMQqnjp] %[^\n]\n", &c1, v1) > 0)) { - switch (c1) { - case 'x': dprintf(fd[1], "%s\n", v1); break; - case 'q': dprintf(fd[1], "quit\n"); break; - case 'Q': dprintf(fd[1], "quit %s\n", v1); break; - case 'j': dprintf(fd[1], "join %s\n", v1); break; - case 'p': dprintf(fd[1], "part %s\n", v1); break; - case 'n': dprintf(fd[1], "names #%s\n", chan); break; - case 'M': dprintf(fd[1], "privmsg nickserv :%s\n", v1); break; - case 'm': dprintf(fd[1], "privmsg %s :%s\n", v2, v1); break; +static int +handle_server_message(void) { + for (;;) { + ssize_t sl = read(conn, &message_buffer[message_end], MSG_MAX - message_end); + if (sl == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* Let's wait for new input */ + return 0; + } else { + perror("read"); + return -2; } - } else dprintf(fd[1], "privmsg #%s :%s", chan, usrin); - } - - if (tcsetattr(STDIN_FILENO, TCSANOW, &save) < 0) exit(2); - - perror("Connection closed"); -} - -static void -child_process(int fd[]) { - int sl, i, o = 0; - char u[MSG_MAX], s, b[MSG_MAX]; - - irc_init(); + } + if (sl == 0) { + fputs("Connection closed\n", stderr); + return -1; + } - raw("NICK %s\r\n", nick); - raw("USER %s - - :%s\r\n", (user ? user : nick), (real ? real : nick)); + size_t old_message_end = message_end; + message_end += sl; - if (pass) raw("PASS %s\r\n", pass); - if (inic) raw("%s\r\n", inic); + /* Iterate over the added part to find \r\n */ + for (size_t i = old_message_end; i < message_end; ++i) { + if (i != 0 && message_buffer[i - 1] == '\r' && message_buffer[i] == '\n') { + /* Cut the buffer here for parsing */ + char saved_char = message_buffer[i + 1]; + message_buffer[i + 1] = '\0'; + raw_parser(message_buffer); + message_buffer[i + 1] = saved_char; - while ((sl = read(conn, &s, 1))) { - if (sl > 0) b[o] = s; + /* Move the part after the parsed line to the beginning */ + memmove(&message_buffer, &message_buffer[i + 1], message_end - i - 1); - if ((o > 0 && b[o - 1] == '\r' && b[o] == '\n') || o == MSG_MAX) { - b[o + 1] = '\0'; - raw_parser(b); - o = 0; - } else if (sl > 0) ++o; + /* There might still be other lines to be read */ + message_end = message_end - i - 1; + i = 0; + } + } + if (message_end == MSG_MAX) { + /* The buffer is full and doesn't contain \r\n */ + message_end = 0; + } + } +} - if (read(fd[0], u, MSG_MAX) > 0) { - for (i = 0; u[i] != '\n'; ++i) continue; - if (u[0] != '/') raw("%-*.*s\r\n", i, i, u); +static void +handle_user_input(void) { + char usrin[MSG_MAX], v1[MSG_MAX - CHA_MAX], v2[CHA_MAX], c1; + if (fgets(usrin, MSG_MAX, stdin) != NULL && + (sscanf(usrin, "/%[m] %s %[^\n]\n", &c1, v2, v1) > 2 || + sscanf(usrin, "/%[xMQqnjp] %[^\n]\n", &c1, v1) > 0)) { + switch (c1) { + case 'x': raw("%s\r\n", v1); break; + case 'q': raw("quit\r\n"); break; + case 'Q': raw("quit %s\r\n", v1); break; + case 'j': raw("join %s\r\n", v1); break; + case 'p': raw("part %s\r\n", v1); break; + case 'n': raw("names #%s\r\n", chan); break; + case 'M': raw("privmsg nickserv :%s\r\n", v1); break; + case 'm': raw("privmsg %s :%s\r\n", v2, v1); break; } + } else { + size_t msg_len = strlen(usrin); + /* Remove the trailing newline to add a carriage return */ + if (usrin[msg_len - 1] == '\n') + usrin[msg_len - 1] = '\0'; + raw("privmsg #%s :%s\r\n", chan, usrin); } } int main(int argc, char **argv) { - int fd[2], cval; + int cval; while ((cval = getopt(argc, argv, "s:p:o:n:k:c:u:r:x:w:W:hvV")) != -1) { switch (cval) { @@ -247,16 +244,56 @@ main(int argc, char **argv) { return 1; } - if (pipe(fd) < 0) { - perror("Pipe() failed"); - return 1; - } + irc_init(); + + raw("NICK %s\r\n", nick); + raw("USER %s - - :%s\r\n", (user ? user : nick), (real ? real : nick)); + + if (pass) raw("PASS %s\r\n", pass); + if (inic) raw("%s\r\n", inic); + + struct termios tp, save; + + tcgetattr(STDIN_FILENO, &tp); + save = tp; + tp.c_cc[VERASE] = 127; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &tp) < 0) return 2; + + struct pollfd fds[2]; + fds[0].fd = STDIN_FILENO; + fds[1].fd = conn; + fds[0].events = POLLIN; + fds[1].events = POLLIN; + + int return_code = 0; - pid_t pid = fork(); + for (;;) { + int poll_res = poll(fds, 2, -1); - switch (pid) { - case -1 : perror("Fork() failed"); return 1; - case 0 : child_process(fd); return 0; - default : parent_process(fd, pid); return 0; + if (poll_res != -1) { + if (fds[0].revents & POLLIN) { + handle_user_input(); + } + + if (fds[1].revents & POLLIN) { + int rc = handle_server_message(); + /* Has the server closed the connection? */ + if (rc != 0) { + if (rc == -2) return_code = EXIT_FAILURE; + goto end; + }; + } + } else { + if (errno == EAGAIN) continue; + perror("poll"); + return_code = EXIT_FAILURE; + goto end; + } } + +end: + if (tcsetattr(STDIN_FILENO, TCSANOW, &save) < 0) return 2; + + return return_code; } |