Skip to content

Instantly share code, notes, and snippets.

@iAmGroute
Last active March 20, 2020 19:11
Show Gist options
  • Save iAmGroute/e5b271affa8b217a12e50654c50deb3c to your computer and use it in GitHub Desktop.
Save iAmGroute/e5b271affa8b217a12e50654c50deb3c to your computer and use it in GitHub Desktop.
A simple C server program that reads from stdin and sends to all connected clients
(doesn't seem to work for h264)
HTTP/1.1 206
access-control-allow-origin: *
connection: keep-alive
content-type: video/h264
content-range: bytes 0-
#include <signal.h> // signal
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <string.h> // memcpy
#include <unistd.h> // close, lseek
#include <arpa/inet.h> // htons
#include <sys/select.h> // select
#include <sys/socket.h> // socket
#define TCP_PORT 35001
#define TCP_BACKLOG 5
#define MAX_CLIENTS 1024
#define BUF_SIZE 262144
#define assert_sys(x) if (x < 0) { perror(#x); exit(1); }
typedef unsigned char u8;
u8 read_buf[16384] = {0};
u8 buf[BUF_SIZE] = {0};
uint buf_r[MAX_CLIENTS] = {0};
uint buf_w = 0;
int nfds = 0;
fd_set readfds, writefds;
fd_set readables, writables;
void kick(int fd)
{
printf("----- Kicking %d\n", fd);
close(fd);
FD_CLR(fd, &readfds);
FD_CLR(fd, &writefds);
if (fd + 1 >= nfds) nfds = fd;
}
int main(void)
{
// Ignore the broken pipe signal
signal(SIGPIPE, SIG_IGN);
// Init the fd sets and their mirrors
FD_ZERO(&readfds); FD_ZERO(&writefds);
FD_ZERO(&readables); FD_ZERO(&writables);
// Init the local (listening) address
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = htons(TCP_PORT),
.sin_addr.s_addr = htonl(INADDR_ANY)
};
// Server socket
int server_fd = socket(PF_INET, SOCK_STREAM, 0); assert_sys(server_fd);
int reuseaddr = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); assert_sys(reuseaddr);
int bind_ok = bind(server_fd, (struct sockaddr *)&sa, sizeof(sa)); assert_sys(bind_ok);
int listen_ok = listen(server_fd, TCP_BACKLOG); assert_sys(listen_ok);
// Will read from stdin and the server socket
FD_SET( 0, &readfds);
FD_SET(server_fd, &readfds);
nfds = server_fd + 1;
while (1) {
// Copy fd sets, because select will change them
memcpy(&readables, &readfds, sizeof(fd_set));
memcpy(&writables, &writefds, sizeof(fd_set));
int select_ok = select(nfds, &readables, &writables, NULL, NULL); assert_sys(select_ok);
// Read from / write to client sockets
for (int i = server_fd + 1; i < nfds; i++) {
if (FD_ISSET(i, &readables)) {
ssize_t n = read(i, read_buf, sizeof(read_buf) - 1);
if (n <= 0) { kick(i); continue; }
read_buf[n] = 0;
printf("----- Read from %d\n%s\n", i, read_buf);
}
if (FD_ISSET(i, &writables)) {
// Send from main buffer
uint pos_r = buf_r[i];
uint tosend = (buf_w >= pos_r ? buf_w : BUF_SIZE) - pos_r;
if (tosend == 0) { FD_CLR(i, &writefds); continue; }
ssize_t n = write(i, &buf[pos_r], tosend);
if (n < 0) { kick(i); continue; }
buf_r[i] = (pos_r + n) % BUF_SIZE;
}
}
// Accept from server socket
if (FD_ISSET(server_fd, &readables)) {
socklen_t len = sizeof(struct sockaddr_in);
int client_fd = accept(server_fd, (struct sockaddr *)&sa, &len); assert_sys(client_fd);
printf("----- New client %d\n", client_fd);
if (client_fd > MAX_CLIENTS) { kick(client_fd); continue; }
FD_SET(client_fd, &readfds);
if (client_fd >= nfds) nfds = client_fd + 1;
buf_r[client_fd] = buf_w;
}
// Read from stdin
if (FD_ISSET(0, &readables)) {
ssize_t n = read(0, &buf[buf_w], BUF_SIZE - buf_w); assert_sys(n);
if (n <= 0) return 0;
buf_w = (buf_w + n) % BUF_SIZE;
// All active sockets will be in `readfds` so just copy the entire set
// instead of setting them in `writefds` one-by-one
memcpy(&writefds, &readfds, sizeof(fd_set));
}
}
}
#include <fcntl.h> // open
#include <signal.h> // signal
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <string.h> // memcpy
#include <unistd.h> // close, lseek
#include <arpa/inet.h> // htons
#include <sys/select.h> // select
#include <sys/socket.h> // socket
#define TCP_PORT 35001
#define TCP_BACKLOG 5
#define MAX_CLIENTS 1024
#define BUF_SIZE 262144
#define assert_sys(x) if (x < 0) { perror(#x); exit(1); }
typedef unsigned char u8;
enum {
WAIT_GREET = 0,
DO_GREET = 1,
HAVE_GREETED = 2
};
u8 *greet_buf = NULL;
size_t greet_size = -1;
u8 read_buf[16384] = {0};
u8 buf[BUF_SIZE] = {0};
uint buf_r[MAX_CLIENTS] = {0};
u8 greet_status[MAX_CLIENTS] = {0};
uint buf_w = 0;
int nfds = 0;
fd_set readfds, writefds;
fd_set readables, writables;
void kick(int fd)
{
printf("----- Kicking %d\n", fd);
greet_status[fd] = WAIT_GREET;
close(fd);
FD_CLR(fd, &readfds);
FD_CLR(fd, &writefds);
if (fd + 1 >= nfds) nfds = fd;
}
int main(void)
{
// Read the "greet" file and save to `greet_buf`
// `greet_size` will be the actual size of the data read
int greet_fd = open("greet", O_RDONLY); assert_sys(greet_fd);
off_t greet_seek = lseek(greet_fd, 0, SEEK_END); assert_sys(greet_seek);
greet_size = greet_seek + 1;
greet_seek = lseek(greet_fd, 0, SEEK_SET); assert_sys(greet_seek);
greet_buf = calloc(1, greet_size);
if (!greet_buf) { printf("Can't allocate %ld bytes of memory\n", greet_size); return 1; }
ssize_t remaining = greet_size;
greet_size = 0;
while (remaining) {
ssize_t read_n = read(greet_fd, &greet_buf[greet_size], remaining); assert_sys(read_n);
if (read_n <= 0) break;
greet_size += read_n;
remaining -= read_n;
}
// Ignore the broken pipe signal
signal(SIGPIPE, SIG_IGN);
// Init the fd sets and their mirrors
FD_ZERO(&readfds); FD_ZERO(&writefds);
FD_ZERO(&readables); FD_ZERO(&writables);
// Init the local (listening) address
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = htons(TCP_PORT),
.sin_addr.s_addr = htonl(INADDR_ANY)
};
// Server socket
int server_fd = socket(PF_INET, SOCK_STREAM, 0); assert_sys(server_fd);
int reuseaddr = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); assert_sys(reuseaddr);
int bind_ok = bind(server_fd, (struct sockaddr *)&sa, sizeof(sa)); assert_sys(bind_ok);
int listen_ok = listen(server_fd, TCP_BACKLOG); assert_sys(listen_ok);
// Will read from stdin and the server socket
FD_SET( 0, &readfds);
FD_SET(server_fd, &readfds);
nfds = server_fd + 1;
while (1) {
// Copy fd sets, because select will change them
memcpy(&readables, &readfds, sizeof(fd_set));
memcpy(&writables, &writefds, sizeof(fd_set));
int select_ok = select(nfds, &readables, &writables, NULL, NULL); assert_sys(select_ok);
// Read from / write to client sockets
for (int i = server_fd + 1; i < nfds; i++) {
if (FD_ISSET(i, &readables)) {
ssize_t n = read(i, read_buf, sizeof(read_buf) - 1);
if (n <= 0) { kick(i); continue; }
read_buf[n] = 0;
printf("----- Read from %d\n%s\n", i, read_buf);
if (greet_status[i] == WAIT_GREET) {
if (n >= 3 &&
read_buf[n-3] == '\n' &&
read_buf[n-2] == '\r' &&
read_buf[n-1] == '\n'
) {
printf("----- Greet OK\n");
greet_status[i] = DO_GREET;
FD_SET(i, &writefds);
}
}
}
if (FD_ISSET(i, &writables)) {
if (greet_status[i] == HAVE_GREETED) {
// Send from main buffer
uint pos_r = buf_r[i];
uint tosend = (buf_w >= pos_r ? buf_w : BUF_SIZE) - pos_r;
if (tosend == 0) { FD_CLR(i, &writefds); continue; }
ssize_t n = write(i, &buf[pos_r], tosend);
if (n < 0) { kick(i); continue; }
buf_r[i] = (pos_r + n) % BUF_SIZE;
}
else {
// Send from greet buffer
uint pos_r = buf_r[i];
uint tosend = greet_size - pos_r;
ssize_t n = write(i, &greet_buf[pos_r], tosend);
if (n < 0) { kick(i); continue; }
if (n < tosend) buf_r[i] = pos_r + n;
else {
buf_r[i] = buf_w;
greet_status[i] = HAVE_GREETED;
}
}
}
}
// Accept from server socket
if (FD_ISSET(server_fd, &readables)) {
socklen_t len = sizeof(struct sockaddr_in);
int client_fd = accept(server_fd, (struct sockaddr *)&sa, &len); assert_sys(client_fd);
printf("----- New client %d\n", client_fd);
if (client_fd > MAX_CLIENTS) { kick(client_fd); continue; }
FD_SET(client_fd, &readfds);
if (client_fd >= nfds) nfds = client_fd + 1;
buf_r[client_fd] = 0;
greet_status[client_fd] = WAIT_GREET;
}
// Read from stdin
if (FD_ISSET(0, &readables)) {
ssize_t n = read(0, &buf[buf_w], BUF_SIZE - buf_w); assert_sys(n);
if (n <= 0) return 0;
buf_w = (buf_w + n) % BUF_SIZE;
// All active sockets will be in `readfds` so just copy the entire set
// instead of setting them in `writefds` one-by-one
memcpy(&writefds, &readfds, sizeof(fd_set));
}
}
}
@iAmGroute
Copy link
Author

iAmGroute commented Mar 20, 2020

The file 'greet' must have '\r\n' line endings and also have an empty line ('\r\n\r\n') at the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment