aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVäinö Mäkelä <vaino.o.makela@gmail.com>2020-09-12 09:00:59 +0300
committerVäinö Mäkelä <vaino.o.makela@gmail.com>2020-09-12 13:15:38 +0300
commitdd3410e61e303bd13e0e65b102f1fb86b09d0104 (patch)
tree4a171c79b8643503ebeed6cd7685d28896ed18e1
parentrefactoring (diff)
downloadkirc-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.
-rw-r--r--kirc.c193
1 files changed, 115 insertions, 78 deletions
diff --git a/kirc.c b/kirc.c
index 53635b6..cbb8025 100644
--- a/kirc.c
+++ b/kirc.c
@@ -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;
}