C Socket Programming Part 2
General
Since the first years i was programming, i was always keen to applications based on communication. Socket programming allows us to communicate with other computers / programs through networking techniques. There are 2 types of socket protocol’s that are mostly used. Those are TCP ( Stream Protocol, or SOCK_STREAM ) and UDP ( Datagram Protocol, or SOCK_DGRAM )
[table id=1 /]
For the above reasons explained, TCP/IP is the protocol used vastly on the internet, from websites, to applications. So, lets take at the steps we should make, in order to send some data over TCP to a remote PC.
- First of all we should have all the info required about the remote system. That includes an IP address represented in a way that our program understands, weather its IPv4 or IPv6 and if it is a hostname, do a DNS Lookup to see the IP addresses it resolves to.
- Initiate our socket, and feed it the data we gathered from step one.
- ( optional ) Bind our socket to a local port, if we are making a server application.
- Connect the socket to the remote system.
- Send any data we want.
- And finally if needed, receive back other data.
Lets see how we will accomplish each task.
getaddrinfo() – magical function!!
This function will help us gather all the information ( and much more ) described in step 1 for our socket to be ready for work!
the prototype is :
[c light="true"]
int getaddrinfo(const char *node,
const char *service,
const struct addrinfo *hints,
struct addrinfo **servinfo);
[/c]
Lets examine its’ parameters..
- char *node – a string representing the hostname or IP that we need the information of. Pretty easy eh?
- char *service – a string that can either represent a port ( eg “80″ ), or the name of a service ( eg “http” ), which are both equivalent.
- struct addrinfo *hints – points to an addrinfo struct which we have already filled out with various infrmation. We will get soon on that.
- struct addrinfo **servinfo – a pointer-to-pointer addrinfo struct, for which the function will allocate the required memory, and fill in the information we need to know about the *node.
Here is an example call to getaddrinfo() :
[c]
#include <string.h> //memset()
#include <sys/types.h> //struct definitions
#include <sys/socket.h> //getaddrinfo()
#include <netdb.h> //AI_PASSIVE definition.
int main() {
int status;
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results
memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // don’t care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
if ((status = getaddrinfo("www.example.com", "80", &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
return 1;
}
// servinfo now points to a linked list of 1 or more struct addrinfos
// … do everything until you don’t need servinfo anymore ….
freeaddrinfo(servinfo); // free the linked-list
return 0;
}
[/c]
So, now that we have the information needed about example.com, we want to create a socket, and later on, connect to it.
Gief my socket!
As you may know, or have heard, in Unix, everything is files! Well, before you say that the word “everything” is too-much, and start arguing, i mean that everything that is about I/O ( files, devices etc ) is a file descriptor. I guess you are familiar with file I/O in C. Well, guess what! It is the same with sockets! Lets take a peek at the prototype of the function which initiates a socket.
[c light="true"]int socket(int domain, int type, int protocol); [/c]
- int domain – PF_INET for IPv4, PF_INET6 for IPv6
- int type – SOCK_STREAM for TCP, SOCK_DGRAM for UDP.
- int protocol – set to zero, so the function will choose the right protocol, according to the other parameters.
But before running to your text editor and calling the function, let me make you happy!! All those parameters are provided from the addrinfo struct which getaddrinfo() filled out for us!! See why its a magical function?
So, using those resources, the socket() call is like this :
[c light="true"]
int s;
s = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
[/c]
I need a port!
The next step, is to bind our socket ( if needed ) to a local port and then listen on it for incoming connections.
[c light="true"]int bind(int sockfd, struct sockaddr *my_addr, int addrlen);[/c]
Return Value
bind() will return -1 on error, and set errno accordingly.
to understand how it works, here is an example :
[c]#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int main() {
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, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
getaddrinfo(NULL, "3490", &hints, &res);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// bind it to the port we passed in to getaddrinfo():
bind(sockfd, res->ai_addr, res->ai_addrlen);
close(socket);
freeaddrinfo(res);
return 0;
}
[/c]
By passing AI_PASSIVE to hints.ai_flags we instruct that we will bind to the host where the program is running, and for this reason, the first argument of getaddrinfo() is NULL.
If we wanted to bind on another IP or host, we define it as the first argument of getaddrinfo() and bypass the AI_PASSIVE statement. Right now, our program is not ready to recieve connections, it is just binded on a port. How to listen() on this port will be explained later. Also, keep in mind that binding on ports below 1024 is not allowed, unless you are superuser. All other ports, 1024 to 65535 can be used by you, without superuser privilege, unless they are used by another program.
Connection Initialized…
So, now we are allowed to connect to a remote IP address / hostname ( or even local! ). This is achieved using the connect() function.
[c light="true"]int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);[/c]
- int sockfd – The socket file descriptor returned from a socket() call.
- struct sockaddr *serv_addr – a sockaddr structure containing the IP address and port.
- int addrlen – The number in bytes of the server address structure.
Lucky for us, arguments 2 and 3, are provided from the getaddrinfo() call! Yeah!
Return Value
If connect() was unsuccessful it returns -1 and sets errno accordingly. We will cover how to read errno later.
send() and recv()
Now that we are connected to a remote host, we can send() data to him and he can then recv() our data. Lets see the prototypes :
[c light="true"]int send(int sockfd, const void *msg, int len, int flags);[/c]
- int sockfd – The file descriptor returned by socket()
- void *msg – Pointer to the data to be sent.
- int len – Length of msg in bytes.
- int flags – Set to zero.
Return Value
send() will return the number of bytes sent, which might not be same as len! So it’s our responsibility to send all the remaining data. The good news is that packets of size 1kB are usually packed into one packet. Now lets see recv().
If there was an error, -1 will be returned, and errno will be set accordingly.
[c light="true"]int recv(int sockfd, void *buf, int len, int flags);[/c]
- int sockfd – The file descriptor returned by socket()
- void *buf – A buffer where data read are written.
- int len – Length of buf in bytes.
- int flags – Set to zero.
Return Value
recv() will return the number of bytes read, which might not be all the data the sender wanted to send. For this reason, zero is returned in case where the connection has been closed, so that we can assume that no more data are to be read.
In case of error, recv() returns -1 and sets errno accordingly.
A lot of informations these are, right? Lets sum up all those information into a real example.
Server – Client example
What’s the best way to put all together what we learned?
Server that waits for connections, and prints the message it recieves :
[ server.c ]
[c light="true"]
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#define CON_NUM 10
#define MAX_IPV4_SIZE 15
int main(int argc, char** argv) {
int sockfd, new_fd;
struct addrinfo hints, *servinfo;
struct sockaddr_in their_addr;
socklen_t sin_size;
char *buff = (char*)malloc(1024);
char ip[MAX_IPV4_SIZE]; // max would be lets say 111.111.111.111 = 3×4 ( nums ) + 3×1 ( dots ) = 15
int rc;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if (argc != 2) {
printf("Usage : %s PORT\n", argv[0]);
return -1;
}
if ((rc = getaddrinfo(NULL, argv[1], &hints, &servinfo)) != 0) {
printf("Error at getaddrinfo() : %s\n", gai_strerror(rc));
return 1;
}
if ((sockfd = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1) {
printf("Error at socket() : %s\n", strerror(errno));
freeaddrinfo(servinfo);
return 1;
}
if (bind(sockfd, servinfo->ai_addr, servinfo->ai_addrlen) == -1) {
printf("Error at bind() : %s\n", strerror(errno));
freeaddrinfo(servinfo);
close(sockfd);
return -1;
}
if (listen(sockfd, CON_NUM) == -1) {
printf("Error at listen() : %s\n", strerror(errno));
freeaddrinfo(servinfo);
close(sockfd);
return -1;
}
sin_size = sizeof their_addr;
while(1) {
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
if (new_fd == -1) {
printf("Error at accept() : %s\n", strerror(errno));
continue;
}
inet_ntop(their_addr.sin_family, (struct sockaddr*)&their_addr.sin_addr, ip, sizeof ip);
printf("Got connection from %s\n", ip);
int j = 1;
while (1) {
j = recv(new_fd, buff, 1024, 0);
if ( j == 0 )
break;
if ( j == -1 ) {
printf("Error at recv() : %s\n", strerror(errno));
freeaddrinfo(servinfo);
close(sockfd);
return -1;
}
printf("Recieved : %s\n", buff);
}
}
freeaddrinfo(servinfo);
close(sockfd);
close(new_fd);
return 0;
}
[/c]
Client that send’s a msg to a server.
[ client.c ]
[c]
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#define HOSTNAME "localhost"
#define PORT "1235"
int main(int argc, char** argv) {
int status, sock, bytes_sent;
struct addrinfo hints, *servinfo;
char msg[] = "hello!!";
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
//Not correct usage, print help and exit.
if (argc != 3) {
printf("Usage : %s HOSTNAME PORT\n", argv[0]);
return -1;
}
//get info about remote host.
if ((status = getaddrinfo(argv[1], argv[2], &hints, &servinfo)) != 0) {
printf("Error during getaddrinfo() : %s\n", gai_strerror(status));
return -1;
}
//Initialize socket.
if ((sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1) {
printf(" Error during socket() : %s\n", strerror(errno));
freeaddrinfo(servinfo); //free servinfo allocated space and exit.
return -1;
}
//connect to remote host.
if (connect(sock, servinfo->ai_addr, servinfo->ai_addrlen) == -1 ) {
printf("Error during connect() : %s\n", strerror(errno));
freeaddrinfo(servinfo);
close(sock); //Also close the socket.
return -1;
}
if ( (bytes_sent = send(sock, msg, strlen(msg), 0)) == -1 ) {
printf("Error during send() : %s\n", strerror(errno));
freeaddrinfo(servinfo);
close(sock);
return -1;
}
printf("Sent %d bytes.\n", bytes_sent);
freeaddrinfo(servinfo);
close(sock);
return 0;
}</pre>
[/c]
Resources : beej.us
Incoming Part 2 : Defining IPv6, how to make your networking application, IPv6 compatible, and IPv4 backwards compatible.
Incoming Part 3 : Converting all this bunch of code, into pretty C++ Class! Don’t miss them!
As always, you can subscribe via RSS or follow wedevblog on twitter for the latest development posts!
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#define CON_NUM 10
#define MAX_IPV4_SIZE 15int main(int argc, char** argv) {
int sockfd, new_fd;
struct addrinfo hints, *servinfo;
struct sockaddr_in their_addr;
socklen_t sin_size;
char *buff = (char*)malloc(1024);
char ip[MAX_IPV4_SIZE]; // max would be lets say 111.111.111.111 = 3×4 ( nums ) + 3×1 ( dots ) = 15
int rc;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if (argc != 2) {
printf(“Usage : %s PORT\n”, argv[0]);
return -1;
}
if ((rc = getaddrinfo(NULL, argv[1], &hints, &servinfo)) != 0) {
printf(“Error at getaddrinfo() : %s\n”, gai_strerror(rc));
return 1;
}
if ((sockfd = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1) {
printf(“Error at socket() : %s\n”, strerror(errno));
freeaddrinfo(servinfo);
return 1;
}
if (bind(sockfd, servinfo->ai_addr, servinfo->ai_addrlen) == -1) {
printf(“Error at bind() : %s\n”, strerror(errno));
freeaddrinfo(servinfo);
close(sockfd);
return -1;
}
if (listen(sockfd, CON_NUM) == -1) {
printf(“Error at listen() : %s\n”, strerror(errno));
freeaddrinfo(servinfo);
close(sockfd);
return -1;
}
sin_size = sizeof their_addr;
while(1) {
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
if (new_fd == -1) {
printf(“Error at accept() : %s\n”, strerror(errno));
continue;
}
inet_ntop(their_addr.sin_family, (struct sockaddr*)&their_addr.sin_addr, ip, sizeof ip);
printf(“Got connection from %s\n”, ip);
int j = 1;
while (1) {
j = recv(new_fd, buff, 1024, 0);
if ( j == 0 )
break;
if ( j == -1 ) {
printf(“Error at recv() : %s\n”, strerror(errno));
freeaddrinfo(servinfo);
close(sockfd);
return -1;
}
printf(“Recieved : %s\n”, buff);
}
}
freeaddrinfo(servinfo);
close(sockfd);
close(new_fd);
return 0;
}
Recent Comments