Skip to content

Instantly share code, notes, and snippets.

@vonnenaut
Last active January 5, 2021 11:55
Show Gist options
  • Save vonnenaut/ab5680eb20ba42f156ae0be0b6000513 to your computer and use it in GitHub Desktop.
Save vonnenaut/ab5680eb20ba42f156ae0be0b6000513 to your computer and use it in GitHub Desktop.
C Socket Programming

C Socket Programming

Sockets are a way to speak to other programs using standard Unix file descriptors.

Basic Overview

socket() -> connect() -> [send()|recv()]

int socket(int _domain, int _type, int _protocol) _THROW;

  1. Creat a socket.
  • make a call to socket() system routine, specifying type, protocol and domain
  • socket()returns a socket descriptor
  1. Connect to a remote address
  • specify IP and port
  1. Communicate through it.
  • use specialized send() and recv() socket calls

Some Types of sockets

  • Internet sockets - DARPA internet addresses; 2 types:
    1. Stream sockets SOCK_STREAM
    2. Datagram sockets SOCK_DGRAM, sometimes called connectionless sockets
  • Unix sockets - path names on local nodes
  • X.25 sockets - CCITT X.25 addresses
  • Raw sockets - allow access to underlying transport provider (very powerful) SOCK_RAW

 


Client Socket Workflow

The client socket is created with socket(), connected to a remote address with connect() and finally retrieves data with recv().

socket() -> connect() -> recv()

 


Server Socket Workflow

The server socket is created with socket(), bound to an IP port with bind(), monitors that port with listen(), allows an in-bound connection to a client socket with accept() and finally communicates two-way with send() and recv().

socket() -> bind() -> listen() -> accept()

 


Stream Sockets

provide errorless, in-order delivery via 2-way communication.

  • telnet and HTTP use stream sockets
  • use TCP to achieve this

Datagram Sockets

provide out-of-order, unreliable delivery

  • use UDP (User Datagram Protocol) to achieve this
  • connectionless; don't have to maintain open connection, i.e., low overhead, quick communication
  • tftp, dhcpcd (DHCP client), multiplayer games, streaming audio and video use datagram sockets
  • reliable SOCK_DGRAM applications use an acknowledgement procedure which confirms delivery of each datagram packet by getting an ACK from the receiver, otherwise resending those not ACK-ed

 


IPv4 and IPv6

127.0.0.1 v4 loopback address ::1 v6 loopback address

Compatibility

192.0.2.33 (v4) --> ::ffff:192.0.2.33

Netmasks

255.255.255.252 -> 11111111 . 11111111 . 11111100

  • also referred to by /30 representing the number of bits for the network portion
  • only last 2 bits get through, representing possible host addresses
  • the rest are for network addresses

 

Byte Order

Big-Endian - aka, network byte order, the bigger numbers are on the left, as usual; b34f is stored in sequential bytes as b3 and then 4f Little-Endian - bytes are stored in reverse order; b34f is stored sequentially as 4f, then b3

  • you will always run values through a function to convert host byte order (which might be big-endian or little-endian) into big-endian/network byte order before sending them through a socket connection
Byte-order Functions Table
Byte-Order Function Description
htons() host-to-network short
htonl() host-to-network long
ntohs() network-to-host short
ntohl() network-to-host long
  • For floating point numbers, check out serialization

 


IPv4 Socket Setup

a struct called addrinfo is used to prep socket address structures for subsequent use, and for host name and service name lookups

addrinfo struct layout
struct addrinfo {
    int             ai_flags;       // AI_PASSIVE, AI_CANNONNAME, 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
}

Process

  1. load struct
  2. call getaddrinfo(), which returns ptr to new linked list of the above structs populated with the needed info.
  • use ai_family to force selection of IPv4 (AF_INET) or v6 (AF_INET6), or use AF_UNSPEC

The embedded structs inside the above might need to be accessed to get info. from them. They're structured as follows:

sockaddr struct
struct sockaddr {
    unsigned short  sa_family;    // address family, AF_xxx
    char            sa_data[14];  // 14 bytes of protocol address
};
  • sa_family for our purposes will be either AF_INET or AF_INET6

  • sa_data contains destination address and port # for the socket

  • To deal w/struct sockaddr, we use struct sockaddr_in for use with IPv4

    • a ptr to struct sockaddr_in can be cast to a ptr to struct sockaddr and vice versa
    • so, even though connect() wants struct sockaddr*, you can use struct sockaddr_in by casting it to the former at the last minute
sockaddr_in struct
// IPv4-only, see struct sockaddr_in6 for IPv6

struct sockaddr_in {
    shrot int          sin_family;  // Address family, AF_INET
    unsigned short int sin_port;    // Port number
    struct in_addr     sin_addr;    // Internet address
    unsigned char      sin_zero[8]; // Same size as struct sockaddr
};
  • this struct makes it easy to ref elements of socket address
  • sin_zero should be set to all zeros via memset()
  • sin_family corresponds to sa_family and should be set to AF_INET
  • sin_port must be in Network Byte Order by using htons()

Digging deeper, here's struct in_addr from above:

in_addr struct
// IPv4-only - see struct in6_addr for IPv6
// Internet address (a structure for historical reasons
struct in_addr {
    uint32_t s_addr;  // a 32-bit int (4 bytes)
};
  • used to be a union, but now it's an int

 

IPv6 Socket Setup

IPv6 Setup Example
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
};

// not knowing if struct will be filled out w/IPv4 or v6, this parallel struct can handle either, then be cast to the type needed
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];
};

 


Storing an IP Address in a Struct

  • IP address must first be converted via inet_pton(), then stored
  • pton stands for 'presentation to network'
  • the code below is not robust - always check that it worked and handle it if it fails (returns -1)
Storing IP Address in Struct Example
struct sockaddr_in sa;    // IPv4
struct sockaddr_in6 sa6;  // IPv6

inet_pton(AF_INET, "10.12.110.57", &(sa.sin_addr)); // IPv4
inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr)); // IPv6

 

Retrieving an IP Address from a Struct

  • use ntop() to convert from struct's binary representation to the dot-notation presentation format
  • ntop stands for network to presentation
Retrieving IP Address from Struct Example
// IPv4
char ip4[INET_ADDRSTRLEN];  // space to hold IPv4 string
struct sockaddr_in sa;

inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);
printf("The IPv4 address is: %s\n", ip4);

// IPv6:
char ip6[INET_ADDRSTRLEN];  // space to hold the UPv6 string
struct sockaddr_in6 sa6;

inet_ntop(AF_INET6, &(sa6.sin6_addr), ip6, INET6_ADDRSTRLEN);
printf("The address is: %s\n", ip6);
  • these functions don't work with domain names, only IP addresses; working with domain names requires the use of getaddrinfo()

 

Jumping from IPv4 to v6

  1. Try using getaddrinfo() to get all the struct sockaddr info instead of packing the structs by hand. Keeps you IP version-agnostic & eliminates many of the next steps.
  2. Anywhere you're hard-coding anything related to IP version, try wrapping it up in a helper function.
  3. Change AF_INET to AF_INET6
  4. Change PF_INET to PF_INET6
  5. Change INADDR_ANY to in6addr_any which are slightly different:
IPv4 -> v6 Example
struct sockaddr_in sa;
struct sockaddr_in6 sa6;
sa.sin_addr.s_addr = INADDR_ANY; // use my IPv4 address
sa6.sin6_addr = in6addr_any;     // use my IPv6 address
  • Also, the value IN6ADDR_ANY_INIT can be used as an initializer when the struct in6_addr is declared, like so: struct in6_addr ia6 = IN6ADDR_ANY_INIT;
  1. Instead of struct sockaddr_in use struct sockaddr_in6, being sure to add “6” to the fields as appropriate (see structs, above). There is no sin6_zero field.
  2. Instead of struct in_addr use struct in6_addr, being sure to add “6” to the fields as appropriate (see structs, above).
  3. Instead of inet_aton() or inet_addr(), use inet_pton().
  4. Instead of inet_ntoa(), use inet_ntop().
  5. Instead of gethostbyname(), use the superior getaddrinfo().
  6. Instead of gethostbyaddr(), use the superior getnameinfo() (although gethostbyaddr() can still work with IPv6).
  7. INADDR_BROADCAST no longer works. Use IPv6 multicast instead.

 

System Calls Related to Sockets and Network Programming

  • the order of calls is important and trips people up; read the man pages
  • the following is in order but missing error-checking code for brevity

getaddrinfo()

  • sets up structs needed later
  • returns pointer to linked-list, res, of results
  • give it 3 params
    • node is host name or IP address to connect to
    • service is port # or name of particular service like ftp, http, etc. (see IANA Port List or /etc/services)
    • hints points to the struct addrinfo you've already filled w/relevant info.
getaddrinfo() Example
#include <sys/types.h>
#include <sys/socket.h>
#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); 

Here's a sample call on a server that wants to listen to its own port 3490; note it doesn't do any listening, just sets up structures needed to do so:

Server Struct Setup
1 int status;
2 struct addrinfo hints;
3 struct addrinfo *servinfo;       // will point to the results
4
5 memset(&hints, 0, sizeof hints); // make sure the struct is empty
6 hints.ai_family = AF_UNSPEC;     // don't care IPv4 or IPv6
7 hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
8 hints.ai_flags = AI_PASSIVE;     // fill in my IP for me
9
10 if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
11     fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
12     exit(1);
13 }
14
15 // servinfo now points to a linked list of 1 or more struct addrinfos
16
17 // ... do everything until you don't need servinfo anymore ....
18
19 freeaddrinfo(servinfo);         // free the linked-list
  • AI_PASSIVE avoids having to hard-code an IP address; assigns address of localhost to the socket structures; could also have put a specific address in as first param to getaddrinfo(), currently set to NULL above

Here is a simple call if you're client wanting to connect to particular server (www.example.net:3490); this doesn't connect, just sets up structures used later:

Client Connecting to Server Example
int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // will point to results

memset(&hints, 0, sizeof hints);  // make sure struct is empty
hints.ai_family = AF_UNSPEC;      // don't care if IPv4 or v6
hints.ai_socktype = SOCK_STREAM;  // TCP stream sockets

// get ready to connect
status = getaddrinfo("www.example.net", "3490", &hints, &servinfo);
// servinfo now points to a linked list of 1 or more struct addrinfos
// etc.
Short Socket Program Example
/*
** showip.c -- show IP addresses for a host given on command line
*/
#include <stdio.h>
#include <string.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[])
{
    struct addrinfo hints, *res, *p;
    int status;
    char ipstr[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr, "usage: showip hostname\n");
        return 1;
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;  // AF_INET or AF_INET66 to force version
    hints.ai_socktype = SOCK_STREAM;

    if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
        return(2);
    }

    printf("IP addresses for %s:\n\n", argv[1]);

    for(p = res; p != NULL; p = p->ai_next) {
        void *addr;
        char *ipver;

        // get the pointer to the address itself,
        // different fields in IPv4 and IPv6:
        if (p->ai_family == AF_INET) { // IPv4
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
            addr = &(ipv4->sin_addr);
            ipver = "IPv4";
        } else { // IPv6
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            addr = &(ipv6->sin6_addr);
            ipver = "IPv6";
        }

        // convert IP to a string and print it:
        inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
        printf(" %s: %s\n", ipver, ipstr);
    }

    freeaddrinfo(res);  // free the linked list

    return 0;
}

Feeding the Socket

int s;
struct addrinfo hints, *res;

// do lookup
// having already filled out "hints" struct
getaddrinfo("www.example.com", "http", &hints, &res);

// should do error-checking on getaddrinfo() and walk
// "res" linked list looking for valid entries instead of 
// assuming 1st one is good

s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

bind()ing the Port

  • commonly done if you'll Listen() for incoming connections on a specific port
  • if you're only doing a connect() as the client and not the server, this is likely unnecessary
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

-sockfd is socket file descriptor returned by socket()

  • my_addr is pointer to struct sockaddr that contains info. about your address, namely port and IP address
  • addrlen is length in bytes of that address
Binding Port Example
struct addrinfo hints, *res;
int sockfd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM;
hints_ai_flags = AI_PASSIVE;  // fill in IP for me

getaddrinfo(NULL, "3490", &hints, &res);

// make socket:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// bind it to port we passed in to getadrinfo()

bind(sockfd, res->ai_addr, res->ai_addrlen);
Binding Port the OLD WAY Example
// The OLD Way
int sockfd;
struct sockaddr_in my_addr;

sockfd = socket(PF_INET, SOCK_STREAM, 0);

my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);  // short, network byte order
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);

bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr);
  • could also assign INADDR_ANY to s_Addr field if desired, to bind to your local IP address
  • ports below 1024 are reserved, use anything between 1025 and 65535 which isn't already being used by another program
  • sometimes you will rerun a server and bind() fails, saying 'Address already in use', can wait until it's clear (minute or so) or add code allowing reuse of port:

Code for Reusing a Port

int yes=1;
// char yes='1'; // Solaris people use this

// lose the pesky "Address already in use" error message
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) {
    perror("setsockopt");
    exit(1);
}
  • sometimes you won't have to call bind(), e.g., when connect()ing to remote machine and don't care what local port is (telnet) - can call connect() which will check to see if socket is unbound and will bind() it to an unused local port if necessary

connect()

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
struct addrinfo hints, *res;
int sockfd;

// first, load up address structs w/getaddrinfo():

memset(&hints, 0, sizeof hints);
hints_ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo("www.example.com", "3490", &hints, &res);

// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// connect!
connect(sockfd, res->ai_addr, res->ai_addrlen);
  • be sure to check return value from connect() which will return -1 on error and set variable errno

listen()

  • listen for incoming connections instead of connecting to remote host
  • 2-step process: listen() and accept()

int listen(int sockfd, int backlog);

-sockfd - socket file descriptor returned from socket() call @ socket creation

  • backlog - # connections allowed on incoming queue, often set to 20, 5 - 10 is probably ok

  • listen() returns -1 and sets errno on error

Listen() Call Sequence:

  1. getaddrinfo();
  2. socket();
  3. bind();
  4. listen();
  5. accept();

accept()

Process:

  1. You are listening on a bound port.
  2. Someone tries to connect, getting into the queue.
  3. You accept their connection, telling them to get the pending connection.
  4. You get returned a new socket (file descriptor) for this single connection. You now have two sockets - one for listening and the other for the actual client connection and communication.
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd() - listen()ing socket (descriptor)
  • addr - usually a pointer to local struct sockaddr_storage, where info. about incoming connection will go, which tells what host is calling from which port
  • addrlen - local integer variable that should be set to sizeof(struct sockaddr_storage) before its address is passed to accept() - accept() won't put more than that many bytes into addr and if it puts fewer, addrlen will be changed to reflect
  • accept() returns -1 and sets errno if there's an error
`accept()` Example
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#define MYPORT "3490" // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold

int main(void)
{
struct sockaddr_storage their_addr;
socklen_t addr_size;
struct addrinfo hints, *res;
int sockfd, new_fd;

// !! don't forget your error checking for these calls !!

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // fill in my IP for me

getaddrinfo(NULL, MYPORT, &hints, &res);

// make a socket, bind it, and listen on it:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
listen(sockfd, BACKLOG);

// now accept an incoming connection:

addr_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);

// ready to communicate on socket descriptor new_fd!
.
.
.
  • use socket descriptor new_fd for all send() and recv() calls
  • you can close the listening sockfd

send() and recv()

  • for communicating over stream or connected datagram sockets
  • for communicating over unconnected datagram sockets, use sendto() and recvfrom() instead

int send(int sockfd, const void *msg, int len, int flags);

  • sockfd - socket descriptor you want to send data to (returned by socket() or accept()
  • msg - pointer to data you want sent
  • len - length of data (msg) in bytes
  • flags - set to zero (see send() man page for more info.)
char *msg = "Beej was here!";
int len, bytes_sent;
.
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
.
.
.
  • send() returns actual # of bytes sent, possibly less than what you sent; it's up to you to send the rest
  • if package is less than 1K, it will likely send the whole thing at once
  • -1 returned on error and errno is set to error number

int recv(int sockfd, void *buf, int len, int flags);

  • recv() returns # bytes actually read into buffer or -1 w/ errno set
  • recv() can return 0 - means remote side closed connection on you

sendto() and recvfrom(), DGRAM

  • for unconnected Datagram sockets
  • requires destination address

int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, socklen_t tolen);

  • similar to above stream socket call to send(), but w/2 addtl. pieces of info.

  • to - pointer to struct sockaddr, likely a sockaddr_in, sockaddr_in6 or sockaddr_storage struct, the latter of which being cast at the last minute

    • whichever it is, it contains dest. IP and port
  • tolen - an int, can be set simply to sizeof *to or sizeof(struct sockaddr_storage)

  • sendto() returns # of bytes actually sent (might be less than # told to send), or -1 on error

  • to get dest. addr. info., you'll likely use getaddrinfo() or recvfrom(), or fill it out by hand

int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

  • from - pointer to local struct sockaddr_storage, which will be filled w/IP address and port of originating machine
  • fromlen - pointer to local int that should be initialized to sizeof *from or sizeof(struct sockaddr_storage), returns length of address actually stored in from
  • recvfrom() returns # of bytes received or -1 on error (w/errno set accordingly)

close() and shoutdown()

  • use close(sockfd) to prevent any more reads or writes; simple method

  • for more control, use shutdown()

    • allows cutting off communication in a certain direction or both ways int shutdown(int sockfd, int how);
how Effect
0 Further receives are disallowed
1 Further sends are disallowed
2 Further sends and receives are disallowed (like chose())
  • shutdown() doesn't actually close file descriptor, just changes its usability; to free a socket descriptor, use close()
  • if using Windows, use closesocket() instead of close()

'getpeername()`

tells you who is at other end of a connected stream socket

#include <sys/socket.h>

`int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);`
  • sockfd is descriptor of connected stream socket, addr is pointer to struct sockaddr (or struct sockaddr_in) which will hold info. about other side of connection

  • addrlen is pointer to an int that should be initialized to sizeof *addr or sizeof(struct sockaddr)

  • returns -1 on error and sets errno accordingly

  • once you have their address, can use inet_ntop(), getnameinfo() or gethostbyaddr() to print or get more info.

gethostname()

  • returns name of computer your program is running on, which can then be used by gethostbyname() to determine IP address of local machine
#include <unistd.h>

int gethostname(char *hostname, size_t size);
  • hostname - pointer to an array of chars that will contain hostname upon function's return
  • size - length in bytes of hostname array

 

A Simple Stream Server

  • client server interaction a la Firefox/Apache or ftp/ftpd
  • process: server waits for connection, accept()s one, fork()s a child process to handle it
Stream Server Example
/*
** server.c -- a stream socket server demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "3490"  // port users connect to
#define BACKLOG 10  // // how many pending connections queue will hold

void sigchld_handler(int s)
{
    // waitpid() might overwrite errno, so we save and restore it:
    int saved_errno = errno;
    
    while (waitpid(-1, NULL, WNOHANG) > 0);
    errno = saved_errno;
}

// get sockaddr, Iv4 or v6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
    struct addrinfo hints, *servinfo, *p;
    struct sockaddr_storage their_addr;  // connector's address information
    socklen_t sin_size;
    struct sigaction sa;
    int yes=1;
    char s[INET6_ADDRSTRLEN];
    int rv;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;  // use my IP

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for (p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("server: socket");
            continue;
        }

        // clear unused socket from previously-closed connection to reuse port
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    freeaddrinfo(servinfo);  // all done with this structure

    if (p == NULL) {
        fprintf(stderr, "server: failed to bind\n");
        exit(1);
    }

    if (listen(sockfd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
    }

    sa.sa_handler = sigchld_handler;  // reap all dead processes
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    printf("server: waiting for connections...\n");

    while (1) {  // main accept() loop
        sin_size = sizeof their_addr;
        new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);

        if (new_fd == -1) {
            perror("accept");
            continue;
        }

        inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr),
        s, sizeof s);
        printf("server: got connection from %s\n", s);

        if (!fork()) { // this is the child process
            close(sockfd);  // child doesn't need the listener

            if (send(new_fd, "Hello, world!\n", 13, 0) == -1)
                perror("send");

            close(new_fd);
            exit(0);
        }

        close(new_fd);  // parent doesn't need this
    }

    return 0;
}

A Simple Stream Client

  • start server, then run client in a different terminal tab
Stream Client Example
/*
** client.c -- a stream socket client demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#define PORT "3490"      // port client will be connecting to
#define MAXDATASIZE 100  // max # bytes we can get at once

// get sockaddr, IPv4 or v6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    int sockfd, numbytes;
    char buf[MAXDATASIZE];
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr, "usage: client hostname\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv == getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all results and connect to first we can
    for (p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("client: socket");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("client: connect");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "client: failed to connect\n");
        return 2;
    }

    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);
    printf("client: connecting to %s\n", s);

    freeaddrinfo(servinfo);  // all done w/this structure

    if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
        perror("recv");
        exit(1);
    }

    buf[numbytes] = '\0';

    printf("client: received '%s'\n",buf);

    close(sockfd);

    return 0;
}

 

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