Last active
May 8, 2022 15:30
-
-
Save Logioniz/2520798a4e4bcf1fb65e to your computer and use it in GitHub Desktop.
Non-blocking servers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <netdb.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <sys/epoll.h> | |
#include <errno.h> | |
#define MAXEVENTS 64 | |
static int make_socket_non_blocking (int sfd) { | |
int flags, s; | |
flags = fcntl (sfd, F_GETFL, 0); | |
if (flags == -1){ perror ("fcntl"); return -1; } | |
flags |= O_NONBLOCK; | |
s = fcntl (sfd, F_SETFL, flags); | |
if (s == -1) { perror ("fcntl"); return -1; } | |
return 0; | |
} | |
static int create_and_bind (char *port) { | |
struct addrinfo hints; | |
struct addrinfo *result, *rp; | |
int s, sfd; | |
memset (&hints, 0, sizeof (struct addrinfo)); | |
hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */ | |
hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */ | |
hints.ai_flags = AI_PASSIVE; /* All interfaces */ | |
s = getaddrinfo (NULL, port, &hints, &result); | |
if (s != 0) { | |
fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s)); | |
return -1; | |
} | |
for (rp = result; rp != NULL; rp = rp->ai_next) { | |
sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); | |
if (sfd == -1) continue; | |
s = bind (sfd, rp->ai_addr, rp->ai_addrlen); | |
if (s == 0) { | |
/* We managed to bind successfully! */ | |
break; | |
} | |
close (sfd); | |
} | |
if (rp == NULL) { | |
fprintf (stderr, "Could not bind\n"); | |
return -1; | |
} | |
freeaddrinfo (result); | |
return sfd; | |
} | |
int main (int argc, char *argv[]) { | |
int sfd, s; | |
int efd; | |
struct epoll_event event; | |
struct epoll_event *events; | |
if (argc != 2) { | |
fprintf (stderr, "Usage: %s [port]\n", argv[0]); | |
exit (EXIT_FAILURE); | |
} | |
sfd = create_and_bind (argv[1]); | |
if (sfd == -1) abort (); | |
s = make_socket_non_blocking (sfd); | |
if (s == -1) abort (); | |
s = listen (sfd, SOMAXCONN); | |
if (s == -1) { | |
perror ("listen"); | |
abort (); | |
} | |
efd = epoll_create1 (0); | |
if (efd == -1) { | |
perror ("epoll_create"); | |
abort (); | |
} | |
event.data.fd = sfd; | |
event.events = EPOLLIN | EPOLLET; | |
s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event); | |
if (s == -1) { | |
perror ("epoll_ctl"); | |
abort (); | |
} | |
/* Buffer where events are returned */ | |
events = calloc (MAXEVENTS, sizeof event); | |
/* The event loop */ | |
while (1) { | |
int n, i; | |
n = epoll_wait (efd, events, MAXEVENTS, -1); | |
for (i = 0; i < n; i++) { | |
if ( | |
(events[i].events & EPOLLERR) || | |
(events[i].events & EPOLLHUP) || | |
(!(events[i].events & EPOLLIN))) { | |
/* An error has occured on this fd, or the socket is not | |
ready for reading (why were we notified then?) */ | |
fprintf (stderr, "epoll error\n"); | |
close (events[i].data.fd); | |
continue; | |
} else if (sfd == events[i].data.fd) { | |
/* We have a notification on the listening socket, which means one or more incoming connections. */ | |
while (1) { | |
struct sockaddr in_addr; | |
socklen_t in_len; | |
int infd; | |
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; | |
in_len = sizeof in_addr; | |
infd = accept (sfd, &in_addr, &in_len); | |
if (infd == -1) { | |
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { | |
/* We have processed all incoming connections. */ | |
break; | |
} else { | |
perror ("accept"); | |
break; | |
} | |
} | |
s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV); | |
if (s == 0) printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", infd, hbuf, sbuf); | |
/* Make the incoming socket non-blocking and add it to the list of fds to monitor. */ | |
s = make_socket_non_blocking (infd); | |
if (s == -1) abort (); | |
event.data.fd = infd; | |
event.events = EPOLLIN | EPOLLET; | |
s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event); | |
if (s == -1) { | |
perror ("epoll_ctl"); | |
abort (); | |
} | |
} | |
continue; | |
} else { | |
/* We have data on the fd waiting to be read. Read and display it. We must read whatever data is available | |
completely, as we are running in edge-triggered mode and won't get a notification again for the same data. */ | |
int done = 0; | |
while (1) { | |
ssize_t count; | |
char buf[512]; | |
count = read (events[i].data.fd, buf, sizeof buf); | |
if (count == -1) { | |
/* If errno == EAGAIN, that means we have read all data. So go back to the main loop. */ | |
if (errno != EAGAIN) { perror ("read"); done = 1; } | |
break; | |
} else if (count == 0) { | |
/* End of file. The remote has closed the connection. */ | |
done = 1; | |
break; | |
} | |
/* Write the buffer to standard output */ | |
s = write (1, buf, count); | |
if (s == -1) { perror ("write"); abort (); } | |
} | |
if (done) { | |
printf ("Closed connection on descriptor %d\n", events[i].data.fd); | |
/* Closing the descriptor will make epoll remove it from the set of descriptors which are monitored. */ | |
close (events[i].data.fd); | |
} | |
} | |
} | |
} | |
free (events); | |
close (sfd); | |
return EXIT_SUCCESS; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl -w | |
use Mojo::Base -strict; | |
use Socket; | |
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK O_NDELAY); | |
use IO::Select; | |
my $port = 3000; | |
my $proto = getprotobyname('tcp'); | |
my $server_addr = '127.0.0.1'; | |
my $server; | |
socket($server, PF_INET, SOCK_STREAM, $proto) or die "Can't open socket $!\n"; | |
setsockopt($server, SOL_SOCKET, SO_REUSEADDR, 1) or die "Can't set socket option to SO_REUSEADDR $!\n"; | |
bind($server, pack_sockaddr_in($port, inet_aton($server_addr))) or die "Can't bind to port $port! \n"; | |
listen($server, 5) or die "listen: $!"; | |
print "SERVER started on port $port\n"; | |
# my $flags = fcntl($server, F_GETFL, 0) or die "Can't get flags for server socket: $!\n"; | |
# fcntl($server, F_SETFL, $flags | O_NONBLOCK) or die "Can't make server socket nonblocking: $!\n"; | |
my $select = IO::Select->new($server); | |
my $done = 0; | |
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{ABRT} = sub { $done = 1 }; | |
while (!$done) { | |
# each second watch who write message | |
for my $fh ($select->can_read(1)) { | |
if ($fh == $server) { | |
my ($client_addr, $client); | |
$client_addr = accept($client, $server); | |
my $name = gethostbyaddr($client_addr, AF_INET); | |
print "Connection recieved from $name\n"; | |
$select->add($client); | |
next; | |
} | |
my $client = $fh; | |
my $data = ''; | |
my $rv = recv $client, $data, 20, 0; | |
unless (defined $rv && length $data) { | |
$select->remove($client); | |
shutdown $client, 2; | |
close $client; | |
next; | |
} | |
print $data; | |
my $result = `$data`; | |
send $client, $result, 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment