Skip to content

Instantly share code, notes, and snippets.

@laurelkeys
Last active March 6, 2026 18:56
Show Gist options
  • Select an option

  • Save laurelkeys/16099abe4f6379b740ae68bf99076ae0 to your computer and use it in GitHub Desktop.

Select an option

Save laurelkeys/16099abe4f6379b740ae68bf99076ae0 to your computer and use it in GitHub Desktop.
Notes on "Beej’s Guide to Network Programming", with examples rewritten in C99.
/*****************************************************************************
*** IP Addresses, structs, and Data Munging *********************************
============================================================================*/
// See https://man7.org/linux/man-pages/man3/getaddrinfo.3.html
// |
// | struct addrinfo {
// | int ai_flags; // AI_PASSIVE, AI_CANONNAME, etc.
// | int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
// | int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
// | int ai_protocol; // use 0 for "any"
// | size_t ai_addrlen; // size of ai_addr in bytes
// | struct sockaddr *ai_addr; // struct sockaddr_in or _in6
// | char *ai_canonname; // full canonical hostname
// |
// | struct addrinfo *ai_next; // linked list, next node
// | };
//
// The `sockaddr` struct is defined as:
// |
// | struct sockaddr {
// | unsigned short sa_family; // address family, AF_xxx
// | char sa_data[14]; // 14 bytes of protocol address
// | };
//
// A pointer to `sockaddr` can be cast to a pointer to `sockaddr_in` (IPv4),
// and vice-versa. The latter is defined as:
// |
// | struct sockaddr_in {
// | short int sin_family; // address family, AF_INET
// | unsigned short int sin_port; // port number, Network Byte Order
// | struct in_addr sin_addr; // internet address
// | unsigned char sin_zero[8]; // same size as struct sockaddr
// | };
// |
// | struct in_addr {
// | uint32_t s_addr; // 4-byte IP address, Network Byte Order
// | };
//
// Use `sockaddr_in6` for IPv6:
// |
// | struct sockaddr_in6 {
// | u_int16_t sin6_family; // address family, AF_INET6
// | u_int16_t sin6_port; // port number, Network Byte Order
// | u_int32_t sin6_flowinfo; // IPv6 flow information
// | struct in6_addr sin6_addr; // IPv6 address
// | u_int32_t sin6_scope_id; // Scope ID
// | };
// |
// | struct in6_addr {
// | unsigned char s6_addr[16]; // IPv6 address
// | };
//
// Lastly, there is another simple structure, `sockaddr_storage`,
// that is designed to be large enough to hold both IPv4 and IPv6 structures:
// |
// | struct sockaddr_storage {
// | sa_family_t ss_family; // address family
// |
// | // all this is padding, implementation specific, ignore it:
// | char __ss_pad1[_SS_PAD1SIZE];
// | int64_t __ss_align;
// | char __ss_pad2[_SS_PAD2SIZE];
// | };
//
// You can use `inet_pton` ("presentation to network") to convert an
// IP address in numbers-and-dots notation into a `in_addr` or `in6_addr`:
// |
// | struct sockaddr_in sa; // IPv4
// | struct sockaddr_in6 sa6; // IPv6
// |
// | inet_pton(AF_INET, "10.12.110.57", &(sa.sin_addr));
// | inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr));
// |
//
// @Note: its return value is 1 on success, 0 if the string doesn't represent
// a valid address for the given address family, and -1 on error.
// A function for the other way around (i.e. "network to presentation") also exists:
// |
// | char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
// | char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
// |
// | inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);
// | printf("The IPv4 address is: %s\n", ip4);
// |
// | inet_ntop(AF_INET6, &(sa6.sin6_addr), ip6, INET6_ADDRSTRLEN);
// | printf("The address is: %s\n", ip6);
// |
//
// @Note: often times, the firewall translates "internal" IP addresses to
// "external" (that everyone else in the world knows) IP addresses using
// a process called Network Address Translation, or NAT.
//
// Althought networks behind a NATing firewall don't need to be on one of
// the specified reserved networks, but they commonly are.
//
// 10.x.x.x is one of a few reserved networks that are only to be used either
// on fully disconnected networks, or on networks that are behind firewalls.
// @Note: NAT and IPv6 don't generally mix, but IPv6 has private networks too,
// in a sense. They'll start with `fdXX:` or `fcXX:`.
/*****************************************************************************
*** Jumping from IPv4 to IPv6 ***********************************************
============================================================================*/
// See https://beej.us/guide/bgnet/pdf/bgnet_usl_c_1.pdf#page=20
/*****************************************************************************
*** System Calls or Bust ****************************************************
============================================================================*/
// @Note: the following includes are required for the system calls below:
// |
// | #include <sys/types.h>
// | #include <sys/socket.h>
//
/*
** getaddrinfo()—Prepare to launch!
*/
// `getaddrinfo` does all kinds of good stuff for you, including DNS and
// service name lookups, and fills out the structs you need:
// |
// | #include <netdb.h>
// |
// | int getaddrinfo(const char *node, // e.g. "www.example.com" or IP
// | const char *service, // e.g. "http" or port number
// | const struct addrinfo *hints,
// | struct addrinfo **res);
//
// It gives you a pointer to a linked-list (`res`) of results, based on:
// * `node`: the host name to connect to, or an IP address
// * `service`: either a port number or a particular service (e.g. http, ftp, smtp)
// * `hints`: points to a struct `addrinfo` filled out with relevant information
// @Example: https://beej.us/guide/bgnet/pdf/bgnet_usl_c_1.pdf#page=22
/*
** socket()—Get the File Descriptor!
*/
// |
// | int socket(int domain, int type, int protocol);
//
// Where the arguments specify what kind of socket we want:
// * `domain`: PF_INET (IPv4) or PF_INET6 (IPv6)
// * `type`: SOCK_STREAM (stream) or SOCK_DGRAM (datagram)
// * `protocol`: "tcp", "udp" or 0 to choose based on `type`
//
// It returns a socket descriptor, or -1 on error (and sets `errno`).
/*
** bind()—What port am I on?
*/
// |
// | int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
//
// Where:
// * `sockfd`: socket file descriptor returned by `socket`
// * `my_addr`: pointer to information about your address, namely, port and IP
// * `addrlen`: the length in bytes of that address
//
// It also returns -1 on error and sets `errno` to the error's value.
// @Example: https://beej.us/guide/bgnet/pdf/bgnet_usl_c_1.pdf#page=25
// @Note: all ports below 1024 are RESERVED (unless you're the superuser)!
// You can have any number above that, up to 65535 (if they aren't already being used).
// @Note: if you are connecting to a remote machine and you don't care what your
// local port is, simply call `connect` as it'll check to see if the socket is unbound,
// and will `bind` it to an unused local port if necessary.
/*
** connect()—Hey, you!
*/
// |
// | int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
//
// Where:
// * `sockfd`: socket file descriptor returned by `socket`
// * `serv_addr`: contains the destination port and IP address
// * `addrlen`: length in bytes of the server address structure
//
// Be sure to check the return value for -1 on error (and the set `errno`).
/*
** listen()—Will somebody please call me?
*/
// Say you want to wait for incoming connections and handle them in
// some way. The process is two step: first you `listen`, then you `accept`.
//
// The `listen` call is fairly simple, but requires a bit of explanation:
// |
// | int listen(int sockfd, int backlog);
//
// Where:
// * `sockfd`: socket file descriptor returned by `socket`
// * `backlog`: the number of connections allowed on the incoming queue
//
// Incoming connections are going to wait in this queue until you `accept` them
// and `backlog` is the limit on how many can queue up. Most systems silently
// limit this number to about 20.
//
// As per usual, `listen` returns -1 and sets `errno` on error.
// @Note: we need to call `bind` before we call `listen` so that the server is
// running on a specific port. So if you're going to be listening for incoming
// connections, the sequence of system calls you'll make is:
// |
// | getaddrinfo();
// | socket();
// | bind();
// | listen();
// | /* accept() goes here */
//
/*
** accept()—“Thank you for calling port 3490.”
*/
// Calling `accept` will get a queued up pending connection on a port you're
// listening to, and return a brand new socket file descriptor to use for this
// single connection. Thus, you'll have two socket file descriptors: the original
// one (still listening for more new connections), and the newly created one,
// which will be finally ready to `send` and `recv`.
// |
// | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//
// Where:
// * `sockfd`: the `listen`ing socket descriptor
// * `addr`:
// usually a pointer to a local struct `sockaddr_storage`, which is where the
// information about the incoming connection will go (e.g. host and port)
// * `addrlen`:
// a local integer variable that should be set to sizeof(struct sockaddr_storage)
// before its address is passed to `accept`, so that it will not put more than that
// many bytes in, and if it puts fewer, it'll change `addrlen` value to reflect it
//
// It also returns -1 on error (and sets `errno` with the error code).
// @Example: https://beej.us/guide/bgnet/pdf/bgnet_usl_c_1.pdf#page=29
/*
** send() and recv()—Talk to me, baby!
*/
// @Note: these two functions are for communicating over stream sockets or connected
// datagram sockets. If you want to use regular unconnected datagram sockets, you'll
// need `sendto` and `recvfrom`.
//
// |
// | int send(int sockfd, const void *msg, int len, int flags);
//
// Where:
// * `sockfd`: the socket descriptor you want to send data to
// * `msg`: pointer to the data you want to send
// * `len`: length of that data in bytes
// * `flags`: you'll pretty much always want to set this to 0
//
// `send` returns the number of bytes actually sent out, so, if it
// doesn't match `len`, you'll need to send the missing data again.
// On error, it returns -1 and sets `errno`.
//
//
// |
// | int recv(int sockfd, void *buf, int len, int flags);
//
// Where:
// * `sockfd`: the socket descriptor to read from
// * `buf`: buffer to read information into
// * `len`: maximum length of the buffer
// * `flags`: you'll pretty much always want to set this to 0
//
// `recv` returns the number of bytes actually read into the buffer,
// or -1 on error (with `errno` set, accordingly).
// @Note: a return of 0 means that the remote side has closed the connection.
/*
** sendto() and recvfrom()—Talk to me, DGRAM-style
*/
// Since datagram sockets aren't connected to a remote host, we also need to
// give it the destination address:
// |
// | int sendto(int sockfd, const void *msg, int len, unsigned int flags,
// | const struct sockaddr *to, socklen_t tolen);
//
// Which is basically a call to `send` with two other pieces of information:
// * `to`: a (usually "reinterpret cast") pointer to a struct `sockaddr`
// * `tolen`: simply set it to sizeof(*to), or sizeof(struct sockaddr_storage)
//
// It returns the number of bytes sent, or -1 on error, just like `send`.
// @Note: `sockaddr_in`, `sockaddr_in6` and `sockaddr_storage` can be cast
// into a struct `sockaddr` and passed as the argument `to`, containing the
// destination IP address and port.
// |
// | int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
// | struct sockaddr *from, int *fromlen);
//
// Again, this is just like `recv`, with the addition of a couple fields:
// * `from`:
// pointer to a local struct `sockaddr_storage` that will be filled with
// the IP address and port of the originating machine
// * `fromlen`:
// pointer to a local int, which should be initialized to sizeof(*from) or
// sizeof(struct sockaddr_storage)
//
// `recvfrom` returns the number of bytes received, or -1 on error, and sets the
// value of `fromlen` to the length of the address actually stored in `from`.
// @Note: we use `sockaddr_storage` instead of `sockaddr_in` (or `sockaddr_in6`)
// so that it is IPv4 / IPv6 generic (as we know it will be big enough for either).
// @Note: if you `connect` a datagram socket, you can then simply use `send` and
// `recv` for all your transactions. The socket itself is still a datagram socket
// and the packets still use UDP, but the socket interface will automatically add
// the destination and source information for you.
/*
** close() and shutdown()—Get outta my face!
*/
// You can just use the regular Unix file descriptor `close` function:
// |
// | close(sockfd);
//
// This will prevent any more reads and writes to the socket. Anyone attempting to
// read or write the socket on the remote end will receive an error.
// If you want more control over how the socket closes, you can use the following, as it
// allows you to cut off communication in a certain direction (or both ways like `close`):
// |
// | int shutdown(int sockfd, int how);
//
// Where:
// * `sockfd`: socket file descriptor you want to shutdown
// * `how`:
// one of the following:
// 0 = Further receives are disallowed
// 1 = Further sends are disallowed
// 2 = Further sends and receives are disallowed (like `close`)
//
// `shutdown` returns 0 on success and -1 on error.
// @Note: `shutdown` doesn't actually close the file descriptor, it just
// changes its usability. To free a socket descriptor, you need to use `close`.
/*
** getpeername()—Who are you?
*/
// `getpeername` will tell you who is at the other end of a connected stream socket:
// |
// | int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
//
// Where:
// * `sockfd`: descriptor of the connected stream socket
// * `addr`: pointer to a struct holding information about the other side of the connection
// * `addrlen`: pointer to an int, initialized to sizeof(*addr) or sizeof(struct sockaddr)
//
// It returns -1 on error and sets `errno` accordingly.
// @Note: the address can then be used with `inet_ntop`, `getnameinfo`, or
// `gethostbyaddr`to print or get more information.
/*
** gethostname()—Who am I?
*/
// This returns the name of the computer that your program is running on.
// |
// | #include <unistd.h>
// |
// | int gethostname(char *hostname, size_t size);
//
// Where:
// * `hostname`: pointer that will contain the hostname upon the function's return
// * `size`: length in bytes of the `hostname` array
//
// It returns 0 on successful completion, and -1 on error, setting `errno` as usual.
// @Note: the name can then be used by `gethostbyname` to determine its IP address.
/*****************************************************************************
*** Client-Server Background ************************************************
============================================================================*/
// See server.c and client.c
// See listener.c and talker.c
/*****************************************************************************
*** Slightly Advanced Techniques ********************************************
============================================================================*/
/*
** Blocking
*/
// Lots of functions block. `accept` blocks, all the `recv` functions block, when you
// first create the socket descriptor with `socket`, the kernel sets it to blocking.
// If you don't want a socket to be blocking, you have to make a call to `fcntl`:
// |
// | #include <unistd.h>
// | #include <fcntl.h>
// |
// | ...
// | sockfd = socket(PF_INET, SOCK_STREAM, 0);
// | fcntl(sockfd, F_SETFL, O_NONBLOCK);
// | ...
//
// By setting a socket to non-blocking, you can effectively "poll" the socket for information.
// @Note: if you try to read from it and there's no data, since it's not allowed to block,
// it will return -1 and `errno` will be set to EAGAIN or EWOULDBLOCK.
// @Note: this type of polling is generally a bad idea though.
/*
** poll()—Synchronous I/O Multiplexing
*/
// @Note: `poll` is horribly slow when it comes to giant numbers of connections.
// In those circumstances, look into an event library such as libevent27.
// |
// | #include <poll.h>
// |
// | int poll(struct pollfd fds[], nfds_t nfds, int timeout);
//
// Where:
// * `fds`: array of information (which sockets to monitor for what)
// * `nfds`: count of elements in the array
// * `timeout`: timeout in miliseconds (negative values to wait forever)
//
// Calling `poll` returns the number of elements that have had an event occur.
// The struct `pollfd` is defined as:
// |
// | struct pollfd {
// | int fd; // the socket descriptor
// | short events; // bitmap of events we're interested in
// | short revents; // when `poll` returns, bitmap of events that occurred
// | };
//
// Where the `events` field is the bitwise-OR of the following:
// POLLIN = Alert me when data is ready to `recv` on this socket
// POLLOUT = Alert me when I can `send` data to this socket without blocking
// @Note: if the `fd` field is negative `poll` will ignore it.
// @Example: https://beej.us/guide/bgnet/pdf/bgnet_usl_c_1.pdf#page=110
// See pollserver.c
/*
** select()—Synchronous I/O Multiplexing, Old School
*/
// @Note: both `select` and `poll` offer similar functionality and performance, and
// only really differ in how they're used. `select` might be slightly more portable,
// but is perhaps a little clunkier in use.
// @Todo: continue from pdf page 46
// https://beej.us/guide/bgnet/pdf/bgnet_usl_c_1.pdf#page=50
//
// All this client does is connect to the host you specify on the command line, port 3490.
// It gets the string that the server sends.
//
// Reference:
// client.c -- a stream socket client demo
// https://beej.us/guide/bgnet/examples/client.c
//
// @Note: `connect` returns "Connection refused" if you don't run the server before.
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // close()
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT "3490" // the port the client will connect to
#define MAXDATASIZE 100 // max number of bytes we can get at once
/// Returns the socket address: `sockaddr_in` for IPv4, `sockaddr_in6` for IPv6
/// and NULL on error.
void *get_in_addr(struct sockaddr *sa) {
if (sa->sa_family == AF_INET) {
struct sockaddr_in *ipv4_sa = (struct sockaddr_in *)sa;
return &(ipv4_sa->sin_addr);
} else if (sa->sa_family == AF_INET6) {
struct sockaddr_in6 *ipv6_sa = (struct sockaddr_in6 *)sa;
return &(ipv6_sa->sin6_addr);
} else assert(false);
return NULL;
}
int main(int argc, char *argv[]) {
int err;
if (argc != 2) {
fprintf(stderr, "usage: client hostname\n");
return EXIT_FAILURE;
}
const struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
if (0 != (err = getaddrinfo(argv[1], PORT, &hints, &res))) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
return EXIT_FAILURE;
}
struct addrinfo *p;
int sockfd;
// Loop through all the results and connect to the first we can.
for (p = res; p != NULL; p = p->ai_next) {
if (-1 == (sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))) {
perror("client: socket");
} else if (-1 == connect(sockfd, p->ai_addr, p->ai_addrlen)) {
perror("client: connect");
close(sockfd);
} else break; // connected
}
if (p == NULL) {
fprintf(stderr, "client: failed to connect\n");
if (res) freeaddrinfo(res);
return EXIT_FAILURE;
}
char addr_str[INET6_ADDRSTRLEN];
inet_ntop(
p->ai_family,
get_in_addr((struct sockaddr *)p->ai_addr),
addr_str, sizeof(addr_str)
);
printf("client: connecting to %s\n", addr_str);
// Free up `res`, which was allocated by getaddrinfo().
freeaddrinfo(res);
char msg_buf[MAXDATASIZE];
const int num_bytes_recvd = recv(sockfd, msg_buf, MAXDATASIZE - 1, 0);
if (-1 == num_bytes_recvd) {
perror("recv");
return EXIT_FAILURE;
}
// @Note: `num_bytes_recvd` could be less than `MAXDATASIZE` (though, messages
// smaller than 1K are usually fine), thus, we could check for this in here,
// and send the missing part if we wanted to.
msg_buf[num_bytes_recvd] = '\0';
printf("client: received '%s'\n", msg_buf);
close(sockfd);
return EXIT_SUCCESS;
}
//
// Sits on a machine waiting for an incoming packet on port 4950.
//
// Reference:
// listener.c -- a datagram sockets "server" demo
// https://beej.us/guide/bgnet/examples/listener.c
//
// @Note: the socket descriptor is closed after receiving a single message.
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // close()
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MYPORT "4950" // the port users will be connecting to
#define MAXBUFLEN 100
/// Returns the socket address: `sockaddr_in` for IPv4, `sockaddr_in6` for IPv6
/// and NULL on error.
void *get_in_addr(struct sockaddr *sa) {
if (sa->sa_family == AF_INET) {
struct sockaddr_in *ipv4_sa = (struct sockaddr_in *)sa;
return &(ipv4_sa->sin_addr);
} else if (sa->sa_family == AF_INET6) {
struct sockaddr_in6 *ipv6_sa = (struct sockaddr_in6 *)sa;
return &(ipv6_sa->sin6_addr);
} else assert(false);
return NULL;
}
int main(void) {
int err;
const struct addrinfo hints = {
.ai_flags = AI_PASSIVE, // use my IP
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_DGRAM,
};
struct addrinfo *res;
if (0 != (err = getaddrinfo(NULL, MYPORT, &hints, &res))) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
return EXIT_FAILURE;
}
struct addrinfo *p;
int sockfd;
// Loop through all the results and bind to the first we can.
for (p = res; p != NULL; p = p->ai_next) {
if (-1 == (sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))) {
perror("listener: socket");
} else if (-1 == bind(sockfd, p->ai_addr, p->ai_addrlen)) {
close(sockfd);
perror("listener: bind");
} else break; // bound socket
}
if (p == NULL) {
fprintf(stderr, "listener: failed to bind\n");
if (res) freeaddrinfo(res);
return EXIT_FAILURE;
}
// Free up `res`, which was allocated by getaddrinfo().
freeaddrinfo(res);
printf("listener: waiting to recvfrom...\n");
struct sockaddr_storage their_addr;
socklen_t addr_len = sizeof(their_addr);
char msg_buf[MAXBUFLEN];
const int num_bytes_recvd = recvfrom(
sockfd,
msg_buf, MAXBUFLEN - 1,
0,
(struct sockaddr *)&their_addr, &addr_len
);
if (-1 == num_bytes_recvd) {
perror("recvfrom");
return EXIT_FAILURE;
}
// @Note: `num_bytes_recvd` could be less than `MAXBUFLEN`.
msg_buf[num_bytes_recvd] = '\0';
char addr_str[INET6_ADDRSTRLEN];
inet_ntop(
their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
addr_str, sizeof(addr_str)
);
printf("listener: got packet from %s\n", addr_str);
printf("listener: packet is %d bytes long\n", num_bytes_recvd);
printf("listener: packet contains \"%s\"\n", msg_buf);
close(sockfd);
return EXIT_SUCCESS;
}
//
// Reference:
// https://beej.us/guide/bgnet/examples/poll.c
//
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
int main(void) {
const struct pollfd pfds[] = {
{
.fd = 0, // standard input
.events = POLLIN, // tell me when ready to read
},
// More if you want to monitor other things as well, e.g.:
// |
// | {
// | .fd = some_socket, // some socket descriptor
// | .events = POLLIN, // tell me when ready to read
// | },
//
};
printf("Hit [Return] or wait 2.5 seconds for timeout\n");
const int num_events = poll(pfds, /*nfds*/1, /*timeout*/2500);
if (num_events == 0) {
printf("Poll timed out!\n");
} else {
// @Note: since there's only one entry in `pfds`, we know that
// it has to be the one that had an event. In case we had many,
// we'd have to scan over it to find which ones they were (and
// we could stop searching after finding `num_events` of them).
const int pollin_happened = pfds[0].revents & POLLIN;
if (pollin_happened)
printf("File descriptor %d is ready to read\n", pfds[0].fd);
else
printf("Unexpected event occurred: %d\n", pfds[0].revents);
}
return EXIT_SUCCESS;
}
//
// Run this in one window, then telnet localhost 9034 from a number of other terminal windows.
// You should be able to see what you type in one window in the other ones (after you hit [Return]).
//
// Not only that, but if you hit Ctrl+] and type "quit" to exit telnet, the server should detect the
// disconnection and remove you from the array of file descriptors.
//
// Reference:
// pollserver.c -- a cheezy multiperson chat server
// https://beej.us/guide/bgnet/examples/pollserver.c
//
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <poll.h> // struct pollfd, poll()
#include <unistd.h> // close()
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT "9034" // the port we are listening on
#define MAXBUFLEN 256
// Error codes.
#define ERR_GET_LISTENER_SOCKET -1
/// Returns the socket address: `sockaddr_in` for IPv4, `sockaddr_in6` for IPv6
/// and NULL on error.
void *get_in_addr(struct sockaddr *sa) {
if (sa->sa_family == AF_INET) {
struct sockaddr_in *ipv4_sa = (struct sockaddr_in *)sa;
return &(ipv4_sa->sin_addr);
} else if (sa->sa_family == AF_INET6) {
struct sockaddr_in6 *ipv6_sa = (struct sockaddr_in6 *)sa;
return &(ipv6_sa->sin6_addr);
} else assert(false);
return NULL;
}
/// Returns a listening socket, or `ERR_GET_LISTENER_SOCKET` if there's an error.
int get_listener_socket(void) {
int err;
// Get us a socket and bind it
const struct addrinfo hints = {
.ai_flags = AI_PASSIVE,
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
if (0 != (err = getaddrinfo(NULL, PORT, &hints, &res))) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
exit(EXIT_FAILURE);
}
const int yes = 1;
struct addrinfo *p;
int listener_fd; // listening socket descriptor
for (p = res; p != NULL; p = p->ai_next) {
if (-1 == (listener_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)))
continue;
// Lose the pesky "address already in use" error message.
setsockopt(listener_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
if (-1 == bind(listener_fd, p->ai_addr, p->ai_addrlen))
close(listener_fd);
else
break; // bound socket
}
if (p == NULL) {
if (res) freeaddrinfo(res);
// If we got here, it means we didn't get bound.
return ERR_GET_LISTENER_SOCKET;
}
// Free up `res`, which was allocated by getaddrinfo().
freeaddrinfo(res);
// Listen.
if (-1 == listen(listener_fd, 10))
return ERR_GET_LISTENER_SOCKET;
return listener_fd;
}
/// Add a new file descriptor to the set.
void add_to_pfds(struct pollfd *pfds[], int newfd, int *fd_count, int *fd_size) {
// If we don't have enough room, realloc more space to the `pfds` array.
if (*fd_count == *fd_size) {
*fd_size *= 2; // double its current size
*pfds = realloc(*pfds, (*fd_size) * sizeof(**pfds));
}
(*pfds)[*fd_count] = (struct pollfd) {
.fd = newfd,
.events = POLLIN, // check ready-to-read
};
++(*fd_count);
}
/// Remove an index from the set.
void del_from_pfds(struct pollfd pfds[], int i, int *fd_count) {
// Copy the one from the end over this one.
pfds[i] = pfds[*fd_count - 1];
--(*fd_count);
}
static void handle_new_connection(const int listener_fd, struct pollfd *pfds[], int *fd_count, int *fd_size);
static void handle_client_data(const int listener_fd, const int sender_fd_idx, struct pollfd pfds[], int *fd_count);
int main(void) {
// Start off with room for 5 connections (we will realloc as necessary).
int fd_count = 0;
int fd_size = 5;
struct pollfd *pfds = malloc(fd_size * sizeof(*pfds));
// Set up and get a listening socket descriptor.
const int listener_fd = get_listener_socket();
if (ERR_GET_LISTENER_SOCKET == listener_fd) {
fprintf(stderr, "error getting listening socket\n");
return EXIT_FAILURE;
}
// Add the listener to the set and update `fd_count`.
pfds[0] = (struct pollfd) {
.fd = listener_fd,
.events = POLLIN, // report ready-to-read on incoming connection
};
++fd_count;
// Main loop.
while (true) {
const int poll_count = poll(pfds, fd_count, /*timeout*/-1);
if (-1 == poll_count) {
perror("poll");
return EXIT_FAILURE;
}
// Run through the existing connections looking for data to read.
for (int i = 0; i < fd_count; ++i) {
// Check if someone's ready to read.
if (pfds[i].revents & POLLIN) {
if (pfds[i].fd == listener_fd) {
// If `listener` is ready to read, handle the new connection.
handle_new_connection(listener_fd, &pfds, &fd_count, &fd_size);
} else {
// Otherwise, we are just a regular client sending data.
handle_client_data(listener_fd, /*sender_fd_idx*/i, pfds, &fd_count);
}
}
}
}
return EXIT_SUCCESS;
}
static void handle_new_connection(
const int listener_fd,
struct pollfd *pfds[],
int *fd_count,
int *fd_size
) {
struct sockaddr_storage remoteaddr;
socklen_t addrlen = sizeof(remoteaddr);
// @Note: accept() returns the new socked descriptor `newfd`
// and sets the client address `remoteaddr`.
int newfd = accept(listener_fd, (struct sockaddr *)&remoteaddr, &addrlen);
if (-1 == newfd) {
perror("accept");
return;
}
// Add `newfd` to `pdfs`.
add_to_pfds(pfds, newfd, fd_count, fd_size);
char addr_str[INET6_ADDRSTRLEN];
inet_ntop(
remoteaddr.ss_family,
get_in_addr((struct sockaddr*)&remoteaddr),
addr_str, sizeof(addr_str)
);
printf("pollserver: new connection from %s on socket %d\n", addr_str, newfd);
}
static void handle_client_data(
const int listener_fd,
const int sender_fd_idx,
struct pollfd pfds[],
int *fd_count
) {
const int sender_fd = pfds[sender_fd_idx].fd;
char msg_buf[MAXBUFLEN];
const int num_bytes_recvd = recv(sender_fd, msg_buf, sizeof(msg_buf), 0);
if (num_bytes_recvd > 0) {
for (int j = 0; j < *fd_count; ++j) {
const int dest_fd = pfds[j].fd;
// Send the received data to everyone, except `listener_fd` and ourselves.
if (dest_fd == listener_fd || dest_fd == sender_fd)
continue;
if (-1 == send(dest_fd, msg_buf, num_bytes_recvd, 0))
perror("send");
}
} else {
// Got an error or connection closed by the client.
if (num_bytes_recvd == 0) {
printf("pollserver: socket %d hung up\n", sender_fd);
} else if (num_bytes_recvd == -1) {
perror("recv");
} else assert(false);
close(sender_fd);
del_from_pfds(pfds, sender_fd_idx, fd_count);
}
}
//
// All this server does is send the string "Hello, world!" out over a stream connection.
//
// All you need to do to test this server is run it in one window, and telnet to it from
// another with `$ telnet remotehostname 3490`, where remotehostname is the name of the
// machine you're running it on.
//
// Reference:
// server.c -- a stream socket server demo
// https://beej.us/guide/bgnet/examples/server.c
//
// @Note: `sigaction` is used to reap zombie processes that appear as the
// `fork`ed child processes exit.
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h> // waitpid(), sigemptyset(), sigaction(), etc.
#include <unistd.h> // fork(), close()
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT "3490" // the port users will be connecting to
#define BACKLOG 10 // how many pending connections the queue will hold
/// Reaps all dead processes.
void sigchld_handler(int s) {
(void)s; // quiet unused variable warning
// @Note: waitpid() might overwrite `errno`, so we save and then restore it.
int saved_errno = errno;
while (waitpid(-1, NULL, WNOHANG) > 0) {}
errno = saved_errno;
}
/// Returns the socket address: `sockaddr_in` for IPv4, `sockaddr_in6` for IPv6
/// and NULL on error.
void *get_in_addr(struct sockaddr *sa) {
if (sa->sa_family == AF_INET) {
struct sockaddr_in *ipv4_sa = (struct sockaddr_in *)sa;
return &(ipv4_sa->sin_addr);
} else if (sa->sa_family == AF_INET6) {
struct sockaddr_in6 *ipv6_sa = (struct sockaddr_in6 *)sa;
return &(ipv6_sa->sin6_addr);
} else assert(false);
return NULL;
}
int main(void) {
int err;
const struct addrinfo hints = {
.ai_flags = AI_PASSIVE, // use my IP
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
if (0 != (err = getaddrinfo(NULL, PORT, &hints, &res))) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
return EXIT_FAILURE;
}
const int yes = 1;
struct addrinfo *p;
int sockfd;
// Loop through all the results and bind to the first we can.
for(p = res; p != NULL; p = p->ai_next) {
if (-1 == (sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))) {
perror("server: socket");
} else if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) {
perror("setsockopt");
return EXIT_FAILURE;
}
if (-1 == bind(sockfd, p->ai_addr, p->ai_addrlen)) {
close(sockfd);
perror("server: bind");
} else break; // bound socket
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
if (res) freeaddrinfo(res);
return EXIT_FAILURE;
}
// Free up `res`, which was allocated by getaddrinfo().
freeaddrinfo(res);
if (-1 == listen(sockfd, BACKLOG)) {
perror("listen");
return EXIT_FAILURE;
}
struct sigaction sa = {
.sa_handler = sigchld_handler,
.sa_flags = SA_RESTART,
};
if (-1 == sigemptyset(&sa.sa_mask)) {
perror("sigemptyset");
return EXIT_FAILURE;
}
if (-1 == sigaction(SIGCHLD, &sa, NULL)) {
perror("sigaction");
return EXIT_FAILURE;
}
printf("server: waiting for connections...\n");
// Main accept() loop.
while (true) {
struct sockaddr_storage their_addr; // connector's address information
socklen_t sin_size = sizeof(their_addr);
// @Note: we listen on sockfd and accept new connections on new_fd.
int new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
if (-1 == new_fd) {
perror("accept");
} else {
char addr_str[INET6_ADDRSTRLEN];
inet_ntop(
their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
addr_str, sizeof(addr_str)
);
printf("server: got connection from %s\n", addr_str);
if (!fork()) {
// This is the child process, so we don't need `sockfd`.
close(sockfd);
if (-1 == send(new_fd, "Hello, world!", 13, 0)) {
perror("send");
}
close(new_fd);
return EXIT_SUCCESS;
}
// This is the parent, so we don't need `new_fd`.
close(new_fd);
}
}
return EXIT_SUCCESS;
}
//
// Reference:
// showip.c -- show IP addresses for a host given on the command line
// https://beej.us/guide/bgnet/examples/showip.c
//
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(int argc, char *argv[]) {
int err;
if (argc != 2) {
fprintf(stderr, "usage: showip hostname\n");
return EXIT_FAILURE;
}
const struct addrinfo hints = {
.ai_family = AF_UNSPEC, // AF_INET or AF_INET6 to force version
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
if (0 != (err = getaddrinfo(/*name*/argv[1], /*service*/NULL, &hints, &res))) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
return EXIT_FAILURE;
}
printf("IP addresses for %s:\n\n", argv[1]);
for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
void *addr; // either a `in_addr *` or a `in6_addr *`
char *ipver; // "IPv4" or "IPv6", depending on `addr`
// Get the pointer to the address itself.
if (p->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else if (p->ai_family == AF_INET6) {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
} else assert(false);
// Convert the IP to a string and print it.
char ipstr[INET6_ADDRSTRLEN];
inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));
printf(" %s: %s\n", ipver, ipstr);
}
printf("\n");
// Free the linked list allocated by `getaddrinfo`.
freeaddrinfo(res);
return EXIT_SUCCESS;
}
//
// Sends a packet to port 4950, on the specified machine, that contains
// whatever the user enters on the command line.
//
// Reference:
// talker.c -- a datagram "client" demo
// https://beej.us/guide/bgnet/examples/talker.c
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // close()
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#define SERVERPORT "4950" // the port users will be connecting to
int main(int argc, char *argv[]) {
int err;
if (argc != 3) {
fprintf(stderr, "usage: talker hostname message\n");
return EXIT_FAILURE;
}
const char *hostname = argv[1];
const char *message = argv[2];
const struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_DGRAM,
};
struct addrinfo *res;
if (0 != (err = getaddrinfo(argv[1], SERVERPORT, &hints, &res))) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
return EXIT_FAILURE;
}
struct addrinfo *p;
int sockfd;
// Loop through all the results and make a socket.
for (p = res; p != NULL; p = p->ai_next) {
if (-1 == (sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))) {
perror("talker: socket");
} else break; // created socket
}
if (p == NULL) {
fprintf(stderr, "talker: failed to create socket\n");
if (res) freeaddrinfo(res);
return EXIT_FAILURE;
}
const int num_bytes_recvd = sendto(
sockfd,
message, strlen(message),
0,
p->ai_addr, p->ai_addrlen
);
// Free up `res`, which was allocated by getaddrinfo().
freeaddrinfo(res);
if (-1 == num_bytes_recvd) {
perror("talker: sendto");
return EXIT_FAILURE;
}
printf("talker: sent %d bytes to %s\n", num_bytes_recvd, hostname);
close(sockfd);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment