a TCP client-server application written in C using sockets
tcp-server / client.c
| 1 | #define _POSIX_C_SOURCE 200809L |
| 2 | #include <sys/socket.h> |
| 3 | #include <netdb.h> |
| 4 | #include <signal.h> |
| 5 | #include <unistd.h> |
| 6 | #include <errno.h> |
| 7 | #include <stdio.h> |
| 8 | #include <stdlib.h> |
| 9 | #include <string.h> |
| 10 | |
| 11 | static int |
| 12 | write_all(int fd, const char *buf, size_t len) |
| 13 | { |
| 14 | while (len > 0) { |
| 15 | ssize_t n = write(fd, buf, len); |
| 16 | if (n < 0 && errno == EINTR) continue; |
| 17 | if (n <= 0) return -1; |
| 18 | buf += n; |
| 19 | len -= (size_t)n; |
| 20 | } |
| 21 | return 0; |
| 22 | } |
| 23 | |
| 24 | /* Result 2 allows a final stdin line without a newline; not a wire message. */ |
| 25 | static int |
| 26 | read_line(int fd, char *line, size_t size) |
| 27 | { |
| 28 | size_t len = 0; |
| 29 | for (;;) { |
| 30 | char c; |
| 31 | ssize_t n = read(fd, &c, 1); |
| 32 | if (n < 0 && errno == EINTR) continue; |
| 33 | if (n < 0) return -1; |
| 34 | if (n == 0 || c == '\n') { |
| 35 | line[len] = '\0'; |
| 36 | if (n == 0) return len == 0 ? 0 : 2; |
| 37 | return 1; |
| 38 | } |
| 39 | if (c == '\0') return -3; |
| 40 | if (len + 1 >= size) return -2; |
| 41 | line[len++] = c; |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | static int |
| 46 | connect_server(const char *host, const char *port) |
| 47 | { |
| 48 | struct addrinfo hints = {0}, *addresses, *addr; |
| 49 | int fd = -1, result, saved = ECONNREFUSED; |
| 50 | hints.ai_family = AF_INET; |
| 51 | hints.ai_socktype = SOCK_STREAM; |
| 52 | hints.ai_flags = AI_NUMERICSERV; |
| 53 | result = getaddrinfo(host, port, &hints, &addresses); |
| 54 | if (result != 0) { |
| 55 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result)); |
| 56 | return -1; |
| 57 | } |
| 58 | for (addr = addresses; addr != NULL; addr = addr->ai_next) { |
| 59 | do { |
| 60 | fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); |
| 61 | if (fd < 0) { |
| 62 | saved = errno; |
| 63 | break; |
| 64 | } |
| 65 | result = connect(fd, addr->ai_addr, addr->ai_addrlen); |
| 66 | if (result == 0) break; |
| 67 | saved = errno; |
| 68 | close(fd); |
| 69 | fd = -1; |
| 70 | } while (saved == EINTR); |
| 71 | if (fd >= 0) break; |
| 72 | } |
| 73 | freeaddrinfo(addresses); |
| 74 | if (fd < 0) { |
| 75 | errno = saved; |
| 76 | perror("connect"); |
| 77 | } |
| 78 | return fd; |
| 79 | } |
| 80 | |
| 81 | int |
| 82 | main(int argc, char **argv) |
| 83 | { |
| 84 | const char *host = argc > 1 ? argv[1] : "127.0.0.1"; |
| 85 | const char *port = argc > 2 ? argv[2] : "9000"; |
| 86 | char line[4097], reply[4097], *end; |
| 87 | struct sigaction action = {0}; |
| 88 | long number; |
| 89 | int fd, status = 0; |
| 90 | |
| 91 | if (argc > 3) { |
| 92 | fprintf(stderr, "usage: %s [host] [port]\n", argv[0]); |
| 93 | return 1; |
| 94 | } |
| 95 | errno = 0; |
| 96 | number = strtol(port, &end, 10); |
| 97 | if (errno || !*port || *end || number < 1 || number > 65535) { |
| 98 | fprintf(stderr, "invalid port: %s\n", port); |
| 99 | return 1; |
| 100 | } |
| 101 | action.sa_handler = SIG_IGN; |
| 102 | sigemptyset(&action.sa_mask); |
| 103 | if (sigaction(SIGPIPE, &action, NULL) < 0) { |
| 104 | perror("sigaction"); |
| 105 | return 1; |
| 106 | } |
| 107 | fd = connect_server(host, port); |
| 108 | if (fd < 0) return 1; |
| 109 | for (;;) { |
| 110 | int result = read_line(STDIN_FILENO, line, sizeof(line)); |
| 111 | if (result == 0) break; |
| 112 | if (result < 0) { |
| 113 | if (result == -1) perror("stdin"); |
| 114 | else fprintf(stderr, "%s\n", result == -2 ? |
| 115 | "input line too long" : "input contains a NUL byte"); |
| 116 | status = 1; |
| 117 | break; |
| 118 | } |
| 119 | if (write_all(fd, line, strlen(line)) < 0 || |
| 120 | write_all(fd, "\n", 1) < 0) { |
| 121 | perror("write"); |
| 122 | status = 1; |
| 123 | break; |
| 124 | } |
| 125 | result = read_line(fd, reply, sizeof(reply)); |
| 126 | if (result == 0) break; |
| 127 | if (result != 1) { |
| 128 | if (result == -1) perror("read"); |
| 129 | else fprintf(stderr, "invalid or incomplete server response\n"); |
| 130 | status = 1; |
| 131 | break; |
| 132 | } |
| 133 | if (write_all(STDOUT_FILENO, reply, strlen(reply)) < 0 || |
| 134 | write_all(STDOUT_FILENO, "\n", 1) < 0) { |
| 135 | perror("stdout"); |
| 136 | status = 1; |
| 137 | break; |
| 138 | } |
| 139 | if (strcmp(line, "QUIT") == 0) break; |
| 140 | } |
| 141 | close(fd); |
| 142 | return status; |
| 143 | } |