udp-transfer

a file transfer program written in C using UDP


udp-transfer / sender.c

sender.c (4.7K) raw | download
1#define _POSIX_C_SOURCE 200809L
2#include <sys/socket.h>
3#include <netdb.h>
4#include <arpa/inet.h>
5#include <poll.h>
6#include <fcntl.h>
7#include <unistd.h>
8#include <errno.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14
15#define PAYLOAD 1024
16#define HEADER 7
17#define DATA 1
18#define ACK 2
19#define END 3
20#define ATTEMPTS 5
21#define TIMEOUT 1000
22
23static int64_t
24now_ms(void)
25{
26 struct timespec ts;
27 if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) return -1;
28 return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
29}
30
31static int
32wait_packet(int fd, int64_t deadline)
33{
34 struct pollfd pfd = {fd, POLLIN, 0};
35 for (;;) {
36 int64_t now = now_ms();
37 int result;
38 if (now < 0) return -1;
39 if (now >= deadline) return 0;
40 result = poll(&pfd, 1, (int)(deadline - now));
41 if (result < 0 && errno == EINTR) continue;
42 if (result <= 0) return result;
43 if (pfd.revents & POLLIN) return 1;
44 errno = EIO;
45 return -1;
46 }
47}
48
49static int
50send_packet(int fd, const unsigned char *packet, size_t size, uint32_t seq)
51{
52 int attempt;
53 for (attempt = 0; attempt < ATTEMPTS; ++attempt) {
54 int64_t deadline;
55 ssize_t n;
56 do {
57 n = send(fd, packet, size, 0);
58 } while (n < 0 && errno == EINTR);
59 if (n < 0) return -1;
60 if ((size_t)n != size) { errno = EIO; return -1; }
61 deadline = now_ms();
62 if (deadline < 0) return -1;
63 deadline += TIMEOUT;
64 for (;;) {
65 unsigned char reply[HEADER + 1];
66 uint32_t received;
67 int result = wait_packet(fd, deadline);
68 if (result < 0) return -1;
69 if (result == 0) break;
70 do {
71 n = recv(fd, reply, sizeof(reply), 0);
72 } while (n < 0 && errno == EINTR);
73 if (n < 0) return -1;
74 if (n != HEADER || reply[0] != ACK || reply[5] || reply[6])
75 continue;
76 memcpy(&received, reply + 1, sizeof(received));
77 if (ntohl(received) == seq) return 0;
78 }
79 }
80 errno = ETIMEDOUT;
81 return -1;
82}
83
84static int
85connect_receiver(const char *host, const char *port)
86{
87 struct addrinfo hints = {0}, *addresses, *addr;
88 int fd = -1, result, saved = EHOSTUNREACH;
89 hints.ai_family = AF_INET;
90 hints.ai_socktype = SOCK_DGRAM;
91 hints.ai_flags = AI_NUMERICSERV;
92 result = getaddrinfo(host, port, &hints, &addresses);
93 if (result != 0) {
94 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result));
95 return -1;
96 }
97 for (addr = addresses; addr != NULL; addr = addr->ai_next) {
98 do {
99 fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
100 if (fd < 0) { saved = errno; break; }
101 if (connect(fd, addr->ai_addr, addr->ai_addrlen) == 0) break;
102 saved = errno;
103 close(fd);
104 fd = -1;
105 } while (saved == EINTR);
106 if (fd >= 0) break;
107 }
108 freeaddrinfo(addresses);
109 if (fd < 0) { errno = saved; perror("connect"); }
110 return fd;
111}
112
113int
114main(int argc, char **argv)
115{
116 unsigned char packet[HEADER + PAYLOAD];
117 uint32_t seq = 0;
118 char *end;
119 long port;
120 int input, fd, status = 1;
121
122 if (argc != 4) {
123 fprintf(stderr, "usage: %s host port input\n", argv[0]);
124 return 1;
125 }
126 errno = 0;
127 port = strtol(argv[2], &end, 10);
128 if (errno || !*argv[2] || *end || port < 1 || port > 65535) {
129 fprintf(stderr, "invalid port: %s\n", argv[2]);
130 return 1;
131 }
132 input = open(argv[3], O_RDONLY);
133 if (input < 0) { perror(argv[3]); return 1; }
134 fd = connect_receiver(argv[1], argv[2]);
135 if (fd < 0) { close(input); return 1; }
136 for (;;) {
137 size_t used = 0;
138 uint32_t wire_seq = htonl(seq);
139 uint16_t wire_len;
140 while (used < PAYLOAD) {
141 ssize_t n = read(input, packet + HEADER + used, PAYLOAD - used);
142 if (n < 0 && errno == EINTR) continue;
143 if (n < 0) { perror("read"); goto done; }
144 if (n == 0) break;
145 used += (size_t)n;
146 }
147 if (used > 0 && seq == UINT32_MAX) {
148 fprintf(stderr, "file exceeds sequence number limit\n");
149 break;
150 }
151 packet[0] = used == 0 ? END : DATA;
152 wire_len = htons((uint16_t)used);
153 memcpy(packet + 1, &wire_seq, sizeof(wire_seq));
154 memcpy(packet + 5, &wire_len, sizeof(wire_len));
155 if (send_packet(fd, packet, HEADER + used, seq) < 0) {
156 perror("transfer");
157 break;
158 }
159 if (used == 0) { status = 0; break; }
160 ++seq;
161 }
162done:
163 close(fd);
164 if (close(input) < 0) { perror("close input"); status = 1; }
165 return status;
166}