Version: 2
Author: Jacob Rhoden
Released Date: January 1998.
This is a basic introduction into using sockets in C. Once you have completed this tutorial, you can learn more advanced techniques in the following non-blocking sockets tutorial
Good luck and if you have any questions, queries, or god forbid you have found any mistakes in this primer please don't hesitate to email me.
You are free to post my tutorial on any WWW or FTP site on the condition that it is not modified in any way and I would appreciate it if you tell me so that I can mention your link it in this document.
A socket can be thought of like a file handle or a byte stream. If you want to open a file from a disk you declare a file handle, if you want to connect to a server you declare a socket and set it up.
A socket has many capabilities, protocols if you like, that it can use to communicate, here we will start off with the most common, the TCP protocol.
This command checks a socket for incoming data or to see if data is ready to be written to the socket. The syntax of the select command is as follows:
s = socket(AF_INET, SOCK_STREAM, 0);
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(hstname);
if(sin.sin_addr.s_addr == INADDR_NONE) {
connect(s, (struct sockaddr *)&sin, sizeof(sin))
The flags in the select command are used to check and get data about sockets, as follows:
FD_ZERO(s, &write_flags) sets all associated flags
in the socket to 0
FD_SET(s, &write_flags) used to set a socket for checking
FD_CLR (s, &write_flags) used to clear a socket from being checked
FD_ISSET(s, &write_flags) used to query as to if the socket is ready
for reading or writing.
How they are used will be demonstrated by example in the example code
You should already know how to use the following commands, they do however act slightly different when using non-blocking sockets.
write(s,buffer,sizeof(buffer)) send the text in "buffer" read(s,buffer,sizeof(buffer)) read available data into "buffer"
When a socket is used in non-blocking mode, these two commands will always return as soon as you call them, ie, if there is nothing for the command to do it will not block and wait for data, instead it will return with an error code. Now we are ready to look at some real code.
Firstly lets declare the variables that will be used.
fd_set read_flags,write_flags; // the flag sets to be used struct timeval waitd; // the max wait time for an event char buffer[8196]; // input holding buffer int stat; // holds return value for select();
Most of our programs time will be spent calling select over and over again (which uses up lots of CPU time) until data is ready to be read or written. That is the purpose of the waitd variable. It determines how long the select() command will wait for data before returning. Following our learn by example style, look carefully at the following code:
// Insert Code to create a socket
while(1) // put program in an infinite loop of reading and writing data
{
}
loop over and over and over really fast and use up to 99% of your CPU time on a *nix machine, which your sys-admin probably wont like.
Finally to put into practice all we have learn't we will write a simple finger client. Of course using non-blocking sockets is a bit of an overkill for a finger client but its for examples sake only. For more realistic examples see section 7.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
// this routine simply converts the address into an
// internet ip
unsigned long name_resolve(char *host_name)
{
struct in_addr addr;
struct hostent *host_ent;
if((addr.s_addr=inet_addr(host_name))==(unsigned)-1) {
host_ent=gethostbyname(host_name);
if(host_ent==NULL) return(-1);
memcpy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);
}
return (addr.s_addr);
}
// The connect routine including the command to set
// the socket non-blocking.
int doconnect(char *address, int port)
{
int x,s;
struct sockaddr_in sin;
s=socket(AF_INET, SOCK_STREAM, 0);
x=fcntl(s,F_GETFL,0); // Get socket flags
fcntl(s,F_SETFL,x | O_NONBLOCK); // Add non-blocking flag
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family=AF_INET;
sin.sin_port=htons(port);
sin.sin_addr.s_addr=name_resolve(address);
if(sin.sin_addr.s_addr==NULL) return(-1);
printf("ip: %s\n",inet_ntoa(sin.sin_addr));
x=connect(s, (struct sockaddr *)&sin, sizeof(sin));
if(x<0) return(-1);
return(s);
}
int main (void)
{
fd_set read_flags,write_flags; // you know what these are
struct timeval waitd;
int thefd; // The socket
char outbuff[512]; // Buffer to hold outgoing data
char inbuff[512]; // Buffer to read incoming data into
int err; // holds return values
memset(&outbuff,0,sizeof(outbuff)); // memset used for portability
thefd=doconnect("203.1.1.1",79); // Connect to the finger port
if(thefd==-1) {
printf("Could not connect to finger server\n");
exit(0);
}
strcat(outbuff,"jarjam\n"); //Add the string jarjam to the output
//buffer
while(1) {
waitd.tv_sec = 1; // Make select wait up to 1 second for data
waitd.tv_usec = 0; // and 0 milliseconds.
FD_ZERO(&read_flags); // Zero the flags ready for using
FD_ZERO(&write_flags);
FD_SET(thefd, &read_flags);
if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags);
err=select(thefd+1, &read_flags,&write_flags,
(fd_set*)0,&waitd);
if(err < 0) continue;
if(FD_ISSET(thefd, &read_flags)) { //Socket ready for reading
FD_CLR(thefd, &read_flags);
memset(&inbuff,0,sizeof(inbuff));
if (read(thefd, inbuff, sizeof(inbuff)-1) <= 0) {
close(thefd);
break;
}
else printf("%s",inbuff);
}
if(FD_ISSET(thefd, &write_flags)) { //Socket ready for writing
FD_CLR(thefd, &write_flags);
write(thefd,outbuff,strlen(outbuff));
memset(&outbuff,0,sizeof(outbuff));
}
// now the loop repeats over again
}
}
This code compiles and was tested using FreeBSD2.2.6
The extra example code has been removed from this file and placed into a ZIP file for neatness. To get a better understanding of what you have learnt here it would be a good idea to check out more complicated and technically more correct code available at:
Example Source: http://rhoden.id.au/doc/sock.zip
To learn how to use more advanced sockets to write better programs move onto the next page on Non-Blocking sockets. Non-Blocking sockets are a way of being able to read and write data while still allowing your program to do other things at the same time.
Also I recommend you check out the man pages on your system because a lot of online references are just repeats of what is already available on your system.
No I'm not some highly capable programmer but I do have a fair bit of background experience in programming. I am currently a 1st year student in the Latrobe University of Melbourne, Australia. Note that non-blocking sockets is a topic that usually isn't covered in any 1st year course, so I will be learning all this again next year (:
URL: http://rhoden.id.au/
Email: jrhoden@unimelb.edu.au
Last Updated: 18th of August, 1998. By Jacob Rhoden (c)1998
Dominoid Productions.