Socket programming

Socket programming is amazingly poorly understood by even professional programmers. Given how critical the ability to use IP-based protocols in today's computing environment is, I'm surprised how few people actually use it. The folks who do it usually cop out by using Visual Basic, which makes socket programming relatively easy but requires a mess of support DLLs and is non-portable outside of Windows. To this end, here's the basics of how to work with sockets in Windows C programming.

First off, a program which simply opens, then closes a TCP socket. This program will try to connect on TCP port 80, but you can change that by simply changing the number 80 where it appears in the code. On the line below that, you'll also need to change the "x.x.x.x" to be the IP address of the system you want to connect to.

#include <winsock.h>

/* That's if you are using Windows sockets, of course. If you're
compiling under Unix/Linux, use these lines instead (yes, both of them):

#include <netinet/in.h>
#include <sys/socket.h>

*/

main()

{

/* (Windows only): You have to do WSAStartup before you can use
sockets. If you don't run a WSAStartup first, no socket functions will
work. WSAStartup needs to have a WSADATA type variable, which
we'll call "ws" in this case. */

WSADATA ws;
WSAStartup(0x0101,&ws);

/* The "0x0101" specifies the Winsock version number the program
wants. */

int tcp_socket;

struct sockaddr_in peer;

char recvbuffer[1024];

/* tcp_socket will be the socket, and peer will be the name of the peer
(host) to connect to. peer is being made as a sockaddr_in type of struct,
which is specifically used for the purpose of holding TCP/IP addresses.
Note that it is more common to name these structs "sin", for "socket in".
recvbuffer is a buffer to store received data in, in case you want to use
recv() to receive from the host. */

peer.sin_family = AF_INET;
peer.sin_port = htons(80);
peer.sin_addr.s_addr = inet_addr("x.x.x.x");

/* The above lines prepare the struct "peer" for the connection. In this
example, port 80 is used, which is the HTTP (Web) port. However, you
can change it to whatever port you want the program to connect to. The
last line specifies the host's IP address. Alternatively, if you want to use
the decimal version of the host's IP address, use this syntax:

peer.sin_addr.s_addr = htonl(x);

Where "x" is the decimal conversion of the IP address.

*/

tcp_socket = socket(AF_INET, SOCK_STREAM, 0);

/*
The above is for normal TCP connections. If you want to use raw
connections instead, use the following line:

tcp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

SOCK_STREAM is for TCP. If you want to use UDP instead, use
SOCK_DGRAM.
*/

/* if (tcp_socket < 0)
   {
       printf("ERROR: Couldn't open socket!\n");
       exit(1);
   } */

/* This "if" statement checks that the socket was made properly, and if
not, it quits with an error message. (Note, however, that Windows
doesn't like printf commands, so that code is commented out here.) If
everything checks out, the socket has now been created. It has not yet
connected to a host, however. You use connect() to do that, like this: */

connect(tcp_socket, (struct sockaddr*)&peer, sizeof(peer));

/* To use connect(), you must already have the name of the peer to
connect to in a struct. In this example, as you can see, the struct is called
peer. */

/* Once you have connected, use send() to send data to the host, and
recv() to receive data from it, like this: */

// send(tcp_socket, "This data will be sent.",23,0);
// The number 23 is the size of the data to be sent... Adjust as necessary!
// recv(tcp_socket, recvbuffer, sizeof(recvbuffer),0);
// This receives some data, then dumps it into recvbuffer.

/* Or, you can use sendto() and recvfrom() to start sending/receiving on
this socket without connecting to anything. */

/* Before ending the program, you should close the socket using close(),
as shown below. */

close(tcp_socket);

return(0);

}

The next logical program is a TCP server. The program below will open your TCP port 80, wait for a connection, and then close.

#include <winsock.h>

main()

{

WSADATA ws;
WSAStartup(0x0101,&ws);

int tcp_socket;
struct sockaddr_in peer;
int peerlen;

peer.sin_family = AF_INET;
peer.sin_port = htons(80);
//peer.sin_addr.s_addr = inet_addr("127.0.0.1");
//The above line would work, except it would only accept connections from
//127.0.0.1. Instead, let's use INADDR_ANY, which states that the program
//should accept connections from any address.
peer.sin_addr.s_addr = htonl(INADDR_ANY);

tcp_socket = socket(AF_INET, SOCK_STREAM, 0);

bind(tcp_socket,(struct sockaddr *)&peer,sizeof(peer));
listen(tcp_socket,3);
peerlen=sizeof(peer);
accept(tcp_socket,(struct sockaddr *)&peer, &peerlen);

close(tcp_socket);

return(0);

}

From TCP, let's move on to UDP. UDP is an unreliable kludge because it doesn't verify that data was sent correctly, and as such isn't used much for most things, but it is used for certain speed-critical applications like media streaming, and UDP is almost always used for DNS, so UDP is good to be able to use. The following program will send the string "Have a nice day!" to UDP port 80 of a specific system. (Again, change the "x.x.x.x" to be the IP address of where you want the string to be sent to.)

#include <winsock.h>

main()

{

WSADATA ws;
WSAStartup(0x0101,&ws);

SOCKET udp_socket;
struct sockaddr_in peer;
int peerlen;

peer.sin_family = AF_INET;
peer.sin_port = htons(80);
peer.sin_addr.s_addr = inet_addr("x.x.x.x");

udp_socket = socket(AF_INET, SOCK_DGRAM, 0);

sendto(udp_socket, "Have a nice day!\n", 17, 0, (struct sockaddr *)&peer, sizeof(peer));

close(udp_socket);

return(0);

}

Finally, here's a UDP server program. This program will listen on UDP port 80, and display any data that gets sent to that port. UDP works a little differently from TCP. Since UDP does not use connections, you cannot simply close the program when the connection closes, because there IS no connection. So instead, this program will go into an infinite loop and keep polling the UDP port for data. When data gets sent to the port, this program will print out whatever data it receives. To exit the program, you have to forcibly kill it, since it will never close itself.

#include <winsock.h>
#include <stdio.h> //for printf

main()

{

WSADATA ws;
WSAStartup(0x0101,&ws);

SOCKET udp_socket;
struct sockaddr_in peer;
int peerlen;

char recvbuffer[20];
int retval;

peer.sin_family = AF_INET;
peer.sin_port = htons(80);
peer.sin_addr.s_addr = htonl(INADDR_ANY);

udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
// SOCK_STREAM is for TCP, SOCK_DGRAM is for UDP.

bind(udp_socket,(struct sockaddr *)&peer,sizeof(peer));

peerlen=sizeof(peer);

//listen() is for TCP sockets, not for UDP, so we won't use listen().
//Similarly, accept() is for use after listen(). Since we don't use listen(),
//we won't use accept() either.

//Now we enter an infinite loop where we keep trying to recvfrom.
while(1) {

  retval = recvfrom(udp_socket, recvbuffer, sizeof(recvbuffer), 0, (struct sockaddr *)&peer, &peerlen);

  //recvfrom returns -1 if it didn't receive anything.
  //If retval is NOT -1, then the recvfrom must have worked, so let's display
  //whatever UDP data was sent.
  if (retval != -1) {printf("%s", recvbuffer);}

  }

return 0;

}

select(): How to make programs that both send and receive

The above set of programs shows the basic syntax for sending and receiving data with TCP and UDP. In the real world, however, most Internet software needs to be able to do both. Many types of programs do plenty of both sending and receiving data at the same time. Unfortunately, writing a program to do this isn't quite as simple as just combining the two techniques. The main problem is accept() (for TCP) and recvfrom() (for UDP). By default, both of these functions are blocking, meaning that if there is currently no data waiting for them, they will halt all further program execution until there is. If all your program does is wait for incoming data, that might not be a problem, but in a real-world program that often must be doing several other things, it's not viable to simply use these functions to poll for incoming data.

Although there is apparently a way to define a socket as non-blocking, the proper way to deal with this problem is to use yet another function: select(). This is a function whose purpose is to simply monitor sockets, and note three things: Whether the socket can be read from, whether the socket can be written to, and whether errors exist with that socket. For our current purposes, we're only interested in the first capability.

We'll go over the syntax for select() in a moment, but here's a line that you can use pretty much verbatim for it:

select(1, &fd, NULL, NULL, &tv);

The first parameter is ignored and included only for backward-compatibility, so you can set it to anything you want. I set it to 1 just for fun, but you can set it to any integer. The second argument is a pointer to a "file descriptor" which, despite the name, actually points to a socket (or a set of sockets). The third and fourth parameters are for similar pointers to sockets to be checked for writability and errors, but since we're not using that functionality here, this can be set to NULL. The final argument is a pointer to a time value, a separate structure which defines how long select() is supposed to wait. Our select() command will wait a certain amount of time before it gives up on data being available on a socket, and we can control how long it will wait with this time value.

The following is a relatively simple program to demonstrate how to use select() on a TCP socket. It opens up TCP socket 80 and waits for incoming data. Each iteration of select() will wait 3 seconds. If no data arrives during that time, select() will abort so that program flow can continue. In this particular program, not much happens in between, but in a real application, the program would be free to do other things so it wouldn't be stuck waiting for incoming data.

Note that my compiler, for whatever reason, doesn't recognize the timeval type; you're supposed to be able to create your time value with a line like "struct timeval tv;", but it doesn't work with my compiler, so I simply create my own definition of a time value at the start of the program and call it "timval"; like the standard time value, it simply consists of two numerical values: The first defines a number of seconds, the second defines a number of milliseconds.

#include <winsock.h>
#include <stdio.h>

typedef struct {long tv_sec; long tv_usec;} timval;

main()

{

WSADATA ws;
WSAStartup(0x0101,&ws);

SOCKET tcp_socket;
struct sockaddr_in peer;
int peerlen;

fd_set fd; //create a file descriptor set called fd
FD_ZERO(&fd); //zero out our fd (otherwise this program tends to crash)
timval tv; //create a time value object
tv.tv_sec = 3; //set our timer value to wait for 3 seconds
tv.tv_usec = 0;
//You can also set tv.tv_usec to something to add some microseconds (yes,
//microseconds; millionths of a second, not thousandths of a second) for an
//extra level of precision, but we don't need to be that precise, so we'll
//just leave this at 0 so our timer value indicates exactly 3 seconds. (We
//only set it to 0 so that it gets some value; otherwise it remains
//uninitialized and will contain whatever random value it has on startup.)

peer.sin_family = AF_INET;
peer.sin_port = htons(80);
peer.sin_addr.s_addr = htonl(INADDR_ANY);

tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
bind(tcp_socket,(struct sockaddr *)&peer,sizeof(peer));
listen(tcp_socket,3);

FD_SET(tcp_socket, &fd); //set our fd set so that it points to our UDP socket
//Note that after every time you use select(), YOU MUST RE-RUN fd_set to set
//the file descriptor if you want to use select() the same way again.

//Oddly enough, it seems like you need to call select() once to set the fd
//value; otherwise it will think that the socket is ready to receive, even if
//it isn't.
//To make the select() go fast, we'll set our time value to zero, then re-set
//when select() is done.
tv.tv_sec=0;
select(1, &fd, NULL, NULL, &tv);
tv.tv_sec=3;

while (!FD_ISSET(tcp_socket, &fd)) //if socket hasn't received any data yet...
        {
        FD_SET(tcp_socket, &fd); //re-set the fd so select() will work
        select(1, &fd, NULL, NULL, &tv);
        if (!FD_ISSET(tcp_socket, &fd)) {printf("Still waiting for data...\n");}
        }

printf("Got data!");

peerlen=sizeof(peer);
accept(tcp_socket,(struct sockaddr *)&peer, &peerlen);

close(tcp_socket);

return(0);

}

setsockopt(): An alternative to raw sockets

There's no denying the power of raw sockets. They allow you to easily and manually set any parameter in an IP or TCP header. Great stuff. There are only a couple of problems with them. First of all, they are somewhat more complicated to use, and can clutter up your code with a lot of low-level header settings that don't need to be (and perhaps shouldn't be) monkeyed with. The other problem is perhaps more serious: Some implementations simply don't support them. In particular, Windows XP was involved in a techie controvery over whether it would (or should) allow raw sockets. While the correct solution to this problem is to not use Windows XP, it's perhaps fair to say that there are times when you may want to set some IP or TCP header options without resorting to raw sockets. Thankfully, there is a potential alternative: setsockopt().

As you might have already guessed, this is simply a function whose name is short for "set socket options", and which allows you to alter several header fields. By way of example, here's a short program that sets the TTL (time to live, an IP header field that defines how many "hops" a packet should be able to survive before it's considered unroutable) to 50 on a UDP transmission. Note that you'll need to set "x.x.x.x" to a real IP address.

#include <winsock.h>

main()

{

WSADATA ws;
WSAStartup(0x0101,&ws);

SOCKET udp_socket;
struct sockaddr_in peer;
int ttl_value=50;

peer.sin_family = AF_INET;
peer.sin_port = htons(80);
peer.sin_addr.s_addr = inet_addr("x.x.x.x");

udp_socket = socket(AF_INET, SOCK_DGRAM, 0);

setsockopt(udp_socket, IPPROTO_IP, IP_TTL, &ttl_value, sizeof(ttl_value) );

sendto(udp_socket, "Have a nice day!\n", 17, 0, (struct sockaddr *)&peer, sizeof(peer));

close(udp_socket);

return(0);

}

Back to the main page


This page hosted by Get your own Free Homepage
1