C Socket Programming Module40 Tenouk
C Socket Programming Module40 Tenouk
NETWORK PROGRAMMING
SOCKET PART II
Note:
This is a continuation from Part I, Module 39. Working program examples compiled using gcc, tested using the
public IPs, run on Fedora 3, with several times of update, as normal user. The Fedora machine used for the testing
having the "No Stack Execute" disabled and the SELinux set to default configuration.
Abilities
- Some of the information in this section is a repetition from the previous one.
telnet telserv.test.com
telnet 131.95.115.204
NAME
gethostbyname() - get network host entry
SYNOPSIS
#include <netdb.h>
extern int h_errno;
struct hostent
*gethostbyname(const char *name);
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]
www.tenouk.com
NAME
getservbyname() - get service entry
SYNOPSIS
#include <netdb.h>
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
}
- s_port: port number for the service given in network byte order.
NAME
getprotobyname() - get protocol entry
SYNOPSIS
#include <netdb.h>
struct protoent
*getprotobyname(const char *name);
struct protoent {
char *p_name;
char **p_aliases;
int p_proto;
}
getpeername()
- The function getpeername() will tell you who is at the other end of a connected stream socket.
- The prototype:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
Allocating a Socket
#include <sys/types.h>
#include <sys/socket.h>
int s;
www.tenouk.com
Connecting to a Server with TCP
- connect().
NAME
connect - initiate a connection on a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
RETURN VALUE
If the connection or binding succeeds, zero is returned.
On error, -1 is returned, and errno is set appropriately.
left = 100;
b = buf;
while (left && (n = read(s, buf, left)) > 0)
{
b += n;
left -= n;
}
- The client and server can not know how many bytes are sent in each write.
- Delivered chunks are not always the same size as in the original write.
- Reads must be handled in a loop to cope with stream sockets.
www.tenouk.com
- Receiver receives the complete datagram unless fewer bytes are read.
- Reading in a loop for a single datagram is pointless with UDP.
- close() is adequate, since shutdown() does not send any messages.
- UDP is unreliable. UDP software needs an error protocol.
connectTCP()
connectUDP()
connectsock()
int connectsock(const char *host, const char *service, const char *transport)
{
struct hostent *phe; /* pointer to host information entry */
struct servent *pse; /* pointer to service information entry */
struct protoent *ppe; /* pointer to protocol information entry*/
struct sockaddr_in sin; /* an Internet endpoint address */
int s, type; /* socket descriptor and socket type */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
www.tenouk.com
type = SOCK_STREAM;
/* Allocate a socket */
s = socket(PF_INET, type, ppe->p_proto);
if(s < 0)
errexit("can't create socket: %s\n", strerror(errno));
s = connectTCP(host, service);
www.tenouk.com
int main(int argc, char *argv[])
{
char *host = "localhost"; /* host to use if none supplied */
char *service = "time"; /* default service name */
time_t now; /* 32-bit integer to hold time */
int s, n; /* socket descriptor, read count*/
switch (argc) {
case 1:
host = "localhost";
break;
case 3:
service = argv[2];
/* FALL THROUGH */
case 2:
host = argv[1];
break;
default:
fprintf(stderr, "usage: UDPtime [host [port]]\n");
exit(1);
}
s = connectUDP(host, service);
TCPecho() function
- The following is a code segment example for using the TCPecho() function.
s = connectTCP(host, service);
/* read it back */
for(inchars = 0; inchars < outchars; inchars+=n)
{
n = read(s, &buf[inchars], outchars - inchars);
if(n < 0)
errexit("socket read failed: %s\n", strerror(errno));
}
fputs(buf, stdout);
}
}
UDPecho() function
- The following is a code segment example for using the UDPecho() function.
www.tenouk.com
int UDPecho(const char *host, const char *service)
{
/* buffer for one line of text */
char buf[LINELEN+1];
/* socket descriptor, read count */
int s, nchars;
s = connectUDP(host, service);
Connection-oriented Servers
Connectionless Servers
Stateless Servers
- Statelessness improves reliability at the cost of longer requests and slower performance.
- Improving performance generally adds state information. For example, adding a cache of file data.
- Crashing clients leave state information in server.
- You could use LRU replacement to re-use space.
- A frequently crashing client could dominate the state table, wiping out performance gains.
- Maintaining state information correctly and efficiently is complex.
www.tenouk.com
- Request processing time (rpt) = total time server uses to handle a single request.
- Observed response time (ort) = delay between issuing a request and receiving a response
- rpt <= ort.
- If the server has a large request queue, ort can be large.
- Iterative servers handle queued requests sequentially.
- With N items queued the average iterative (ort = N * rpt).
- With N items queued a concurrent server can do better.
- Implementations restrict queue size.
- Programmers need a concurrent design if a small queue is inadequate.
- The following is a sample of pseudo codes for iterative, connection oriented server.
create a socket
bind to a well-known port
place in passive mode
while (1)
{
Accept the next connection
while (client writes)
{
read a client request
perform requested action
send a reply
}
close the client socket
}
close the passive socket
Using INADDR_ANY
create a socket
bind to a well-known port
while (1)
{
read a request from some client
send a reply to that client
}
create a socket
bind to a well-known port
while (1)
{
read a request from some client
fork
if(child)
{
send a reply to that client
exit
}
}
www.tenouk.com
Concurrent, Connection-Oriented Server Algorithm
create a socket
bind to a well-known port
use listen to place in passive mode
while (1)
{
accept a client connection
fork
if (child)
{
communicate with new socket
close new socket
exit
}
else
{close new socket}
}
- The following is a sample of pseudo codes for concurrency using a single process.
create a socket
bind to a well-known port
while (1)
{
use select to wait for I/O
if(original socket is ready)
{
accept() a new connection and add to read list
}
else if (a socket is ready for read)
{
read data from a client
if(data completes a request)
{
do the request
if(reply needed) add socket to write list
}
}
else if (a socket is ready for write)
{
write data to a client
if(message is complete)
{
remove socket from write list
}
else
{
adjust write parameters and leave in write list
}
}
}
- Iterative vs Concurrent.
www.tenouk.com
▪ Use multiple processes if each slave is isolated or if you have multiple CPUs.
- Connection-Oriented vs Connectionless.
u_short portbase = 0;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
/* Allocate a socket */
s = socket(PF_INET, type, ppe->p_proto);
if(s < 0)
errexit("can't create socket: %s\n", strerror(errno));
A TIME Server
www.tenouk.com
{
struct sockaddr_in fsin;
char *service = "time";
char buf[1];
int sock;
time_t now;
int alen;
sock = passiveUDP(service);
while (1)
{
alen = sizeof(fsin);
if(recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &alen) < 0)
errexit("recvfrom: %s\n", strerror(errno));
time(&now);
now = htonl((u_long)now);
sendto(sock, (char *)&now, sizeof(now), 0, (struct sockaddr *)&fsin, sizeof(fsin));
}
}
A DAYTIME Server
while (1) {
ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
if(ssock < 0)
errexit("accept failed: %s\n", strerror(errno));
TCPdaytimed(ssock);
close(ssock);
}
}
time(&now);
pts = ctime(&now);
write(fd, pts, strlen(pts));
return;
}
www.tenouk.com
- An iterative server may block for excessive time periods.
- An example is an echo server. A client could send many megabytes blocking other clients for
substantial periods.
- A concurrent echo server could handle multiple clients simultaneously. Abusive clients would not
affect polite clients as much.
- The following is a sample codes for concurrent Echo server using fork().
signal(SIGCHLD, reaper);
while (1)
{
alen = sizeof(fsin);
ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
if(ssock < 0) {
if(errno == EINTR)
continue;
errexit("accept: %s\n", strerror(errno));
}
switch (fork())
{
/* child */
case 0:
close(msock);
exit(TCPechod(ssock));
/* parent */
default:
close(ssock);
break;
case -1:
errexit("fork: %s\n", strerror(errno));
}
}
}
Data-driven Processing
www.tenouk.com
- Arrival of data triggers processing.
- A message is typically a request.
- Server replies and awaits additional requests.
- If processing time is small, the requests may be possible to handle sequentially.
- Timesharing would be necessary only when the processing load is too high for sequential processing.
- Timesharing with multiple slaves is easier.
- A process calls select to wait for one (or more) of a collection of open files (or sockets) to be ready for
I/O.
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval
*timeout);
- The following is a sample codes for Echo server using a single process.
nfds = getdtablesize();
FD_ZERO(&afds);
FD_SET(msock, &afds);
while (1) {
memcpy(&rfds, &afds, sizeof(rfds));
if(select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
errexit("select: %s\n", strerror(errno));
if(FD_ISSET(msock, &rfds))
{
int ssock;
alen = sizeof(fsin);
ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
if(ssock < 0)
errexit("accept: %s\n", strerror(errno));
FD_SET(ssock, &afds);
}
for(fd=0; fd < nfds; ++fd)
if(fd != msock && FD_ISSET(fd, &rfds))
if(echo(fd) == 0)
{
(void) close(fd);
FD_CLR(fd, &afds);
}
}
}
www.tenouk.com
char buf[BUFSIZ];
int cc;
Multiprotocol Servers
- Using separate UDP and TCP servers gives the system administrator more flexibility.
- Using separate servers result in 2 moderately simple servers.
- Using one server eliminates duplicate code simplifying software maintenance.
- Using one server reduces the number of active processes.
FD_ZERO(&rfds);
while (1) {
FD_SET(tsock, &rfds);
FD_SET(usock, &rfds);
if(select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
errexit("select error: %s\n", strerror(errno));
if(FD_ISSET(tsock, &rfds))
{
/* TCP slave socket */
int ssock;
alen = sizeof(fsin);
ssock = accept(tsock, (struct sockaddr *)&fsin, &alen);
if(ssock < 0)
errexit("accept failed: %s\n", strerror(errno));
daytime(buf);
(void) write(ssock, buf, strlen(buf));
(void) close(ssock);
}
if(FD_ISSET(usock, &rfds))
{
alen = sizeof(fsin);
if(recvfrom(usock, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &alen) < 0)
errexit("recvfrom: %s\n", strerror(errno));
daytime(buf);
(void) sendto(usock, buf, strlen(buf), 0, (struct sockaddr *)&fsin, sizeof(fsin));
}
}
}
www.tenouk.com
(void) time(&now);
sprintf(buf, "%s", ctime(&now));
}
Multiservice Servers
- Fewer processes.
- Less memory.
- Less code duplication.
- Server complexity is really a result of accepting connections and handling concurrency.
- Having one server means the complex code does not need to be replicated.
- Server opens multiple passive TCP sockets each bound to a different port.
- Server keeps an array of function pointers to associate each socket with a service functions.
- Server uses select to determine which socket (port) to service next.
- When a connection is ready, server calls accept to start handling a connection.
- Server calls the proper service function.
- Master uses select to wait for connections over a set of passive TCP sockets.
- Master forks after accept.
- Slave handles communication with the client.
- Master uses select to wait for connections over a set of passive TCP sockets.
- After each accepts the new socket is added to the fd_set(s) as needed to handle client
communication.
- Complex if the client protocols are not trivial.
- Master uses select() to wait for connections over a set of passive TCP sockets.
- Master forks after accept.
- Child process uses execve to start a slave program to handle client communication.
- Different protocols are separated making it simpler to maintain.
- Changes to a slave program can be implemented without restarting the master.
- Master uses select to wait for connections over a set of passive TCP sockets.
- In addition the fd_set includes a set of UDP sockets awaiting client messages.
- If a UDP message arrives, the master calls a handler function which formulates and issues a reply.
- If a TCP connection is needed the master calls accept.
- For simpler TCP connections, the master can handle read and write requests iteratively.
- The master can also use select.
- Lastly the master can use fork and let the child handle the connection.
www.tenouk.com
struct service {
char *sv_name;
char sv_useTCP;
int sv_sock;
int (*sv_func)(int);
};
nfds = 0;
FD_ZERO(&afds);
for(psv = &svent[0]; psv->sv_name; ++psv)
{
if(psv->sv_useTCP)
psv->sv_sock = passiveTCP(psv->sv_name, QLEN);
else
psv->sv_sock = passiveUDP(psv->sv_name);
fd2sv[psv->sv_sock] = psv;
nfds = MAX(psv->sv_sock+1, nfds);
FD_SET(psv->sv_sock, &afds);
}
while (1) {
memcpy(&rfds, &afds, sizeof(rfds));
if(select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
{
if(errno == EINTR)
continue;
errexit("select error: %s\n", strerror(errno));
}
for(fd=0; fd<nfds; ++fd)
{
if(FD_ISSET(fd, &rfds))
{
psv = fd2sv[fd];
if(psv->sv_useTCP)
doTCP(psv);
else
psv->sv_func(psv->sv_sock);
}
}
}
}
alen = sizeof(fsin);
ssock = accept(psv->sv_sock, (struct sockaddr *)&fsin, &alen);
if(ssock < 0)
errexit("accept: %s\n", strerror(errno));
switch (fork())
{
case 0:
break;
case -1:
www.tenouk.com
errexit("fork: %s\n", strerror(errno));
default:
(void) close(ssock);
/* parent */
return;
}
/* child */
for(fd = NOFILE; fd >= 0; --fd)
if(fd != ssock) (void) close(fd);
exit(psv->sv_func(ssock));
}
Concurrency vs Iteration
Level of Concurrency
- OS can run out of resources such as memory, processes, sockets, buffers causing blocking, thrashing,
crashing...
- Demand for one service can inhibit others e.g. web server may prevent other use.
- Over-use can limit performance e.g. ftp server could be so slow that clients cancel requests wasting
time spent doing a partial transfer.
Cost of Concurrency
- Assuming a forking concurrent server, each connection requires time for a process creation (c).
- Each connection also requires some time for processing requests (p).
- Consider 2 requests arriving at the same time.
- Iterative server completes both at time 2p.
- Concurrent server completes both perhaps at time 2c+p.
- If p < 2c the iterative server is faster.
- The situation can get worse with more requests.
- The number of active processes can exceed the CPU capacity.
- Servers with heavy loads generally try to dodge the process creation cost.
www.tenouk.com
- Due to child processes inheriting the parent's passive socket, the slaves can all wait in accept on the
same socket.
- For UDP, the slaves can all call recvfrom on the same socket.
- To avoid problems like memory leaks, the slaves can be periodically replaced.
- For UDP, bursts can overflow buffers causing data loss. Pre-allocation can limit this problem.
Dynamic Pre-allocation
- Pre-allocation can cause extra processing time if many slaves are all waiting on the same socket.
- If the server is busy, it can be better to have many slaves pre-allocated.
- If the server is idle, it can be better to have very few slaves pre-allocated.
- Some servers (Apache) adjust the level of concurrency according to service demand.
Delayed Allocation
- Rather than immediately forking, the master can quickly examine a request.
- It may be faster for some requests to handle them in the master rather than forking.
- Longer requests may be more appropriate to handle in a child process.
- If it is hard to quickly estimate processing time, the server can set a timer to expire after a small time
and then fork to let a child finish the request.
Client Concurrency
------------------------Program Examples----------------------
DNS
- DNS stands for "Domain Name System" (for Windows implementation it is called Domain Name
Service). For socket it has three major components:
▪ Domain name space and resource records: Specifications for a tree-structured name space and the
data associated with the names.
▪ Name servers: Server programs that hold information about the domain tree structure and that set
information.
▪ Resolvers: Programs that extract information from name servers in response to client requests.
- DNS used to translate the IP address to domain name and vice versa. This way, when someone enters:
telnet serv.google.com
- telnet can find out that it needs to connect() to let say, "198.137.240.92". To get these
information we can use gethostbyname():
#include <netdb.h>
- As you see, it returns a pointer to a struct hostent, and struct hostent is shown below:
struct hostent
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
www.tenouk.com
Member Description
h_name Official name of the host.
h_aliases A NULL-terminated array of alternate names for the host.
h_addrtype The type of address being returned; usually AF_INET.
h_length The length of the address in bytes.
A zero-terminated array of network addresses for the host. Host
h_addr_list
addresses are in Network Byte Order.
h_addr The first address in h_addr_list.
Table 40.1
- gethostbyname() returns a pointer to the filled struct hostent, or NULL on error but errno
is not set, h_errno is set instead.
- As said before in implementation we use Domain Name Service in Windows and BIND in Unix/Linux.
Here, we configure the Forward Lookup Zone for name to IP resolution and Reverse Lookup Zone for
the reverse.
- The following is a program example using the gethostname().
/*****getipaddr.c ******/
/****a hostname lookup program example******/
#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>
www.tenouk.com
- Compile and link the program.
- Run the program. Because of the server used in this testing is using public IP address, we can test it
querying the public domain such as www.yahoo.com :o).
- With gethostbyname(), you can’t use perror() to print error message since errno is not used
instead, call herror().
- You simply pass the string that contains the machine name ("www.google.com") to
gethostbyname(), and then grab the information out of the returned struct hostent.
- The only possible weirdness might be in the printing of the IP address. Here, h->h_addr is a
char*, but inet_ntoa() wants a struct in_addr passed to it. So we need to cast h-
>h_addr to a struct in_addr*, then dereference it to get the data.
- Just about everything on the network deals with client processes talking to server processes and vice-
versa. For example take a telnet.
- When you telnet to a remote host on port 23 at client, a program on that server normally called
telnetd (telnet daemon), will respond. It handles the incoming telnet connection, sets you up with a
login prompt, etc. In Windows this daemon normally called a service. The daemon or service must be
running in order to do the communication.
- Note that the client-server pair can communicate using SOCK_STREAM, SOCK_DGRAM, or anything
else (as long as they’re using the same protocol). Some good examples of client-server pairs are
telnet/telnetd, ftp/ftpd, or bootp/bootpd. Every time you use ftp, there’s a remote
program, ftpd that will serve you.
- Often, there will only be one server, and that server will handle multiple clients using fork() etc. The
basic routine is: server will wait for a connection, accept() it and fork() a child process to handle
it. The following program example is what our sample server does.
- What this server does is send the string "This is a test string from server!" out over a
stream connection.
www.tenouk.com
- To test this server, run it in one window and telnet to it from another window or run it in a server and
telnet to it from another machine with the following command.
- Where the_remote_hostname is the name of the machine you’re running it on. The following is
the server source code:
void sigchld_handler(int s)
{
while(wait(NULL) > 0);
}
www.tenouk.com
if(listen(sockfd, BACKLOG) == -1)
{
perror("Server-listen() error");
exit(1);
}
printf("Server-listen() is OK...Listening...\n");
/*accept() loop*/
while(1)
{
sin_size = sizeof(struct sockaddr_in);
if((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1)
{
perror("Server-accept() error");
continue;
}
else
printf("Server-accept() is OK...\n");
printf("Server-new socket, new_fd is OK...\n");
printf("Server: Got connection from %s\n", inet_ntoa(their_addr.sin_addr));
[bodo@bakawali testsocket]$
- Verify that the program is running in the background. You may do this from another terminal.
www.tenouk.com
[bodo@bakawali testsocket]$ bg
[1]+ ./serverprog &
- Verify that the program/process is listening on the specified port, waiting for connection.
- Then, trying the telnet. Open another terminal, telnet itself with the specified port number. Here we
use the server name, bakawali. When the string is displayed press the Escape character Ctrl+] ( ^]
). Then we have a real telnet session.
...
telnet> quit
Connection closed.
[bodo@bakawali ~]$
- If we do not stop the server program/process (Ctrl+Z), at the server terminal the following messages
should be displayed. Press Enter (Carriage Return) key back to the prompt.
- To stop the process just issue a normal kill command. Before that verify again.
www.tenouk.com
[bodo@bakawali testsocket]$ netstat -a | grep 3490
tcp 0 0 *:3490 *:* LISTEN
[bodo@bakawali testsocket]$
- The server program seems OK. Next section is a client program, clientprog.c that we will use to
test our server program, serverprog.c.
- The sigaction() code is responsible for cleaning the zombie processes that appear as the
fork()ed child processes. You will get the message from this server by using the client program
example presented in the next section.
- This client will connect to the host that you specify in the command line, with port 3490. It will get the
string that the previous server sends. The following is the source code.
www.tenouk.com
//host byte order
their_addr.sin_family = AF_INET;
//short, network byte order
printf("Server-Using %s and port %d...\n", argv[1], PORT);
their_addr.sin_port = htons(PORT);
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
//zero the rest of the struct
memset(&(their_addr.sin_zero), '\0', 8);
buf[numbytes] = '\0';
printf("Client-Received: %s", buf);
printf("Client-Closing sockfd\n");
close(sockfd);
return 0;
}
- Run the program with server IP address or name as an argument. Here we use IP address.
- Make sure your previous serverprog program is running. We will connect using the same server.
You can try running the server and client program at different machines.
www.tenouk.com
Server-bind() is OK...
Server-listen() is OK...Listening...
Server-sigaction() is OK...
Server-accept() is OK...
Server-new socket, new_fd is OK...
Server: Got connection from 203.106.93.94
Server-send() is OK...!
Server-new socket, new_fd closed successfully...
- Well, our server and client programs work! Here we run the server program and let it listens for
connection. Then we run the client program. They got connected!
- Notice that if you don’t run the server before you run the client, connect() returns "Connection
refused" message as shown below.
- The following program examples use the UDP, the connectionless datagram. The senderprog.c
(client) is sending a message to receiverprog.c (server) that acts as listener.
www.tenouk.com
if((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1, 0, (struct sockaddr *)&their_addr,
&addr_len)) == -1)
{
perror("Server-recvfrom() error lol!");
/*If something wrong, just exit lol...*/
exit(1);
}
else
{
printf("Server-Waiting and listening...\n");
printf("Server-recvfrom() is OK...\n");
}
if(close(sockfd) != 0)
printf("Server-sockfd closing failed!\n");
else
printf("Server-sockfd successfully closed!\n");
return 0;
}
- Run the program, and then verify that it is running in background, start listening, waiting for
connection.
- This is UDP server, trying telnet to this server will fail because telnet uses TCP instead of UDP.
- Notice that in our call to socket() we’re using SOCK_DGRAM. Also, note that there’s no need to
listen() or accept(). The following is the source code for senderprog.c (the client).
www.tenouk.com
struct sockaddr_in their_addr;
struct hostent *he;
int numbytes;
if (argc != 3)
{
fprintf(stderr, "Client-Usage: %s <hostname> <message>\n", argv[0]);
exit(1);
}
/* get the host info */
if ((he = gethostbyname(argv[1])) == NULL)
{
perror("Client-gethostbyname() error lol!");
exit(1);
}
else
printf("Client-gethostname() is OK...\n");
if (close(sockfd) != 0)
printf("Client-sockfd closing is failed!\n");
else
printf("Client-sockfd successfully closed!\n");
return 0;
}
[bodo@bakawali testsocket]$ ./senderprog 203.106.93.94 "Testing UDP datagram message from client"
Client-gethostname() is OK...
Client-socket() sockfd is OK...
Using port: 4950
Server-Waiting and listening...
Server-recvfrom() is OK...
Server-Got packet from 203.106.93.94
Server-Packet is 42 bytes long
Server-Packet contains "Testing UDP datagram message from client"
Server-sockfd successfully closed!
Client-sendto() is OK...
www.tenouk.com
sent 42 bytes to 203.106.93.94
Client-sockfd successfully closed!
[1]+ Done ./receiverprog
[bodo@bakawali testsocket]$
- Here, we test the UDP server and the client using the same machine. Make sure there is no restriction
such as permission etc. for the user that run the programs.
- To make it really real, may be you can test these programs by running receiverprog on some
machine, and then run senderprog on another. If there is no error, they should communicate.
- If senderprog calls connect() and specifies the receiverprog’s address then the
senderprog may only sent to and receive from the address specified by connect().
- For this reason, you don’t have to use sendto() and recvfrom(); you can simply use send()
and recv().
Blocking
- In a simple word 'block' means sleep but in a standby mode. You probably noticed that when you run
receiverprog, previously, it just sits there until a packet arrives.
- What happened is that it called recvfrom(), there was no data, and so recvfrom() is said to
"block" (that is, sleep there) until some data arrives. The socket functions that can block are:
accept()
read()
readv()
recv()
recvfrom()
recvmsg()
send()
sendmsg()
sendto()
write()
writev()
- The reason they can do this is because they’re allowed to. When you first create the socket descriptor
with socket(), the kernel sets it to blocking.
- If you don’t want a socket to be blocking, you have to make a call to fcntl() something like the
following:
#include <unistd.h>
#include <fcntl.h>
...
...
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
...
...
- By setting a socket to non-blocking, you can effectively 'poll' the socket for information. If you try to
read from a non-blocking socket and there’s no data there, it’s not allowed to block, it will return -1
and errno will be set to EWOULDBLOCK.
- Generally speaking, however, this type of polling is a bad idea. If you put your program in a busy-wait
looking for data on the socket, you’ll suck up CPU time.
- A more elegant solution for checking to see if there’s data waiting to be read comes in the following
section on select().
- One traditional way to write network servers is to have the main server block on accept(), waiting
for a connection. Once a connection comes in, the server fork()s, then the child process handles the
connection and the main server is able to service new incoming requests.
- With select(), instead of having a process for each request, there is usually only one process that
multiplexes all requests, servicing each request as much as it can.
- So one main advantage of using select() is that your server will only require a single process to
handle all requests. Thus, your server will not need shared memory or synchronization primitives for
different tasks to communicate.
www.tenouk.com
- As discussed before we can use the non-blocking sockets’ functions but it is CPU intensive.
- One major disadvantage of using select(), is that your server cannot act like there's only one client,
like with a fork()'ing solution. For example, with a fork()'ing solution, after the server fork()s,
the child process works with the client as if there was only one client in the universe, the child does not
have to worry about new incoming connections or the existence of other sockets.
- With select(), the programming isn't as transparent. The prototype is as the following:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
- The function monitors "sets" of file descriptors; in particular readfds, writefds, and
exceptfds. If you want to see if you can read from standard input and some socket descriptor,
sockfd, just add the file descriptors 0 and sockfd to the set readfds.
- The parameter numfds should be set to the values of the highest file descriptor plus one. In this
example, it should be set to sockfd+1, since it is assuredly higher than standard input that is 0.
- When select() returns, readfds will be modified to reflect which of the file descriptors you have
selected which is ready for reading. You can test them with the macro FD_ISSET() listed below.
- Let see how to manipulate these sets. Each set is of the type fd_set. The following macros operate
on this type:
- select() works by blocking until something happens on a file descriptor/socket. The 'something' is
the data coming in or being able to write to a file descriptor, you tell select() what you want to be
woken up by. How do you tell it? You fill up an fd_set structure with some macros.
- Most select()based servers look quite similar:
▪ Fill up an fd_set structure with the file descriptors you want to know when data comes in on.
▪ Fill up an fd_set structure with the file descriptors you want to know when you can write on.
▪ Call select() and block until something happens.
▪ Once select() returns, check to see if any of your file descriptors was the reason you woke
up. If so, 'service' that file descriptor in whatever particular way your server needs to (i.e. read in
a request for a Web page).
▪ Repeat this process forever.
- Sometimes you don’t want to wait forever for someone to send you some data. Maybe every 60
seconds you want to print something like "Processing..." to the terminal even though nothing has
happened.
- The timeval structure allows you to specify a timeout period. If the time is exceeded and
select() still hasn’t found any ready file descriptors, it’ll return, so you can continue processing.
- The struct timeval has the following fields:
struct timeval
{
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};
- Just set tv_sec to the number of seconds to wait, and set tv_usec to the number of microseconds to
wait. There are 1,000,000 microseconds in a second. Also, when the function returns, timeout
might be updated to show the time still remaining.
- Standard UNIX time slice is around 100 milliseconds, so you might have to wait that long no matter
how small you set your struct timeval.
- If you set the fields in your struct timeval to 0, select() will timeout immediately,
effectively polling all the file descriptors in your sets. If you set the parameter timeout to NULL, it
will never timeout, and will wait until the first file descriptor is ready.
- Finally, if you don’t care about waiting for a certain set, you can just set it to NULL in the call to
select().
- The following code snippet waits 5.8 seconds for something to appear on standard input.
www.tenouk.com
/*selectcp.c - a select() demo*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
/* file descriptor for standard input */
#define STDIN 0
FD_ZERO(&readfds);
FD_SET(STDIN, &readfds);
/* don’t care about writefds and exceptfds: */
select(STDIN+1, &readfds, NULL, NULL, &tval);
if (FD_ISSET(STDIN, &readfds))
printf("A key was pressed lor!\n");
else
printf("Timed out lor!...\n");
return 0;
}
- Compile and link the program. Make sure there is no error :o).
- If you’re on a line buffered terminal, the key you hit should be RETURN or it will time out anyway.
- Now, some of you might think this is a great way to wait for data on a datagram socket and you are
right: it might be. Some Unices can use select() in this manner, and some can’t. You should see
what your local man page says on the matter if you want to attempt it.
- Some Unices update the time in your struct timeval to reflect the amount of time still remaining
before a timeout. But others do not. Don’t rely on that occurring if you want to be portable. Use
gettimeofday() if you need to track time elapsed.
- When a socket in the read set closes the connection, select() returns with that socket descriptor
set as "ready to read". When you actually do recv() from it, recv() will return 0. That’s how you
know the client has closed the connection.
- If you have a socket that is listen()ing, you can check to see if there is a new connection by putting
that socket’s file descriptor in the readfds set.
1. Check the best selling C/C++, Networking, Linux and Open Source books at Amazon.com.
www.tenouk.com