Last active
March 6, 2026 18:56
-
-
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.
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
| /***************************************************************************** | |
| *** 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 |
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
| // | |
| // 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; | |
| } |
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
| // | |
| // 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; | |
| } |
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
| // | |
| // 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; | |
| } |
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
| // | |
| // 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); | |
| } | |
| } |
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
| // | |
| // 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; | |
| } |
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
| // | |
| // 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; | |
| } |
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
| // | |
| // 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