tcp-server

a TCP client-server application written in C using sockets


tcp-server / client.c

client.c (3.9K) raw | download
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
11static int
12write_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. */
25static int
26read_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
45static int
46connect_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
81int
82main(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}