Professional Documents
Culture Documents
Patterns in C++
Category: Programming
Contents [show]
The Internet application layer combines the application, presentation and session layers of the
OSI model. Its in this layer where the Internet protocols HTTP, SSH, DNS, etc. are
implemented that directly interact with Internet applications.
At the bottom of the OSI stack is the datalink and physical layers which map to a single Network
Link layer in the Internet model. Network drivers are implemented here that provide the Network
layer with the means to send packets over physical network media such as Ethernet, PPP and
ADSL.
The Network and Transport layers are the same across both models. The Network layer in the
Internet model provides connectionless Internet protocol packet delivery, host IP addresses and
routing hosts and other networks. The ICMP, ARP and DHCP are implemented in the Network
layer on top of IP.
Both TCP and UDP protocols live in the transport layer and add the concept of ports to
differentiate applications running on a given host. TCP provides connection based, reliable
network communication and stream based data delivery services. Reliability is achieved through
retransmission of dropped packets. UDP provides connectionless and packet based delivery
where the data is delivered in datagrams packets with port numbers. UDP, like IP, gives only
best effort data delivery without retransmissions of dropped packets.
BSD Sockets is an API to the transport layer of the Internet Protocol Stack. It supports creating
both TCP and UDP network I/O.
Socket Workflow
To establish TCP connections the server host calls socket() to create a listening socket then
specifies the IP address and TCP port on which the server will receive connection requests with a
call to bind(). Calling listen() puts the server into listening mode which then blocks on the
accept() waiting for incoming connections.
The client connects to the server by calling socket() then connect() with a socket address that
includes the IP address and TCP port specifying used for the bind() call on the server. On the
server the accept() function returns with a connection socket descriptor when the clients
connection request is received.
After connecting the server blocks on a call to read() waiting for a client request. The client
calls write() to send a request then blocks on a call to read() waiting for the servers response.
When the server is done processing the request, it sends back a response to the client. The
exchange of requests and responses repeats until the client is done, at which time it closes the
connection. The server detects this event when read() returns 0. The server responds by closing
its end of the connection then returning to get another connection.
In most servers connections are accepted in one thread and a new thread or process is created to
handle each connection. To keep things simple the example here describes an iterative server
where each request is handled one at a time.
Provides network I/O mechanisms and returns IP address and TCP port of
peer applications.
TCPStream -
For the code examples in this blog, each of these classes has an include file (.h) and source file
(.cpp) of the same name. For example, tcpconnector.h and tcpconnector.cpp for
the TCPConnector class.
TCPStream Class
Interface
The TCPStream class provides methods to send and receive data over a TCP/IP connection. It
contains a connected socket descriptor and information about the peer either client or server
in the form of the IP address and TCP port. TCPStream includes simple get methods that return
address and port, but not the socket descriptor which is kept private. One of the advantages of
programming with objects is the ability to logically group data members and methods to avoid
exposing data, in this case the socket descriptor, to the calling program that it does not need to
see. Each connection is completely encapsulated in each TCPStream object.
TCPStream objects
are
the TCPStream constructors
Constructor
The constructor stores the connected socket descriptor then converts the socket information
structure fields to a peer IP address string and peer TCP port. These parameters can be inspected
with calls to TCPStream::getPeerIP() and TCPStream::getPeerPort().
1
<arpa/inet.h>
2 #include
#include "tcpstream.h"
3
4 TCPStream::TCPStream(int sd, struct sockaddr_in* address) : m_sd(sd) {
5
char ip[50];
inet_ntop(PF_INET, (struct in_addr*)&(address->sin_addr.s_addr),
6
ip, sizeof(ip)-1);
7
m_peerIP = ip;
8
m_peerPort = ntohs(address->sin_port);
9 }
10
Destructor
The destructor simply closes the connection.
1TCPStream::~TCPStream()
2{
close(m_sd);
3
}
4
TCPConnector Class
Interface
1
<netinet/in.h>
2 #include
#include "tcpstream.h"
3
4 class TCPConnector
5 {
public:
6
TCPStream* connect(int port, const char* server);
7
8
private:
9
int resolveHost(const char* host, struct in_addr* addr);
10};
11
Constructor/Destructor
The TCPConnector class does not use any member variables so the default constructor and
destructor generated by the C++ compiler are fine. No others are defined.
Connect to Server
[Lines 6-12] TCPConnector::connect() call takes a server host name or IP address string and
the server listening port as arguments. The server struct sockaddr_in sin_family is set to
PF_INET and the sin_port is set to the TCP port on which the server is listening for connections.
1 #include <string.h>
2 #include <netdb.h>
#include <arpa/inet.h>
3 #include "tcpconnector.h"
4
5 TCPStream* TCPConnector::connect(const char* server, int port)
6 {
struct sockaddr_in address;
7
8
memset (&address, 0, sizeof(address));
9
address.sin_family = AF_INET;
10
address.sin_port = htons(port);
11
if (resolveHostName(server, &(address.sin_addr)) != 0) {
inet_pton(PF_INET, server, &(address.sin_addr));
12
}
13
int sd = socket(AF_INET, SOCK_STREAM, 0);
14
if (::connect(sd, (struct sockaddr*)&address, sizeof(address)) != 0) {
15
return NULL;
}
16
return new TCPStream(sd, &address);
17
}
18
19
20
21
[Lines 13-15] TCPConnector::resolveHost() to convert the DNS host name string to an IP
address. If this call fails the assumption is made the server string contains an IP address and it is
converted to an IP address in network byte order.
[Lines 16] The first argument to socket() selects the protocol family and the second specifies
the nature of the network communication. Together PF_INET and SOCK_STREAM mandate the
TCP/IP protocol.
[Lines 17-20] We call ::connect() passing it the socket descriptor, pointer to the server struct
sockaddr_in structure, cast to a struct sockaddr pointer, and the length of the server address
structure. The ::connect() call is prefeced with the :: qualifier so the compiler does not confuse
this function with TCPConnector::connect(). If ::connect() succeeds a TCPStream object
is created with the connected socket descriptor and the server socket address information and a
pointer to the TCPStream object is returned to the caller.
TCPAcceptor Class
Interface
includes member variables for the listening socket descriptor, the socket address
information IP address and TCP port and a flag that indicates whether or not the
TCPAcceptor has started listening for connections.
TCPAcceptor
Two public methods are supported. One to start the listening and the other to accept connections.
1
2 #include <string>
3 #include <netinet/in.h>
4 #include "tcpstream.h"
5
using namespace std;
6
7 class TCPAcceptor
8 {
9
int m_lsd;
string m_address;
10
int m_port;
11
12
bool m_listening;
13
14 public:
TCPAcceptor(int port, const char* address="");
15
~TCPAcceptor();
16
17
int
start();
18
TCPStream* accept();
19
20 private:
TCPAcceptor() {}
21
};
22
23
Constructor
The constructor sets the member variables to as shown here. Setting m_lsd indicates that the
lisetning socket has not been created.
1#include <stdio.h>
2#include <string.h>
3#include <arpa/inet.h>
4#include "tcpacceptor.h"
5
6TCPAcceptor::TCPAcceptor(int port, const char* address)
: m_lsd(0), m_port(port), m_address(address), m_listening(false) {}
7
Destructor
If the listening socket has been created then it is closed in the destructor.
1TCPAcceptor::~TCPAcceptor()
2{
if (m_lsd > 0) {
3
close(m_lsd);
4
}
5
}
6
34
35
[Line 7] First we create a listening socket descriptor for TCP/IP. The socket() call for servers is
the same as it is for clients.
[Line 9-12] Next we initialize a socket address structure setting the protocol family PF_INET
and the listening TCP port.
[Line 13-18] If the server listening IP address has m_address has been set, inet_ntop() is
called to convert it to a numerical IP address in network byte order. If inet_ntop() fails then the
socket listening address is set to any IP address meaning the server will listening for connections
on all the network interfaces.
[Line 20-21] Normally when you stop a server listening on a given IP address and port, it takes a
few seconds before you can starting listening on the same IP address and port when you restart
your server. To disable this condition and make it possible to immediately resue a listening port,
we set the SO_REUSEADDR socket option for the listening socket before calling bind().
[Line 23-27] Bind the listening socket address to the socket descriptor. If bind() fails display
and error message then return value returned by bind().
[Line 28-34] Turn on server listening with the listen() function. The second argument of this
function sets the number of connection requests TCP will queue. This may not be supported for
your particular operating system. If listen() fails, display an error message. Otherwise, set the
m_listening flag to true and return the listen() call return value
12
13
14
15}
16
}
return new TCPStream(sd, &address);
[Lines 11-15] When a connection with a client is established, the socket address structure is
populated with the clients socket information and ::accept() returns 0. Then a TCPStream
Test Applications
Echo Server
First lets build a server with the TCPAcceptor class. To keep things simple well just make an
iterative server that handles one connection at a time. The server will be defined in the file
server.cpp.
1 #include <stdio.h>
2 #include <stdlib.h>
#include "tcpacceptor.h"
3
4 int main(int argc, char** argv)
5 {
if (argc < 2 || argc > 4) {
6
printf("usage: server <port> [<ip>]\n");
7
exit(1);
8
}
9
10
TCPStream* stream = NULL;
11
TCPAcceptor* acceptor = NULL;
if (argc == 3) {
12
acceptor = new TCPAcceptor(atoi(argv[1]), argv[2]);
13
}
14
else {
15
acceptor = new TCPAcceptor(atoi(argv[1]));
}
16
if (acceptor->start() == 0) {
17
while (1) {
18
stream = acceptor->accept();
19
if (stream != NULL) {
20
size_t len;
char line[256];
21
while ((len = stream->receive(line, sizeof(line))) > 0) {
22
line[len] = NULL;
23
printf("received - %s\n", line);
24
stream->send(line, len);
25
}
delete stream;
26
}
27
}
28
}
29
perror("Could not start the server");
30
31
32
33
34}
35
36
37
exit(-1);
[Lines 5-10] The server accepts the listening TCP port and optionally the listening IP Address on
the command line. If the number of arguments is not correct an error message is displayed
informing the user how to correctly invoke the application.
[Lines 12-20] The TCPAcceptor object is created with the command line arguments. Minimally
the IP address must be specified. Then the server starts listening for connections.
[Lines 21-32] If the call to TCPAcceptor::start() is successful, the server continually and
indefinitely accepts connections from clients and processes each connection one at a time.
Processing consists of getting a string of bytes from the client, displaying the string and returning
it to the client. The string of bytes is NULL terminated at the index in the receive buffer equal to
the value returned by the receive operation. This is repeated until the client closes the connection
indicated by a return value of 0 from TCPStream::receive(). Deleting the stream object closes
the connection on the server side.
Echo Client
The client application takes the server TCP port and IP address on the command line. For each
connection a string is displayed and sent to the server, the echoed string is received back and
displayed, then the connection is closed. The client will be defined in the file client.cpp.
1 #include <stdio.h>
2 #include <stdlib.h>
#include <string>
3 #include "tcpconnector.h"
4
5 using namespace std;
6
7 int main(int argc, char** argv)
8 {
if (argc != 3) {
9
printf("usage: %s <port> <ip>\n", argv[0]);
10
exit(1);
11
}
12
int len;
13
string message;
14
char line[256];
15
TCPConnector* connector = new TCPConnector();
16
TCPStream* stream = connector->connect(argv[2], atoi(argv[1]));
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38}
39
40
41
if (stream) {
message = "Is there life on Mars?";
stream->send(message.c_str(), message.size());
printf("sent - %s\n", message.c_str());
len = stream->receive(line, sizeof(line));
line[len] = NULL;
printf("received - %s\n", line);
delete stream;
}
stream = connector->connect(argv[2], atoi(argv[1]));
if (stream) {
message = "Why is there air?";
stream->send(message.c_str(), message.size());
printf("sent - %s\n", message.c_str());
len = stream->receive(line, sizeof(line));
line[len] = NULL;
printf("received - %s\n", line);
delete stream;
}
exit(0);
make -f Makefile.client
make -f Makefile.server
First run the server on port 9999 and localhost in a terminal window:
$ server 9999 localhost
In another terminal window run the client and you should get the following output:
$ client 9999 localhost
sent - Is there life on Mars?
received - Is there life on Mars?
sent - Why is there air?
received - Why is there air?