Simon Amor <[email protected]>
In this tutorial, I will attempt to explain the use and
syntax of some
of the basic UNIX networking functions in C. If you want to know more
about Windows Sockets programming, I'm afraid the WinSock resources at
StarDust have disappeared now - any pointers to similar pages would be
appreciated!
You might also want to check out the Internet programming crash course
There are some example programs which I will explain how to write from
the beginning. If you would like to know more about a function, you might
have on-line manual pages (use 'man function') or your system administrators
may be able to provide manuals.
You can view or download the source code but I
cannot guarantee that it will work on all systems so if you find
any code that doesn't work, please email me with details of your system (Operating System, Architecture etc)
and what doesn't work and I'll see if I can fix it.
I would also appreciate email telling me if it DOES work on your system
(or if you managed to modify parts and get it working), if you could email
the alterations to me, I can then incorporate your modifications into the
source code.
Some platforms that have been tested:
With some versions of UNIX, the example programs will not link, complaining about things such as undefined symbols bind, accept, listen and socket.
Such errors will probably look like this:
Undefined first referenced symbol in file socket /var/tmp/ccLkjMcu.o accept /var/tmp/ccLkjMcu.o bind /var/tmp/ccLkjMcu.o listen /var/tmp/ccLkjMcu.o ld: fatal: Symbol referencing errors. No output written to a.out
This generally indicates that you should link in an additional library by adding -lsocket to the cc line.
For example: cc -o netapp netapp.c -lsocket
This tutorial assumes you know how to program in C.
If you find this tutorial of any interest whatsoever, please email
[email protected]
and let me know. It's nowhere near complete as yet, but mail
me anyway even if it's only to say "Hey! get that tutorial finished" :-)
Back to contents
#include <sys/types.h> #include <sys/socket.h>
int socket(int af, int type, int protocol);
int socket_desc; socket_desc=socket(AF_INET,SOCK_STREAM,0); if (socket_desc==-1) perror("Create socket");
socket() returns a socket
descriptor which can be used in other network commands. This will create
a socket which uses DARPA Internet addresses, and the method of connection
is a byte stream
which is similar to a pipe. An alternative to byte streams is a datagram
but this tutorial does not cover them.
With servers, the first socket created is often known as a " master
socket". Before the socket can send or receive data, it must be
connected to another socket. If acting as a master
socket, it must be bound to a port
number so that clients can know where to "find" the socket and
connect to it.
If successful, socket() returns a valid socket descriptor;
otherwise
it returns -1 and sets errno to indicate the error. perror() or
strerror() can be used to turn the errno value into a human readable
string.
To set up a master socket, you need to bind the socket descriptor to a in many ways. To create a socket, you can use the socket() function as described above in Creating a socket.
Includes:
#include <sys/socket.h> #include <netinet/in.h>
Syntax:
int bind(int s, struct sockaddr *addr, int addrlen);
C source:
struct sockaddr_in address; /* type of socket created in socket() */ address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; /* 7000 is the port to use for connections */ address.sin_port = htons(7000); /* bind the socket to the port specified above */ bind(socket_desc,(struct sockaddr *)&address,sizeof(address));
If successful, bind() returns 0; otherwise it returns -1 and sets errno
to indicate the error.
The port specified in the source code above (port 7000) is where the
server can be connected to. To test this, compile the program `sockbind'
from the source code
directory and run it. While it is running, type:
telnet localhost 7000
and you should get
Trying...
for a few seconds and then
telnet: Unable to connect to remote host: Connection refused
as the server program finishes. This indicates the server was ok. If the connection is refused immediately, there is probably a problem with the server.
#include <sys/socket.h>
int listen(int s, int backlog);
listen(socket_desc,3);
To actually tell the server to accept a connection, you have to use the function accept()
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, int *addrlen);
int addrlen; struct sockaddr_in address; addrlen = sizeof(struct sockaddr_in); new_socket = accept(socket_desc, (struct sockaddr *)&address, &addrlen); if (new_socket<0) perror("Accept connection");
There is some example source code in the directory, the program to compile is called `accept' While it is running, type:
telnet localhost 7000
and you should get
Trying... Connected to localhost. Escape character is '^]'.
and then 10 seconds later, it should close the connection. Once again, if the connection is refused immediately, there is probably a problem with the server.
Probably one of the easiest things to do with a socket, is close it. This is done using close()
#include <unistd.h>
int close(int sockdes);
close(socket_desc);
#include <sys/socket.h>
int send(int s, const void *msg, int len, int flags);
char *message="This is a message to send\n\r"; send(socket_desc,message,strlen(message),0);
This is a message and this is the second line and the third.
This is a message and this is the second line and the third.
send() is used to transmit a message to another socket and can be used
only when the socket is in a connected state. The socket descriptor that
specifies the socket on which the message will be sent is 's' in the syntax
above. 'msg' points to the buffer containing the message and the length
of the message is given by len, in bytes.
The supported values for flags are zero, or MSG_OOB
(to send out-of-band data) - a write() call made to a socket behaves in
exactly the same way as send() with flags set to zero.
Upon successful completion, send() returns the number of bytes sent.
Otherwise, it returns -1 and sets errno to indicate a locally-detected
error. The `accept' program is modified to send a welcome message to the
connection before it closes the socket. To see how this is done, have a
look at the source
code for the program `send'.
#include <sys/socket.h>
int recv(int s, void *msg, int len, int flags);
int bufsize=1024; /* a 1K buffer */ char *buffer=malloc(bufsize); recv(socket_desc,buffer,bufsize,0);
The flags parameter can be set to MSG_PEEK,
MSG_OOB,
both, or zero. If it is set to MSG_PEEK, any data returned to the user
still is treated as if it had not been read, i.e the next recv() re-reads
the same data.
A read() call made to a socket behaves in exactly the same way as a
recv() with flags set to zero.
If successful, recv() returns the number of bytes received, otherwise,
it returns -1 and sets errno to indicate the error. recv() returns 0 if
the socket is blocking and the connection to the remote node failed.
To allow certain socket operations requires manipulation of socket options using setsockopt()
#include <sys/socket.h>
int setsockopt(int s, int level, int optname, const void *optval, int optlen);
#define TRUE 1 #define FALSE 0 int socket_desc; /* master socket returned by socket() */ int opt=TRUE; /* option is to be on/TRUE or off/FALSE */ setsockopt(socket_desc,SOL_SOCKET,SO_REUSEADDR, (char *)&opt,sizeof(opt));
SOL_SOCKET specifies the option is a `socket level' option, these are
defined in <sys/socket.h>
The socket is identified by the socket
descriptor s.
The option SO_REUSEADDR
is only valid for AF_INET sockets.
There are two kinds of options: boolean and non-boolean. Boolean options
are either set or not set and also can use optval and optlen to pass information.
Non-boolean options always use optval and optlen to pass information.
To enable a socket to be read without waiting if there is no input, the socket must be set non-blocking using the following snippet of code.
fcntl(mastersocket, F_SETFL, FNDELAY);
or
fcntl(mastersocket, F_SETFL, O_NONBLOCK);
If the above returns a non-zero result, the operation failed and errno should be set to an appropriate value.
Using select to monitor a number of sockets (or just one) is fairly straightforward and is shown in the code below. Please note this is incomplete code as the creation of the master socket is not included (see previous details).
fd_set readfds; /* create a list of sockets to check for activity */ FD_ZERO(&readfds); /* specify mastersocket - ie listen for new connections */ FD_SET(mastersocket, &readfds); /* wait for connection, forever if have to */ new_conns=select(max_conns, readfds, NULL, NULL, NULL); if ((new_conns<0) && (errno!=EINTR)) { /* there was an error with select() */ } if (FD_ISSET(mastersocket,&readfds)) { /* Open the new socket */ }
Of course, the above will only wait for activity on the master socket. What
you need to do is run it inside a loop which repeats until the server is
shut down.
Any newly created sockets will need to be monitored as well (unless
the connections accepted are closed after outputing a message).
For an example of this, see the sample program multi.c which accepts up to three connections and relays data from one socket to the others. It's almost a very basic chat server.
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
struct hostent *hent; hent = gethostbyname("www.foobar.net");
struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ }
To establish a connection to another socket (similar to telnet), use the function connect().
#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, struct sockaddr *serv_addr, int addrlen );
Create the socket using socket(), convert the hostname to an IP address using gethostbyname() and then issue the connect() call passing the relevant structures containing the IP address and port to connect to.
struct hostent *he; struct sockaddr_in server; int sockfd; /* resolve localhost to an IP (should be 127.0.0.1) */ if ((he = gethostbyname("localhost")) == NULL) { puts("error resolving hostname.."); exit(1); } /* * copy the network address part of the structure to the * sockaddr_in structure which is passed to connect() */ memcpy(&server.sin_addr, he->h_addr_list[0], he->h_length); server.sin_family = AF_INET; server.sin_port = htons(7000); /* connect */ if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) { puts("error connecting.."); exit(1); }
Using connect() ... more to come when/if I get around to it.
Changes:
23rd June 2004 : Added information about getting it to work on Mac
OSX 10.3 (due to a query regarding this from Nicolas). Tweaked the
source to netlib slightly - main bugfix was to correct filenames in the
Makefile.
15th November 2003 : Removed link to StarDust WinSock resources as this site has disappeared.
20th February 2003 : In Creating a socket, fixed comparison of
socket() return value to -1 to indicate error instead of 0 (thanks to
Vince for pointing this out).
16th September 2002 : Converted to be XHTML 1.0 Strict compliant and use CSS properly. Also added link to glossary page.
14th September 2002 : Added url to Internet programming crash course (thanks to Kelly Mandrake).
20th March 2001 : Improved navigation
2nd December 2000 : Updated locations and email addresses
29th May 1999 : Added information about -lsocket.
4th May 1998 : Added an extra include for socket() as per the man page.
13th April 1998 : Added links to downloadable source for netlib.
18th March 1998 : Added a link to downloadable archive for offline reading.
9th October 1997 : Corrected he->h_addr[0] to he->h_addr_list[0] under the connect section