Substance:
- Characteristics of the Application
- Programming with Socket API
- Data Structure and Handling
- Variable changes
- IP address handling
- System Call
- Socket programming usage scenarios
- Advanced Socket
- Remote Procedure Call (RPC)
Introduction
The highest layer is the application layer. This layer communicates with each other between hosts and is the interface that is visible to the user on the TCP/IP protocol.
Application Layer Metamodel
1. Characteristics of the Application
In the application layer there are several characteristics that are the same, namely:
1). It is an application written by the user (user-written) or an application that is already a standard with TCP/IP products in it. The TCP/IP set applications that are included include:
- TELNET, used to access remote hosts through an interactive terminal.
- FTP (File Transfer Protocol) is used to transfer files between disks.
- SMTP (Simple Mail Transfer Protocol) is used as a mail system on the internet.
2). Using UDP or TCP transport system
3). Using client-server model
2. Programming with Socket API
Application Programming Interface (API) can be used by users to create an application. While for network facilities, you can use the SOCKET API section. This section will explain examples of APIs used for networking.
This chapter explains how to use the C programming language for networking purposes on a Linux machine.
Example of compiling and running a program:
#gcc --o program source.c
#./program
3. Data Structure and Handling
Before using socket programming, a structure variable is needed to store information about the network. The structures needed include:
- sockaddr
- sockaddr_in
Examples of its use are:
struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
Where, sa_family is used to determine the type of family used in this chapter using AF_INET means using the INTERNETWORKING family. While for sa_data is used for information on the destination and port used.
To use this structure, one more structure is needed, namely sockadd_in, where "in" means internet.
struct sockaddr_in {
short int sin_family; // Address family
unsigned short int sin_port; // Port number
struct in_addr sin_addr; // Internet address
unsigned char sin_zero[8]; // Same size as struct sockaddr
};
With this structure, the programmer will easily control the data. In the sin_zero section, it is used as a complement where it must be set to a value of 0, this can be done using the memset() function.
To use an IP address, a structure variable is also needed, namely the in_addr structure, where the in_addr structure is as follows:
// Internet address (a structure for historical reasons)
struct in_addr {
unsigned long s_addr; // that's a 32-bit long, or 4 bytes
};
So that users can do it by creating a variable, for example ina and of the type struct sockaddr_in, then ina.sin_addr.s_addr can be used as an object for the IP address.
4. Changes in variables
The initial change that can be used is the change from short (2 bytes) to long (4 bytes). Then another change is the change from host to network. So that each change can be abbreviated into 1 letter, namely , s, l, n, and h.
Functions that can be used for these changes include:
- htons() : change host to network with short system
- htonl() : change host to network with long system
- ntohs() : network change to host with short system
- ntohl() : network change to host with long system
5. IP address handling
There are several ways to enter an IP address into a variable in socket programming.
If we already have a variable struct sockadd_in ina, and we have an IP address of "10.252.102.23". Then with the inet_addr() function, we can change the IP address to an unsigned long. Example of use:
ina.sin_addr.s_addr = inet_addr("10.252.102.23");
Apart from that, there is another way, namely by using inet_aton:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
And an example of its use is as follows:
struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
inet_aton("10.252.102.53", &(my_addr.sin_addr));
memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct
So if we want to display the contents of these variables, we can do this with the additional function inet_ntoa (network to ascii).
printf("%s", inet_ntoa(ina.sin_addr));
Complete example:
char *a1, *a2;
.
.
a1 = inet_ntoa(ina1.sin_addr); // this is 192.168.4.14
a2 = inet_ntoa(ina2.sin_addr); // this is 10.12.110.57
printf("address 1: %s\n",a1);
printf("address 2: %s\n",a2);
Then it will produce
address 1: 10.12.110.57
address 2: 10.12.110.57
6. System Call
System calls are functions in programming. These functions are used to run and access the network.
socket()
Use:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
This function is used for initialization in socket usage. Where the domain contains AF_INET, while the type contains SOCK_STREAM or SOCK_DGRAM and the protocol contains the number 0.
SOCK_STREAM is used when using the TCP protocol and SOCK_DGRAM is used for the UDP protocol.
In addition to the above contents, there are many others and can be seen on the manual page.
bind()
Use:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
The bind function is used to associate IP addresses and ports. The sockfd variable is obtained from the socket() function.
Example:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 3490
main()
{
int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct
// don't forget your error checking for bind():
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
.
.
.
If you want to use our machine's IP address, you can use:
my_addr.sin_port = 0; // choose an unused port at random
my_addr.sin_addr.s_addr = INADDR_ANY; // use my IP address
connect()
Use:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
Example:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DEST_IP "10.12.110.57"
#define DEST_PORT 23
main()
{
int sockfd;
struct sockaddr_in dest_addr; // will hold the destination addr
sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!
dest_addr.sin_family = AF_INET; // host byte order
dest_addr.sin_port = htons(DEST_PORT); // short, network byte order
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
memset(&(dest_addr.sin_zero), '\0', 8); // zero the rest of the struct
// don't forget to error check the connect()!
connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
.
.
.
listen()
Use:
int listen(int sockfd, int backlog);
The function of the listen command is used to wait for a connection from a host.
accept()
Use:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
The function of accept is used after the listen function. Where the socket will forward to the new socket variable after a host contacts. Accept will form a new socket and can be processed for send or recv.
Example:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 3490 // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold
main()
{
int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
struct sockaddr_in my_addr; // my address information
struct sockaddr_in their_addr; // connector's address information
int sin_size;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = INADDR_ANY; // auto-fill with my IP
memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct
// don't forget your error checking for these calls:
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
listen(sockfd, BACKLOG);
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
.
.
.
send() and recv()
Use:
int send(int sockfd, const void *msg, int len, int flags);
int recv(int sockfd, void *buf, int len, unsigned int flags);
The function of send and recv is for data exchange. The send() and recv() functions are used for data with connection-oriented protocols, while for connectionless-oriented protocols use sendto() and recvfrom().
The *msg pointer is the content of the data to be sent, as well as *buf is a pointer that contains the data received. The len variable is used as the length of the data.
Example:
char *msg = "Beej was here!";
int len, bytes_sent;
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
.
.
.
sendto() and recvfrom()
Use:
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct
sockaddr *to, int tolen);
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr
*from, int *fromlen);
The function of sendto and recvfrom is to exchange data with the DGRAM protocol. The function is almost the same as the send and recv functions where there are additional variables, namely struct sockaddr *to, and int toleni.
close() and shutdown()
Use:
close(sockfd);
int shutdown(int sockfd, int how);
The close() and shutdown() functions are used to close a connection after exchanging data. Shutdown is used when a certain condition is desired, the variable is added to the how variable. The variable has a certain value and meaning, namely:
- 0 -- After closing, only receiving is allowed
- 1 -- After closing, only sending is allowed
- 2 -- After closing, receiving and sending are not allowed (same as close() )
getpeername()
Use:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
The getpeername() function is used to find out information about the destination.
gethostname()
Use:
#include <unistd.h>
int gethostname(char *hostname, size_t size);
The gethostname() function is used to find out information about our network machines.
DNS -- Sending to "whitehouse.gov", Answered "198.137.240.92"
Use:
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
The hostent structure has objects in it, including:
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
#define h_addr h_addr_list[0]
Where:
- h_name -- the official name of a host
- h_aliases -- NULL , alternative names of a host
- h_addrtype -- type of address, example AF_INET
- h_length -- length of IP address data
- h_addr_list -- ZERO, set of IPs with that name
- h_addr -- first address of h_addr_list
To get the results from the hostent structure, the gethostbyname() function is used. How to use it can be seen in the example program.
Example program:
/*
** getip.c - a hostname lookup demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
struct hostent *h;
if (argc != 2) { // error check the command line
fprintf(stderr,"usage: getip address\n");
exit(1);
}
if ((h=gethostbyname(argv[1])) == NULL) { // get the host info
herror("gethostbyname");
exit(1);
}
printf("Host name : %s\n", h->h_name);
printf("IP Address : %s\n", inet_ntoa(*((struct in_addr *)h->h_addr)));
return 0;
}
7. Socket programming usage scenarios
Socket programming uses a client-server system, where the client process talks to the server process and vice versa. For example, a client with a telnet application will contact a server running the telnetd application.
Client Server
The flow diagram used is shown in
Connection-oriented Program Flowchart
Connectionless-oriented Program Flowchart
STREAM Based Server Example
/*
** server.c - a stream socket server demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define MYPORT 3490 // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold
void sigchld_handler(int s)
{
while(wait(NULL) > 0);
}
int main(void)
{
int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
struct sockaddr_in my_addr; // my address information
struct sockaddr_in their_addr; // connector's address information
int sin_size;
struct sigaction sa;
int yes=1;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))== -1)
{
perror("bind");
exit(1);
}
if (listen(sockfd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
sa.sa_handler = sigchld_handler; // reap all dead processes
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
while(1) { // main accept() loop
sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr,
&sin_size)) == -1) {
perror("accept");
continue;
}
printf("server: got connection from %s\n",
inet_ntoa(their_addr.sin_addr));
if (!fork()) { // this is the child process
close(sockfd); // child doesn't need the listener
if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); // parent doesn't need this
}
return 0;
}
To try the server program run: #telnet server 3490
STREAM Based Client Example
/*
** client.c - a stream socket client demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 3490 // the port client will be connecting to
#define MAXDATASIZE 100 // max number of bytes we can get at once
int main(int argc, char *argv[])
{
int sockfd, numbytes;
char buf[MAXDATASIZE];
struct hostent *he;
struct sockaddr_in their_addr; // connector's address information
if (argc != 2) {
fprintf(stderr,"usage: client hostname\n");
exit(1);
}
if ((he=gethostbyname(argv[1])) == NULL) { // get the host info
perror("gethostbyname");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
their_addr.sin_family = AF_INET; // host byte order
their_addr.sin_port = htons(PORT); // short, network byte order
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
memset(&(their_addr.sin_zero), '\0', 8); // zero the rest of the struct
if (connect(sockfd, (struct sockaddr *)&their_addr,sizeof(struct sockaddr))
== -1) {
perror("connect");
exit(1);
}
if ((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
perror("recv");
exit(1);
}
buf[numbytes] = '\0';
printf("Received: %s",buf);
close(sockfd);
return 0;
}
This program searches for a server with port 3490 and receives a string from the server and displays it to the screen.
Socket with DATAGRAM
The listener program will be ready on a machine and will wait for packets to be sent to port 4950. The talker program will send packets to that port.
Listing program listernet :
/*
** listener.c - a datagram sockets "server" demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MYPORT 4950 // the port users will be connecting to
#define MAXBUFLEN 100
int main(void)
{
int sockfd;
struct sockaddr_in my_addr; // my address information
struct sockaddr_in their_addr; // connector's address information
int addr_len, numbytes;
char buf[MAXBUFLEN];
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct
if (bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1)
{ perror("bind");
exit(1);
}
addr_len = sizeof(struct sockaddr);
if ((numbytes=recvfrom(sockfd,buf, MAXBUFLEN-1, 0,(struct sockaddr
*)&their_addr, &addr_len)) == -1) {
perror("recvfrom");
exit(1);
}
printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr));
printf("packet is %d bytes long\n",numbytes);
buf[numbytes] = '\0';
printf("packet contains \"%s\"\n",buf);
close(sockfd);
return 0;
}
Listing program talker:
/*
** talker.c - a datagram "client" demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define MYPORT 4950 // the port users will be connecting to
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in their_addr; // connector's address information
struct hostent *he;
int numbytes;
if (argc != 3) {
fprintf(stderr,"usage: talker hostname message\n");
exit(1);
}
if ((he=gethostbyname(argv[1])) == NULL) { // get the host info
perror("gethostbyname");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}
their_addr.sin_family = AF_INET; // host byte order
their_addr.sin_port = htons(MYPORT); // short, network byte order
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
memset(&(their_addr.sin_zero), '\0', 8); // zero the rest of the struct
if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0,(struct sockaddr
*)&their_addr, sizeof(struct sockaddr))) == -1) {
perror("sendto");
exit(1);
}
printf("sent %d bytes to %s\n", numbytes,
inet_ntoa(their_addr.sin_addr));
close(sockfd);
return 0;
}
8. Advanced socket
This section explains the use of several functions that can support the work of network programs using socket programming.
Blocking
A server application can receive data packets simultaneously, for that it is necessary to release a limiter or what is called non-blocking. So that the server can receive data simultaneously.
In socket() initialization, socket initially has an initial blocking value. To make it non-blocking, call the fcntl() function. This can be seen in the following example:
#include <unistd.h>
#include <fcntl.h>
.
.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
.
.
select() -- Synchronous I/O Multiplexing
With the select function, applications will be able to sort and process data at the same time. Example of using select()
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct
timeval *timeout);
To clarify, here is an example of a program that will wait for 2.5 seconds to see if any data comes in from keyboard input.
/*
** select.c - a select() demo
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define STDIN 0 // file descriptor for standard input
int main(void)
{
struct timeval tv;
fd_set readfds;
tv.tv_sec = 2;
tv.tv_usec = 500000;
FD_ZERO(&readfds);
FD_SET(STDIN, &readfds);
// don't care about writefds and exceptfds:
select(STDIN+1, &readfds, NULL, NULL, &tv);
if (FD_ISSET(STDIN, &readfds))
printf("A key was pressed!\n");
else
printf("Timed out.\n");
return 0;
}
Example of using select() in a multiperson chat server application
/*
** selectserver.c - a cheezy multiperson chat server
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 9034 // port we're listening on
int main(void)
{
fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()
struct sockaddr_in myaddr; // server address
struct sockaddr_in remoteaddr; // client address
int fdmax; // maximum file descriptor number
int listener; // listening socket descriptor
int newfd; // newly accept()ed socket descriptor
char buf[256]; // buffer for client data
int nbytes;
int yes=1; // for setsockopt() SO_REUSEADDR, below
int addrlen;
int i, j;
FD_ZERO(&master); // clear the master and temp sets
FD_ZERO(&read_fds);
// get the listener
if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
// lose the pesky "address already in use" error message
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
// bind
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = INADDR_ANY;
myaddr.sin_port = htons(PORT);
memset(&(myaddr.sin_zero), '\0', 8);
if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) {
perror("bind");
exit(1);
}9\.
// listen
if (listen(listener, 10) == -1) {
perror("listen");
exit(1);
}
// add the listener to the master set
FD_SET(listener, &master);
// keep track of the biggest file descriptor
fdmax = listener; // so far, it's this one
// main loop
for(;;) {
read_fds = master; // copy it
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
perror("select");
exit(1);
}
// run through the existing connections looking for data to read
for(i = 0; i <= fdmax; i++) {
if (FD_ISSET(i, &read_fds)) {
// we got one!!
if (i == listener) {
// handle new connections
addrlen = sizeof(remoteaddr);
if ((newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen))
== -1) {
perror("accept");
} else {
FD_SET(newfd, &master); // add to master set
if (newfd > fdmax) { // keep track of the maximum
fdmax = newfd;
}
printf("selectserver: new connection from %s on socket %d\n",
inet_ntoa(remoteaddr.sin_addr), newfd);
}
} else {
// handle data from a client
if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0) {
// got error or connection closed by client
if (nbytes == 0) {
// connection closed
printf("selectserver: socket %d hung up\n", i);
} else {
perror("recv");
}
close(i); // bye!
FD_CLR(i, &master); // remove from master set
} else {
// we got some data from a client
for(j = 0; j <= fdmax; j++) {
// send to everyone!
if (FD_ISSET(j, &master)) {
// except the listener and ourselves
if (j != listener && j != i) {
if (send(j, buf, nbytes, 0) == -1) {
perror("send");
}
}
}
}
}
} // it's SO UGLY!
}
}
}
return 0;
}
9. Remote Procedure Call (RPC)
RPC is a protocol that allows a computer program to provide a subroutine to another computer to execute a command without the programmer having to create the program first.