Sockets in C
It has been a while since I have done anything with sockets in C, so I figured I should refresh my memory. Beej already has a great guide to network programming, so I’ll try to make a very simple and small introduction. If you want to know more, you should refer to his guide.
To be able to send (or receive) something through sockets, you need perform at least two steps:
- Create a socket with socket(3).
- Attach an address to it by:
- binding (bind(3)) a local address to it (if you are the server),
- or connecting (connect(3)) it to a destination address (usually as a client).
In addition to these, you have to perform other steps if you are the server:
- Allow the socket to receive incoming connections by using listen(3)
- and finally wait for a new client with accept(3).
After these are successfully completed, you can use recv(3) and send(3) to interact with the remote end. The steps above are valid for TCP sockets; with UDP you don’t have to do all of them since it uses connectionless communication. After the communication is done, you should close the socket using close(3).
Let’s start by creating a socket for a client application. The prototype is:
1
|
|
where domain
is basically AF_INET
for IPv4 connections and AF_INET6
for
IPv6, type
is SOCK_STREAM
for TCP connections and SOCK_DGRAM
for UDP. By
leaving protocol
to 0
, we say that we support only one protocol family and
protocol is then deduced from the domain
. Naturally there are more options
than these and you can read more from the man-pages. The return value is a file
descriptor to newly created socket (in a successful case). So to create an IPv4
socket for TCP communication, we could do:
1 2 3 4 5 |
|
Now we need to assign an address to the socket by using bind
or connect
,
which are pretty similar to each other:
1 2 3 4 5 |
|
Here the sockfd
is the file descriptor returned by the socket
, addr
is a
pointer to an address in the network format and addrlen
is its length. In both
cases return value is zero on success and -1
on error.
You would basically have your IP address as a string, e.g. "127.0.0.1"
which
is in printable format. To get a network format from that, you would most likely
use function like inet_pton(3):
1
|
|
With inet_pton
you transform printable (pton) address to network address
format (pton), for example like this:
1 2 3 4 5 6 |
|
Then after setting the port (using 6000
as an example) and the family (IPv4),
1 2 |
|
you could connect the previously created sock_fd
to the destination address:
1 2 3 4 5 6 |
|
Now that the socket is created and connected to the destination address, we can send something (with send(3)):
1
|
|
Here socket
is the sock_fd
that we have connected, buffer
contains the
message to be sent and length
is its length in bytes. flags
argument is not
interesting for us, so we can leave it as 0
. send
will return the number of
bytes sent or -1
on failure. But basically it will either send all the bytes
or fail with -1
, so you can just check that the return value is not negative.
1 2 3 4 5 6 7 8 |
|
To create the server application, we will create the socket same way as before
but now we will use bind
instead of connect
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
We use constant INADDR_ANY
as an address to say that we want to bind to all
interfaces on the machine (i.e. not restricting us only to “127.0.0.1” but to
allow packets to arrive in any address assigned to us). Next we have to inform
the operating system that we are interested in incoming connections by using
listen
:
1
|
|
Here socket
is the socket file descriptor that we have bound and backlog
is
basically means how many clients there can be queued for this socket. We are
expecting only one client now so let’s just use 1
as a backlog
. listen
returns 0
on success and -1
on failure, so remember to check the return
value also here.
1 2 3 4 5 6 |
|
The actual waiting for a new client happens in accept
:
1 2 |
|
Here socket
is the file descriptor that has been bound and listened, address
is a structure where the address of connected client is stored and address_len
is its length. Last two arguments are optional, so if you are not interested in
client’s address, you may just give NULL
to both. Return value is a socket
file descriptor or -1
on failure. Notice though that accept
will block until
a client arrives, meaning that this function call will not return until someone
connects to our server.
1 2 3 4 5 6 |
|
Now we can use client_sock
for sending and receiving. Since our client program
sends a message to the server, we might as well use recv
here and print the
received bytes:
1
|
|
recv
function prototype is basically the same as with send
. flags
can be
omitted also here for now. Only difference is the return value; recv
will
return the length of the message received, -1
on failure or 0
if client has
properly closed the socket. So if zero is returned, client didn’t send anything.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
And that’s all there is to get a simple client/server application in C. Remember to check the return values and print errors with perror(3) if you run in to any problems. As always, you can download the complete files for the client and the server.