Menu
Network Story 1Network Story 2Network Story 3Network Story 4Network Story 5Network Story 6Socket Example 1Socket Example 2Socket Example 3Socket Example 4Socket Example 5Socket Example 6Socket Example 7Advanced TCP/IP 1Advanced TCP/IP 2Advanced TCP/IP 3Advanced TCP/IP 4Advanced TCP/IP 5 | My Training Period: xx hours
This is a continuation from Part I series, Introduction to Socket Programming. Working program examples if any compiled usinggcc, tested using the public IPs, run on Linux/Fedora Core 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.
The abilities that supposed to be acquired:
Client Design Consideration
Identifying a Server's Address
Looking Up a Computer Name
|
Looking Up a Port Number by Name
NAME
getservbyname() - get service entry
SYNOPSIS
#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
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.
Looking Up a Protocol by Name
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;
}
p_proto: the protocol number (can be used in socket call).
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);
sockfd is the descriptor of the connected stream socket.
addr is a pointer to a struct sockaddr (or a struct sockaddr_in) that will hold the information about the other side of the connection.
addrlen is a pointer to an int, which should be initialized to sizeof(struct sockaddr).
The function returns -1 on error and sets errno accordingly.
Once you have their address, you can use inet_ntoa() or gethostbyaddr() to print or get more information.
Allocating a Socket
#include <sys/types.h>
#include <sys/socket.h>
int s;
s = socket(PF_INET, SOCK_STREAM, 0);
Specifying PF_INET and SOCK_STREAM leaves the protocol parameter irrelevant.
Choosing a Local Port Number
The server will be using a well-known port.
Once a client port is set, the server will be aware as needed.
You could bind to a random port above 1023.
A simpler choice is to leave out the bind call.
connect() will choose a local port if required.
Connecting to a Server with TCP
connect().
NAME
connect - initiate a connection on a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int connect(int s, struct sockaddr *serv_addr, int addrlen);
RETURN VALUE
If the connection or binding succeeds, zero is returned.
On error, -1 is returned, and errno is set appropriately.
We will use a sockaddr_in structure (possibly cast).
After connect, s is available to read/write.
Communicating with TCP
Code segment example:
char *req = "send cash";
char buf[100], *b;
write (s, req, strlen(req));
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.
Closing a TCP Connection
In the simplest case close works well.
Sometimes it is important to tell the server that a client will send no more requests, while still keeping the socket available for reading.
res = shutdown(s, 1);
The 1 means no more writes will happen.
The server detects end of file on the socket.
After the server sends all the replies it can close.
Connected vs Unconnected UDP Sockets
Communicating with UDP
|
Client Example – Some variations
To make a connection, a client must:
select UDP or TCP...
determine a server's IP address...
determine the proper port...
make the socket call...
make the connect call...
Frequently the calls are essentially the same.
A library offers normal capability with a simple interface.
connectTCP()
The following is a code segment example using the connectTCP() function.
int connectTCP(const char *host, const char *service)
{ return connectsock(host, service, "tcp"); }
connectUDP()
The following is a code segment example using the connectUDP() function.
int connectUDP(const char *host, const char *service)
{ return connectsock(host, service, "udp"); }
connectsock()
The following is a code segment example using the connectsock() function.
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;
/* Map service name to port number */if(pse = getservbyname(service, transport))
sin.sin_port = pse->s_port;
else if ((sin.sin_port = htons((u_ short)atoi(service))) == 0)
errexit("can't get \"%s\" service entry\n", service);
/* Map host name to IP address, allowing for dotted decimal */
if(phe = gethostbyname(host))
memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
else if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE)
errexit("can't get \"%s\" host entry\n", host);
/* Map transport protocol name to protocol number */
if((ppe = getprotobyname(transport)) == 0)
errexit("can't get \"%s\" protocol entry\n", transport);
/* Use protocol to choose a socket type */
if(strcmp(transport, "udp") == 0)type = SOCK_DGRAM;
else
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));
/* Connect the socket */
if(connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
errexit("can't connect to %s.%s: %s\n", host, service, strerror(errno));
return s;
}
A TCP DAYTIME Client
DAYTIME service prints date and time.
TCP version sends upon connection. Server reads no client data.
UDP version sends upon receiving any message.
The following is a code segment example implementing the TCP Daytime.
#define LINELEN 128
int main(int argc, char *argv[ ])
{
/* host to use if none supplied */
char *host = "localhost";
/* default service port */
char *service = "daytime";
switch (argc) {
case 1:
host = "localhost";
break;
case 3:
service = argv[2];
/* FALL THROUGH */
case 2:
host = argv[1];
break;
default:
fprintf(stderr, "usage: TCPdaytime [host [port]]\n");exit(1);}
TCPdaytime(host, service);
exit(0);
}
void TCPdaytime(const char *host, const char *service)
{
/* buffer for one line of text */
char buf[LINELEN+1];
/* socket, read count */
int s, n;
s = connectTCP(host, service);
while((n = read(s, buf, LINELEN)) > 0)
{
/* ensure null-terminated */
buf[n] = '\0';
(void) fputs(buf, stdout);
}
}
A UDP TIME Client
The TIME service is for computers.
Returns seconds since 1-1-1900.
Useful for synchronizing and time-setting.
TCP and UDP versions return time as an integer.
Need to use ntohl to convert.
The following is a code segment example implementing UDP Time.
#define MSG "What time is it?\n"
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);
(void) write(s, MSG, strlen(MSG));
/* Read the time */
n = read(s, (char *)&now, sizeof(now));
if(n < 0)
errexit("read failed: %s\n", strerror(errno));
/* put in host byte order */
now = ntohl((u_long)now);
printf("%s", ctime(&now));
exit(0);
}
TCP and UDP Echo Clients
main() is like the other clients.
TCPecho() function
The following is a code segment example for using the TCPecho() function.
int TCPecho(const char *host, const char *service)
{
char buf[LINELEN+1]; /* buffer for one line of text */
int s, n; /* socket descriptor, read count*/
int outchars, inchars; /* characters sent and received */
s = connectTCP(host, service);
while(fgets(buf, sizeof(buf), stdin))
{
/* insure line null-terminated */
buf[LINELEN] = '\0';
outchars = strlen(buf);
(void) write(s, buf, outchars);
/* 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.
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);
while(fgets(buf, sizeof(buf), stdin))
{
/* ensure null-terminated */
buf[LINELEN] = '\0';
nchars = strlen(buf);
(void) write(s, buf, nchars);
if(read(s, buf, nchars) < 0)
errexit("socket read failed: %s\n", strerror(errno));
fputs(buf, stdout);
}
}
Concurrent vs Iterative Servers
An iterative server processes one request at a time.
A concurrent server processes multiple requests at a time real or apparent concurrency.
A single process can use asynchronous I/O to achieve concurrency.
Multiple server processes can achieve concurrency.
Concurrent servers are more complex.
Iterative servers cause too much blocking for most applications.
Avoiding blocking results in better performance.
Connection-Oriented vs Connectionless Servers
TCP provides a connection-oriented service.
UDP provides a connectionless service.
Connection-oriented Servers
Easy to program, since TCP takes care of communication problems.
Also a single socket is used for a single client exclusively (connection).
Handling multiple sockets is intense juggling.
For trivial applications the 3-way handshake is slow compared to UDP.
Resources can be tied up if a client crashes.
Connectionless Servers
No resource depletion problem.
Server/client must cope with communication errors. Usually client sends a request and resends if needed.
Selecting proper timeout values is difficult.
UDP allows broadcast/multicast.
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.
Request Processing Time
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.
Iterative, Connection-Oriented Server Algorithm
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
Some server computers have multiple IP addresses.
A socket bound to one of these will not accept connections to another address.
Frequently you prefer to allow any one of the computer's IP addresses to be used for connections.
Use INADDR_ANY (0L) to allow clients to connect using any one of the host's IP addresses.
Iterative, Connectionless Server Algorithm
The following is a sample of pseudo codes for iterative, connectionless server.
create a socket
bind to a well-known port
while (1)
{
read a request from some client
send a reply to that client
}
recvfrom(s, buf, len, flags, from, fromlen)
sendto(s, buf, len, flags, to, to_len)
Concurrent, Connectionless Server Algorithm
The following is a sample of pseudo codes for concurrent, connectionless server.
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
}
}
Overhead of fork and exit is expensive.
Not used much.
Concurrent, Connection-Oriented Server Algorithm
The following is a sample of pseudo codes for connection-oriented server.
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}
}
Single program has master and slave code.
It is possible for slave to use execve.
Concurrency Using a Single Process
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
}
}
}
When to Use the Various Server Types
Iterative vs Concurrent.
Iterative server is simpler to write.
Concurrent server is faster.
Use iterative if it is fast enough.
Real vs Apparent Concurrency.
Writing a single process concurrent server is harder.
Use a single process if data must be shared between clients.
Use multiple processes if each slave is isolated or if you have multiple CPUs.
Connection-Oriented vs Connectionless.
Use connectionless if the protocol handles reliability.
Use connectionless on a LAN with no errors.
Avoiding Server Deadlock
Client connects but sends no request. Server blocks in read call.
Client sends request, but reads no replies. Server blocks in write call.
Concurrent servers with slaves are robust.
Continue on next Module…
Further reading and digging:
|Winsock & .NET |Winsock | < TCP/IP Socket Programming Interfaces (APIs) | Linux Socket Index | More Socket APIs & C Code Snippets > |