Skip to content

Commit dd3410e

Browse files
committed
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.
1 parent 3175b73 commit dd3410e

File tree

1 file changed

+115
-78
lines changed

1 file changed

+115
-78
lines changed

kirc.c

Lines changed: 115 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
#include <stdlib.h>
99
#include <string.h>
1010
#include <fcntl.h>
11-
#include <sys/select.h>
12-
#include <sys/wait.h>
1311
#include <termios.h>
12+
#include <poll.h>
13+
#include <errno.h>
1414

1515
#define MSG_MAX 512 /* guaranteed max message length */
1616
#define CHA_MAX 200 /* gauranteed max channel length */
@@ -42,20 +42,6 @@ log_append(char *str, char *path) {
4242
fclose(out);
4343
}
4444

45-
static int
46-
kbhit(void) {
47-
48-
int byteswaiting;
49-
struct timespec ts = {0};
50-
fd_set fds;
51-
52-
FD_ZERO(&fds);
53-
FD_SET(0, &fds);
54-
byteswaiting = pselect(1, &fds, NULL, NULL, &ts, NULL);
55-
56-
return byteswaiting > 0;
57-
}
58-
5945
static void
6046
raw(char *fmt, ...) {
6147

@@ -154,73 +140,84 @@ raw_parser(char *usrin) {
154140
} else printw("%*s\x1b[33;1m%-.*s\x1b[0m %s", s, "", g, nickname, message);
155141
}
156142

157-
static void
158-
parent_process(int fd[], pid_t pid) {
159-
char usrin[MSG_MAX], v1[MSG_MAX - CHA_MAX], v2[CHA_MAX], c1;
160-
struct termios tp, save;
143+
static char message_buffer[MSG_MAX + 1];
144+
/* The first character after the end */
145+
static size_t message_end = 0;
161146

162-
tcgetattr(STDIN_FILENO, &tp);
163-
save = tp;
164-
tp.c_cc[VERASE] = 127;
165-
166-
if (tcsetattr(STDIN_FILENO, TCSANOW, &tp) < 0) exit(2);
167-
168-
while (waitpid(pid, NULL, WNOHANG) == 0) {
169-
if (!kbhit()) dprintf(fd[1], "/\n");
170-
else if (fgets(usrin, MSG_MAX, stdin) != NULL &&
171-
(sscanf(usrin, "/%[m] %s %[^\n]\n", &c1, v2, v1) > 2 ||
172-
sscanf(usrin, "/%[xMQqnjp] %[^\n]\n", &c1, v1) > 0)) {
173-
switch (c1) {
174-
case 'x': dprintf(fd[1], "%s\n", v1); break;
175-
case 'q': dprintf(fd[1], "quit\n"); break;
176-
case 'Q': dprintf(fd[1], "quit %s\n", v1); break;
177-
case 'j': dprintf(fd[1], "join %s\n", v1); break;
178-
case 'p': dprintf(fd[1], "part %s\n", v1); break;
179-
case 'n': dprintf(fd[1], "names #%s\n", chan); break;
180-
case 'M': dprintf(fd[1], "privmsg nickserv :%s\n", v1); break;
181-
case 'm': dprintf(fd[1], "privmsg %s :%s\n", v2, v1); break;
147+
static int
148+
handle_server_message(void) {
149+
for (;;) {
150+
ssize_t sl = read(conn, &message_buffer[message_end], MSG_MAX - message_end);
151+
if (sl == -1) {
152+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
153+
/* Let's wait for new input */
154+
return 0;
155+
} else {
156+
perror("read");
157+
return -2;
182158
}
183-
} else dprintf(fd[1], "privmsg #%s :%s", chan, usrin);
184-
}
185-
186-
if (tcsetattr(STDIN_FILENO, TCSANOW, &save) < 0) exit(2);
187-
188-
perror("Connection closed");
189-
}
190-
191-
static void
192-
child_process(int fd[]) {
193-
int sl, i, o = 0;
194-
char u[MSG_MAX], s, b[MSG_MAX];
195-
196-
irc_init();
159+
}
160+
if (sl == 0) {
161+
fputs("Connection closed\n", stderr);
162+
return -1;
163+
}
197164

198-
raw("NICK %s\r\n", nick);
199-
raw("USER %s - - :%s\r\n", (user ? user : nick), (real ? real : nick));
165+
size_t old_message_end = message_end;
166+
message_end += sl;
200167

201-
if (pass) raw("PASS %s\r\n", pass);
202-
if (inic) raw("%s\r\n", inic);
168+
/* Iterate over the added part to find \r\n */
169+
for (size_t i = old_message_end; i < message_end; ++i) {
170+
if (i != 0 && message_buffer[i - 1] == '\r' && message_buffer[i] == '\n') {
171+
/* Cut the buffer here for parsing */
172+
char saved_char = message_buffer[i + 1];
173+
message_buffer[i + 1] = '\0';
174+
raw_parser(message_buffer);
175+
message_buffer[i + 1] = saved_char;
203176

204-
while ((sl = read(conn, &s, 1))) {
205-
if (sl > 0) b[o] = s;
177+
/* Move the part after the parsed line to the beginning */
178+
memmove(&message_buffer, &message_buffer[i + 1], message_end - i - 1);
206179

207-
if ((o > 0 && b[o - 1] == '\r' && b[o] == '\n') || o == MSG_MAX) {
208-
b[o + 1] = '\0';
209-
raw_parser(b);
210-
o = 0;
211-
} else if (sl > 0) ++o;
180+
/* There might still be other lines to be read */
181+
message_end = message_end - i - 1;
182+
i = 0;
183+
}
184+
}
185+
if (message_end == MSG_MAX) {
186+
/* The buffer is full and doesn't contain \r\n */
187+
message_end = 0;
188+
}
189+
}
190+
}
212191

213-
if (read(fd[0], u, MSG_MAX) > 0) {
214-
for (i = 0; u[i] != '\n'; ++i) continue;
215-
if (u[0] != '/') raw("%-*.*s\r\n", i, i, u);
192+
static void
193+
handle_user_input(void) {
194+
char usrin[MSG_MAX], v1[MSG_MAX - CHA_MAX], v2[CHA_MAX], c1;
195+
if (fgets(usrin, MSG_MAX, stdin) != NULL &&
196+
(sscanf(usrin, "/%[m] %s %[^\n]\n", &c1, v2, v1) > 2 ||
197+
sscanf(usrin, "/%[xMQqnjp] %[^\n]\n", &c1, v1) > 0)) {
198+
switch (c1) {
199+
case 'x': raw("%s\r\n", v1); break;
200+
case 'q': raw("quit\r\n"); break;
201+
case 'Q': raw("quit %s\r\n", v1); break;
202+
case 'j': raw("join %s\r\n", v1); break;
203+
case 'p': raw("part %s\r\n", v1); break;
204+
case 'n': raw("names #%s\r\n", chan); break;
205+
case 'M': raw("privmsg nickserv :%s\r\n", v1); break;
206+
case 'm': raw("privmsg %s :%s\r\n", v2, v1); break;
216207
}
208+
} else {
209+
size_t msg_len = strlen(usrin);
210+
/* Remove the trailing newline to add a carriage return */
211+
if (usrin[msg_len - 1] == '\n')
212+
usrin[msg_len - 1] = '\0';
213+
raw("privmsg #%s :%s\r\n", chan, usrin);
217214
}
218215
}
219216

220217
int
221218
main(int argc, char **argv) {
222219

223-
int fd[2], cval;
220+
int cval;
224221

225222
while ((cval = getopt(argc, argv, "s:p:o:n:k:c:u:r:x:w:W:hvV")) != -1) {
226223
switch (cval) {
@@ -247,16 +244,56 @@ main(int argc, char **argv) {
247244
return 1;
248245
}
249246

250-
if (pipe(fd) < 0) {
251-
perror("Pipe() failed");
252-
return 1;
253-
}
247+
irc_init();
248+
249+
raw("NICK %s\r\n", nick);
250+
raw("USER %s - - :%s\r\n", (user ? user : nick), (real ? real : nick));
251+
252+
if (pass) raw("PASS %s\r\n", pass);
253+
if (inic) raw("%s\r\n", inic);
254+
255+
struct termios tp, save;
256+
257+
tcgetattr(STDIN_FILENO, &tp);
258+
save = tp;
259+
tp.c_cc[VERASE] = 127;
260+
261+
if (tcsetattr(STDIN_FILENO, TCSANOW, &tp) < 0) return 2;
262+
263+
struct pollfd fds[2];
264+
fds[0].fd = STDIN_FILENO;
265+
fds[1].fd = conn;
266+
fds[0].events = POLLIN;
267+
fds[1].events = POLLIN;
268+
269+
int return_code = 0;
254270

255-
pid_t pid = fork();
271+
for (;;) {
272+
int poll_res = poll(fds, 2, -1);
256273

257-
switch (pid) {
258-
case -1 : perror("Fork() failed"); return 1;
259-
case 0 : child_process(fd); return 0;
260-
default : parent_process(fd, pid); return 0;
274+
if (poll_res != -1) {
275+
if (fds[0].revents & POLLIN) {
276+
handle_user_input();
277+
}
278+
279+
if (fds[1].revents & POLLIN) {
280+
int rc = handle_server_message();
281+
/* Has the server closed the connection? */
282+
if (rc != 0) {
283+
if (rc == -2) return_code = EXIT_FAILURE;
284+
goto end;
285+
};
286+
}
287+
} else {
288+
if (errno == EAGAIN) continue;
289+
perror("poll");
290+
return_code = EXIT_FAILURE;
291+
goto end;
292+
}
261293
}
294+
295+
end:
296+
if (tcsetattr(STDIN_FILENO, TCSANOW, &save) < 0) return 2;
297+
298+
return return_code;
262299
}

0 commit comments

Comments
 (0)