C Socket Programming Module41 Tenouk
C Socket Programming Module41 Tenouk
NETWORK PROGRAMMING
SOCKET PART III
-Program Examples-
Note:
This is a continuation from Part II, Module40. Working program examples compiled using gcc, tested using the
public IPs, run on Fedora 3 with several times update, as normal user. The Fedora machine used for the testing
having the "No Stack Execute" disabled and the SELinux set to default configuration. All the program
example is generic. Beware codes that expand more than one line. Have a nice ride lol!
- The following program example acts like a simple multi-user chat server. Start running it in one
window, then telnet to it ("telnet hostname 2020") from multiple other windows.
- When you type something in one telnet session, it should appear in all the others windows.
/*******select.c*********/
/*******Using select() for I/O multiplexing*/
#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>
/* port we're listening on */
#define PORT 2020
www.tenouk.com Page 1 of 27
int nbytes;
/* for setsockopt() SO_REUSEADDR, below */
int yes = 1;
int addrlen;
int i, j;
/* clear the master and temp sets */
FD_ZERO(&master);
FD_ZERO(&read_fds);
/* bind */
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = INADDR_ANY;
serveraddr.sin_port = htons(PORT);
memset(&(serveraddr.sin_zero), '\0', 8);
/* listen */
if(listen(listener, 10) == -1)
{
perror("Server-listen() error lol!");
exit(1);
}
printf("Server-listen() is OK...\n");
/* loop */
for(;;)
{
/* copy it */
read_fds = master;
www.tenouk.com Page 2 of 27
FD_SET(newfd, &master); /* add to master set */
if(newfd > fdmax)
{ /* keep track of the maximum */
fdmax = newfd;
}
printf("%s: New connection from %s on socket %d\n", argv[0],
inet_ntoa(clientaddr.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("%s: socket %d hung up\n", argv[0], i);
else
perror("recv() error lol!");
/* close it... */
close(i);
/* remove from master set */
FD_CLR(i, &master);
}
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() error lol!");
}
}
}
}
}
}
}
}
return 0;
}
- You can leave the program running at the background (Ctrl + z).
www.tenouk.com Page 3 of 27
- Do some verification.
- Telnet from other computers or windows using hostname or the IP address. Here we use hostname,
bakawali. Use escape character ( Ctrl + ] ) to terminate command. For other telnet command please
type help.
- The last two messages were typed at another two machines that connected through socket 5 and 6
(socket 4 is another window of the server) using telnet. Socket 5 and 6 are from Windows 2000 Server
machines.
- The following are messages on the server console. There are another two machine connected to the
server and the messages at the server console is shown below.
- When the clients disconnected from the server through socket 4, 5 and 6, the following messages appear
on the server console.
...
Server-select() is OK...
Server-select() is OK...
./select: socket 5 hung up
Server-select() is OK...
./select: socket 6 hung up
Server-select() is OK...
./select: socket 4 hung up
- There are two file descriptor sets in the code: master and read_fds. The first, master, holds all
the socket descriptors that are currently connected, as well as the socket descriptor that is listening for
new connections.
- The reason we have the master set is that select() actually changes the set you pass into it to
reflect which sockets are ready to read. Since we have to keep track of the connections from one call of
select() to the next, we must store these safely away somewhere. At the last minute, we copy the
master into the read_fds, and then call select().
- Then every time we get a new connection, we have to add it to the master set and also every time a
connection closes, we have to remove it from the master set.
- Notice that we check to see when the listener socket is ready to read. When it is, it means we have
a new connection pending, and we accept() it and add it to the master set. Similarly, when a client
www.tenouk.com Page 4 of 27
connection is ready to read, and recv() returns 0, we know that the client has closed the connection,
and we must remove it from the master set.
- If the client recv() returns non-zero, though, we know some data has been received. So we get it, and
then go through the master list and send that data to all the rest of the connected clients.
- The following program examples are connection-oriented where sockets use TCP to connect a server to
a client, and a client to a server. This example provides more complete sockets’ APIs usage.
/************tcpserver.c************************/
/* Header files needed to use the sockets API. */
/* File contain Macro, Data Type and Structure */
/***********************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
/* BufferLength is 100 bytes */
#define BufferLength 100
/* Server port number */
#define SERVPORT 3111
int main()
{
/* Variable and structure definitions. */
int sd, sd2, rc, length = sizeof(int);
int totalcnt = 0, on = 1;
char temp;
char buffer[BufferLength];
struct sockaddr_in serveraddr;
struct sockaddr_in their_addr;
fd_set read_fd;
struct timeval timeout;
timeout.tv_sec = 15;
timeout.tv_usec = 0;
/* bind to an address */
www.tenouk.com Page 5 of 27
memset(&serveraddr, 0x00, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVPORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*client IP*/
printf("Server-new socket, sd2 is OK...\n");
printf("Got connection from the f***ing client: %s\n", inet_ntoa(their_addr.sin_addr));
www.tenouk.com Page 6 of 27
/* When select() indicates that there is data */
/* available, use the read() function to read */
/* 100 bytes of the string that the */
/* client sent. */
/***********************************************/
/* read() from client */
rc = read(sd2, &buffer[totalcnt], (BufferLength - totalcnt));
if(rc < 0)
{
perror("Server-read() error");
close(sd);
close(sd2);
exit (-1);
}
else if (rc == 0)
{
printf("Client program has issued a close()\n");
close(sd);
close(sd2);
exit(-1);
}
else
{
totalcnt += rc;
printf("Server-read() is OK\n");
}
}
}
else if (rc < 0)
{
perror("Server-select() error");
close(sd);
close(sd2);
exit(-1);
}
/* rc == 0 */
else
{
printf("Server-select() timed out.\n");
close(sd);
close(sd2);
exit(-1);
}
close(sd);
close(sd2);
exit(-1);
}
www.tenouk.com Page 7 of 27
/*****************************************/
/* Close the connection to the client and */
/* close the server listening socket. */
/******************************************/
close(sd2);
close(sd);
exit(0);
return 0;
}
- Run the program. In this example we let the program run in the background.
- Do some verification.
- When the next program example (the TCP client) is run, the following messages should be expected at
the server console.
- If the server program and then the client are run, the following messages should be expected at the
server console.
www.tenouk.com Page 8 of 27
Trying 203.106.93.94...
Connected to bakawali.jmti.gov.my (203.106.93.94).
Escape character is '^]'.
^]
telnet> help
Commands may be abbreviated. Commands are:
- Well, it looks that we have had a telnet session with the server.
- Well, let try the client program that will connect to the previous server program.
- The following example shows how to connect a socket client program to a connection-oriented server.
/************tcpclient.c************************/
/* Header files needed to use the sockets API. */
/* File contains Macro, Data Type and */
/* Structure definitions along with Function */
/* prototypes. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
/* BufferLength is 100 bytes */
#define BufferLength 100
/* Default host name of server system. Change it to your default */
/* server hostname or IP. If the user do not supply the hostname */
/* as an argument, the_server_name_or_IP will be used as default*/
#define SERVER "The_server_name_or_IP"
/* Server's port number */
#define SERVPORT 3111
www.tenouk.com Page 9 of 27
/* The socket() function returns a socket */
/* descriptor representing an endpoint. */
/* The statement also identifies that the */
/* INET (Internet Protocol) address family */
/* with the TCP transport (SOCK_STREAM) */
/* will be used for this socket. */
/******************************************/
/* get a socket descriptor */
if((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Client-socket() error");
exit(-1);
}
else
printf("Client-socket() OK\n");
/*If the server hostname is supplied*/
if(argc > 1)
{
/*Use the supplied argument*/
strcpy(server, argv[1]);
printf("Connecting to the f***ing %s, port %d ...\n", server, SERVPORT);
}
else
/*Use the default server name or IP*/
strcpy(server, SERVER);
if(rc < 0)
{
perror("Client-write() error");
rc = getsockopt(sd, SOL_SOCKET, SO_ERROR, &temp, &length);
if(rc == 0)
{
www.tenouk.com Page 10 of 27
/* Print out the asynchronously received error. */
errno = temp;
perror("SO_ERROR was");
}
close(sd);
exit(-1);
}
else
{
printf("Client-write() is OK\n");
printf("String successfully sent lol!\n");
printf("Waiting the %s to echo back...\n", server);
}
totalcnt = 0;
while(totalcnt < BufferLength)
{
- Run the program. Before that don’t forget to run the server program first. The first run is without the
server hostname/IP.
www.tenouk.com Page 11 of 27
Echoed data from the f***ing server: This is a test string from client lol!!!
[bodo@bakawali testsocket]$
- Well, it works!
- The connectionless protocol server and client examples illustrate the socket APIs that are written for
User Datagram Protocol (UDP). The server and client examples use the following sequence of function
calls:
▪ socket()
▪ bind()
- The following figure illustrates the client/server relationship of the socket APIs for a connectionless
protocol.
www.tenouk.com Page 12 of 27
Connecting a UDP server and client
- The following examples show how to use UDP to connect a server to a connectionless client, and a
connectionless client to a server.
- The first example shows how to use UDP to connect a connectionless socket server program to a client.
/*******************udpserver.c*****************/
/* Header files needed to use the sockets API. */
/* File contain Macro, Data Type and Structure */
/* definitions along with Function prototypes. */
/* header files */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* Server's port number, listen at 3333 */
#define SERVPORT 3333
www.tenouk.com Page 13 of 27
printf("Using IP %s and port %d\n", inet_ntoa(serveraddr.sin_addr), SERVPORT);
printf("UDP server - Listening...\n");
www.tenouk.com Page 14 of 27
- Verify that the udp server is listening at port 3333 waiting for the client connection.
- Without the client program (next example) you can try telneting the server using port 3333 for testing.
For this program example the following telnet session cannot be established for UDP/connectionless.
- The following example shows how to use UDP to connect a connectionless socket client program to a
server. This program will be used to connect to the previous UDP server.
/****************udpclient.c********************/
/* Header files needed to use the sockets API. */
/* File contain Macro, Data Type and Structure */
/* definitions along with Function prototypes. */
/***********************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
www.tenouk.com Page 15 of 27
/* If the hostname/IP of the server is supplied */
/* Or if(argc = 2) */
if(argc > 1)
strcpy(server, argv[1]);
else
{
/*Use default hostname or IP*/
printf("UDP Client - Usage %s <Server hostname or IP>\n", argv[0]);
printf("UDP Client - Using default hostname/IP!\n");
strcpy(server, SERVER);
}
if(rc < 0)
{
perror("UDP Client - recvfrom() error");
close(sd);
exit(-1);
}
else
{
printf("UDP client received the following: \"%s\" message\n", bufptr);
www.tenouk.com Page 16 of 27
printf(" from port %d, address %s\n", ntohs(serveraddr.sin_port),
inet_ntoa(serveraddr.sin_addr));
}
- Run the program. Before that make sure the previous program example (the UDP server) is running.
- Well, our udp server and client communicated successfully. The following are the expected messages at
server console.
There are a number of ways that you can design a connection-oriented socket server. While additional socket server
designs are possible, the designs provided in the examples below are the most common:
Note: A worker job or a worker thread refers to a process or sub-process (thread) that does data processing by using
the socket descriptor. For example, a worker process accesses a database file to extract and format information for
sending to the remote peer through the socket descriptor and its associated connection. It could then receive a
response or set of data from the remote peer and update the database accordingly.
Depending on the design of the server, the worker usually does not perform the connection "bring-up" or initiation.
This is usually done by the listening or server job or thread. The listening or server job usually passes the descriptor
to the worker job or thread.
Iterative server
In the iterative server example, a single server job handles all incoming connections and all data flows with the
client jobs. When the accept() API completes, the server handles the entire transaction. This is the easiest
server to develop, but it does have a few problems. While the server is handling the request from a given client,
additional clients could be trying to get to the server. These requests fill the listen() backlog and some of them
will be rejected eventually.
www.tenouk.com Page 17 of 27
All of the remaining examples are concurrent server designs. In these designs, the system uses multiple jobs and
threads to handle the incoming connection requests. With a concurrent server there are usually multiple clients that
connect to the server at the same time.
The spawn() server and spawn() worker example uses the spawn() API to create a new job (often called a
"child job") to handle each incoming request. After spawn() completes, the server can then wait on the
accept() API for the next incoming connection to be received. The only problem with this server design is the
performance overhead of creating a new job each time a connection is received. You can avoid the performance
overhead of the spawn() server example by using pre-started jobs. Instead of creating a new job each time a
connection is received, the incoming connection is given to a job that is already active. If the child job is already
active, the sendmsg() and recvmsg() APIs.
Servers that use sendmsg() and recvmsg() APIs to pass descriptors remain unhindered during heavy activity.
They do not need to know which worker job is going to handle each incoming connection. When a server calls
sendmsg(), the descriptor for the incoming connection and any control data are put in an internal queue for the
AF_UNIX socket. When a worker job becomes available, it calls recvmsg() and receives the first descriptor and
the control data that was in the queue.
An example of how you can use the sendmsg() API to pass a descriptor to a job that does not exist, a server can
do the following:
The child job calls recvmsg() to receive the descriptor that the server passed. The child job was not active when
the server called sendmsg(). The sendmsg() and recvmsg() APIs are extremely flexible. You can use these
APIs to send data buffers, descriptors, or both.
In the previous examples, the worker job did not get involved until after the server received the incoming connection
request. The multiple accept() servers and multiple accept() workers example of the system turns each of
the worker jobs into an iterative server. The server job still calls the socket(), bind(), and listen() APIs.
When the listen() call completes, the server creates each of the worker jobs and gives a listening socket to each
one of them. All of the worker jobs then call the accept() API. When a client tries to connect to the server, only
one accept() call completes, and that worker handles the connection. This type of design removes the need to
give the incoming connection to a worker job, and saves the performance overhead that is associated with that
operation. As a result, this design has the best performance. A worker job or a worker thread refers to a process or
sub-process (thread) that does data processing by using the socket descriptor. For example, a worker process
accesses a database file to extract and format information for sending to the remote peer through the socket
descriptor and its associated connection. It could then receive a response or set of data from the remote peer and
update the database accordingly.
Depending on the design of the server, the worker usually does not perform the connection "bring-up" or initiation.
This is usually done by the listening or server job or thread. The listening or server job usually passes the descriptor
to the worker job or thread.
- This example shows how you can write an iterative server program. The following simple figure
illustrates how the server and client jobs interact when the system used the iterative server design.
www.tenouk.com Page 18 of 27
Figure 2: An example of socket APIs used for iterative server design.
- In the following example of the server program, the number of incoming connections that the server
allows depends on the first parameter that is passed to the server. The default is for the server to allow
only one connection.
www.tenouk.com Page 19 of 27
printf("Iserver - socket() is OK\n");
www.tenouk.com Page 20 of 27
- Compile and link
- The server is waiting the connections from clients. The following program example is a client program.
- This example provides the code for the client job. The client job does a socket(), connect(),
send(), recv(), and close().
- The client job is not aware that the data buffer it sent and received is going to a worker job rather than to
the server.
- This client job program can also be used to work with other previous connection-oriented server
program examples.
if(argc !=2)
{
printf("Usage: %s <Server_name or Server_IP_address>\n", argv[0]);
exit (-1);
}
/* Create an AF_INET stream socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("client - socket() error");
exit(-1);
}
else
printf("client - socket() is OK.\n");
/* Initialize the socket address structure */
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(SERVER_PORT);
/* Connect to the server */
rc = connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
if(rc < 0)
{
perror("client - connect() error");
close(sockfd);
exit(-1);
}
else
{
printf("client - connect() is OK.\n");
printf("connect() completed successfully.\n");
www.tenouk.com Page 21 of 27
printf("Connection with %s using port %d established!\n", argv[1], SERVER_PORT);
}
- Run the program and make sure you run the server program as in the previous program example.
www.tenouk.com Page 22 of 27
accept() is OK and completed successfully!
I am waiting client(s) to send message(s) to me...
The message from client: "This is a test message from a stupid client lol!"
Echoing it back to client...
Iserver - send() is OK.
[bodo@bakawali testsocket]$
www.tenouk.com Page 23 of 27
[bodo@bakawali testsocket]$
- IP multicasting provides the capability for an application to send a single IP datagram that a group of
hosts in a network can receive. The hosts that are in the group may reside on a single subnet or may be
on different subnets that have been connected by multicast capable routers.
- Hosts may join and leave groups at any time. There are no restrictions on the location or number of
members in a host group. A class D Internet address in the range 224.0.0.1 to 239.255.255.255
identifies a host group.
- An application program can send or receive multicast datagrams by using the socket() API and
connectionless SOCK_DGRAM type sockets. Each multicast transmission is sent from a single network
interface, even if the host has more than one multicasting-capable interface.
- It is a one-to-many transmission method. You cannot use connection-oriented sockets of type
SOCK_STREAM for multicasting.
- When a socket of type SOCK_DGRAM is created, an application can use the setsockopt() function
to control the multicast characteristics associated with that socket. The setsockopt() function
accepts the following IPPROTO_IP level flags:
- The following examples enable a socket to send and receive multicast datagrams. The steps needed to
send a multicast datagram differ from the steps needed to receive a multicast datagram.
- The following example enables a socket to perform the steps listed below and to send multicast
datagrams:
www.tenouk.com Page 24 of 27
exit(1);
}
else
printf("Opening the datagram socket...OK.\n");
/* Try the re-read from the socket if the loopback is not disable
if(read(sd, databuf, datalen) < 0)
{
perror("Reading datagram message error\n");
close(sd);
exit(1);
}
else
{
printf("Reading datagram message from client...OK\n");
printf("The message is: %s\n", databuf);
}
*/
return 0;
}
- Before running this multicaster program, you have to run the client program first as in the following.
- The following example enables a socket to perform the steps listed below and to receive multicast
datagrams:
www.tenouk.com Page 25 of 27
3. Use the bind() verb to specify the local port number. Specify the IP address as
INADDR_ANY in order to receive datagrams that are addressed to a multicast group.
4. Use the IP_ADD_MEMBERSHIP socket option to join the multicast group that receives the
datagrams. When joining a group, specify the class D group address along with the IP address
of a local interface. The system must call the IP_ADD_MEMBERSHIP socket option for each
local interface receiving the multicast datagrams.
5. Receive the datagram.
www.tenouk.com Page 26 of 27
/* Read from the socket. */
datalen = sizeof(databuf);
if(read(sd, databuf, datalen) < 0)
{
perror("Reading datagram message error");
close(sd);
exit(1);
}
else
{
printf("Reading datagram message...OK.\n");
printf("The message from multicast server is: \"%s\"\n", databuf);
}
return 0;
}
1. Check the best selling C/C++, Networking, Linux and Open Source books at Amazon.com.
2. Broadcasting.
3. Telephony.
www.tenouk.com Page 27 of 27