1. What are TCP Sockets?
2. TCP Clients and Servers
3. Socket Address Structures
4. TCP Library Calls
5. A Client-Server Echo Program
6. Exercise
What are TCP Sockets?
A TCP socket is defined as an endpoint for communication. A socket consists of the pair
A TCP connection consists of a pair of sockets. Sockets are distinguished by client and server sockets. A server listens on a port, waiting for incoming requests from clients.
For example, a web server listens at port 80 for incoming request from clients (web browsers). When a client wishes to make a connection with a server socket, the client is assigned a port from the local host. Suppose that client X (at IP address 146.86.3.15) wishes to browse a web page on the server 146.86.5.20. If the port the local host assigned client X is port 12345, the connection between the client and the server is uniquely identified by the socket pair
<146.86.3.15 : 12345, 146.86.5.20 : 80>
TCP Clients and Servers
The general order of library calls for a TCP client is as follows:
socket()
connect()
send() and/or recv()
close()
The general order of library calls for a TCP server is as follows:
socket()
bind()
listen()
accept()
send() and/or recv()
close()
Socket Address Structures
Internet socket addresses are stored in 16-byte structures of the type sockaddr_in defined as follows:
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
For Internet applications, the sin_family member is always AF_INET, the sin_port member is a 16-bit port number, and the sin_addr member is a 32-bit IP address. The in_addr structure has the following form
struct in_addr {
unsigned long s_addr;
};
Note that the in_addr struct is exactly 4 bytes long, which is the same size as an IP address. The IP address and port number are always stored in network (big-endian) byte order. For example:
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(9999)
sin.sin_addr.s_addr = inet_addr("128.227.224.3");
In the above code example, the structure sin holds the IP address 128.227.224.3, and references the port number 9999. Two utility functions are used to set these values. The function htons returns the integer argument passed into it in network byte order. The function inet_addr converts the string argument from a dotted-quad into a 32-bit integer. Its return value is also in network byte order.
TCP Library Calls
socket:
The socket library call has the following prototype:
int socket(int family, int type, int protocol);
In short, this function creates "an end point for communication". The return value from this function is a handle to a socket. This number is passed as a parameter to almost all of the other library calls. Since the focus of this document is on TCP/IP based sockets, the family parameter should be set to AF_INET. The type parameter can be either SOCK_STREAM (for TCP), or SOCK_DGRAM (for UDP). The protocol field is intended for specifying a specific protocol in case the network model support different types of stream and datagram models. However, TCP/IP only has one protocol for each, so this field should always be set to 0. Example to create a TCP socket:
int s;
s = socket(AF_INET, SOCK_STREAM, 0);
connect:
The connect library call has the following prototype:
int connect(int socket, struct sockaddr_in * address, int address_length);
This function establishes an Internet connection with the server at socket address address. The value of address_length is always sizeof(sockaddr_in). The connect function blocks until either the connection is successfully established, or an error occurs. If successful, the socket descriptor is now ready for reading and writing. Example of a connect call:
int c;
struct sockaddr_in server_address;
s = socket(...);
server_address.??? = ...;
c = connect(s, &server_address, sizeof(sockaddr_in));
bind:
Before sending and receiving data using a socket, it must first be associated with a local source port and a network interface address. The mapping of a socket to a TCP/UDP source port and IP address is called a "binding". The bind function call is used to declare the mapping between the socket, the TCP/UDP source port, and the network interface device. The prototype for bind is as follows:
bind(int socket, struct sockaddr_in *address, int address_length);
The first argument is a socket handle (the number returned from the socket function call). The second argument is a sockaddr_in structure. The sin_port field of the address argument is the local source port number associated with this socket. That is, for every "send" operation with this socket, the source port field in the TCP/UDP header gets set with this value. If specifying an exact source port is not required, setting this value to INADDR_ANY(0) allows the operating system to pick any available port number. The sin_addr field specifies which network interface device to use. Since most hosts only have one network interface and only one IP address, this field should be set with the host's own IP address. However, the socket library provides no immediate way for a host to determine its own IP address! However, specifying the value of INADDR_ANY(0) in this field tells the operating system to pick any available interface and address.
The address of the sockaddr_in structure is passed into the bind call, so that the socket will now be ready to communicate with remote hosts. The third parameter passed to bind is the length of the sockaddr_in structure.
Example:
struct sockaddr_in sin;
int s;
s = socket(AF_INET, SOCK_STREAM, 0);
sin.sin_family = AF_INET;
sin.sin_port = htons(9999);
sin.sin_addr.s_addr = INADDR_ANY;
bind(s, (struct sockaddr *)&sin, sizeof(sin));
/* s is now a usable TCP socket. Source port is 9999 */
It is recommended that the return from bind be checked; bind will fail by returning -1 if the port that is being requested for use is already taken. When bind is called on a TCP socket, the socket is now ready for the connect or accept calls.
listen:
The server listens for incoming connection requests from clients. The prototype for listen is as follows:
listen(int socket, int backlog);
The first argument is a socket handle (the number returned from the socket function call). The second argument specifies the maximum number of connections that can be pending on the specified socket (typically set to a large value, such as 1024).
accept:
This is a blocking operation that does not return until a remote client has established a connection; when it completes, it returns a new socket that corresponds to this just-established connection. The prototype for accept is as follows:
int accept(int socket, struct sockaddr_in * address, int * addr_len);
The address argument contains the remote client's address. Note that when accept returns, the original socket that was given as argument still exists; it is used in a future invocation of accept.
send and recv:
Once a connection is established, the application processes invoke the following two operations to send and receive data:
int send(int socket, char * message, int msg_len, int flags);
int recv(int socket, char *buffer, int buf_len, int flags);
Both operations take a set of flags that control certain details of the operation.
A client-server echo program
The server in this program listens at port 6500. Once a client makes a request to the server, everything the client enters, the server echoes back.
The echo client: EchoClient.c
This client not only gets the input and output stream from the socket, but it also gets the input stream from the standard input. This allows it to read from the keyboard. Whenever the user enters a line on the keyboard, the client reads it and writes it to the socket. The client then reads from the socket, awaiting the echo back from the server.
The echo server: EchoServer.c
To compile the client and server code on Windows, make sure to add the library module Wsock32.lib to your project (select the Setting option under the Project menu).
To compile the client and server code On Unix, you can use this Makefile.
Testing the client-server echo program
To test the echo client-server application, start the server by entering (in Unix)
./EchoServer
Now you can test the server using both telnet and the client.
a) Using telnet: in a separate window, enter the command:
telnet localhost
Now, everything you enter on that window will be echoed back to you. To quit, open another terminal on tanner and kill both applications (EchoServer and telnet).
a) Using the echo client: in a separate window, start the echo client by entering (in Unix)
./EchoClient localhost
Now, everything you enter on that window will be echoed back to you. Quit the client with CTRL-D (this is the end-of-file character in Unix).
Exercise
The EchoServer program can handle one single client at a time. For instance, if you try to run concurrently two echo clients (in two separate windows), only one client can talk to the server; the second client can start talking to the server only after the first client finishes its execution.
Modify the code for the echo server to support multiple clients concurrently. The server should spawn a new thread each time it receives a connection request from a client. It is the thread now that handles (services) the client, while the server goes back to listening for new incoming connections from other clients.
------------------------------
EchoClient.c
#ifndef unix
#define WIN32
#include
#include
#else
#define closesocket close
#include
#include
#include
#include
#include
#endif
#include
#include
#define PROTOPORT 6500 /* default protocol port number */
extern int errno;
char localhost[] = "localhost"; /* default host name */
/*------------------------------------------------------------------------
* Program: client
*
* Purpose: allocate a socket, connect to a server, and print all output
*
* Syntax: client [ host [port] ]
*
* host - name of a computer on which server is executing
* port - protocol port number server is using
*
* Note: Both arguments are optional. If no host name is specified,
* the client uses "localhost"; if no protocol port is
* specified, the client uses the default given by PROTOPORT.
*
*------------------------------------------------------------------------
*/
int main(int argc, char * argv[])
{
struct hostent *ptrh; /* pointer to a host table entry */
struct protoent *ptrp; /* pointer to a protocol table entry */
struct sockaddr_in sad; /* structure to hold an IP address */
int sd; /* socket descriptor */
int port; /* protocol port number */
char *host; /* pointer to host name */
int n; /* number of characters read */
char buf[1000]; /* buffer for data from the server */
char *text; /* pointer to user's line of text */
#ifdef WIN32
WSADATA wsaData;
WSAStartup(0x0101, &wsaData);
#endif
memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */
sad.sin_family = AF_INET; /* set family to Internet */
/* Check command-line argument for protocol port and extract */
/* port number if one is specified. Otherwise, use the default */
/* port value given by constant PROTOPORT */
if (argc > 2) { /* if protocol port specified */
port = atoi(argv[2]); /* convert to binary */
} else {
port = PROTOPORT; /* use default port number */
}
if (port > 0) /* test for legal value */
sad.sin_port = htons((u_short)port);
else { /* print error message and exit */
fprintf(stderr,"Bad port number %s\n",argv[2]);
exit(1);
}
/* Check host argument and assign host name. */
if (argc > 1) {
host = argv[1]; /* if host argument specified */
} else {
host = localhost;
}
/* Convert host name to equivalent IP address and copy to sad. */
ptrh = gethostbyname(host);
if ( ((char *)ptrh) == NULL ) {
fprintf(stderr,"Invalid host: %s\n", host);
exit(1);
}
memcpy(&sad.sin_addr, ptrh->h_addr, ptrh->h_length);
/* Map TCP transport protocol name to protocol number. */
if ( ((int)(ptrp = getprotobyname("tcp"))) == 0) {
fprintf(stderr, "Cannot map \"tcp\" to protocol number");
exit(1);
}
/* Create a socket. */
sd = socket(AF_INET, SOCK_STREAM, ptrp->p_proto);
if (sd < 0) {
fprintf(stderr, "Socket creation failed\n");
exit(1);
}
/* Connect the socket to the specified server. */
if (connect(sd, (struct sockaddr *)&sad, sizeof(sad)) < 0) {
fprintf(stderr,"Connect failed\n");
exit(1);
}
/* Repeatedly read data from user and send it to server. */
text = fgets(buf, sizeof(buf), stdin);
while (text != NULL) {
send(sd, buf, strlen(buf), 0);
n = recv(sd, buf, sizeof(buf), 0);
write(1, buf, n);
text = fgets(buf, sizeof(buf), stdin);
}
/* Close the socket. */
closesocket(sd);
/* Terminate the client program gracefully. */
exit(0);
}
--------------------------
---------------------------
EchoServer.c
#ifndef unix
#define WIN32
#include
#include
#else
#define closesocket close
#include
#include
#include
#include
#endif
#include
#include
#define PROTOPORT 6500 /* default protocol port number */
#define QLEN 6 /* size of request queue */
int visits = 0; /* counts client connections */
/*------------------------------------------------------------------------
* Program: server
*
* Purpose: allocate a socket and then repeatedly execute the following:
* (1) wait for the next connection from a client
* (2) send a short message to the client
* (3) close the connection
* (4) go back to step (1)
*
* Syntax: server [ port ]
*
* port - protocol port number to use
*
* Note: The port argument is optional. If no port is specified,
* the server uses the default given by PROTOPORT.
*
*------------------------------------------------------------------------
*/
main(argc, argv)
int argc;
char *argv[];
{
struct hostent *ptrh; /* pointer to a host table entry */
struct protoent *ptrp; /* pointer to a protocol table entry */
struct sockaddr_in sad; /* structure to hold server's address */
struct sockaddr_in cad; /* structure to hold client's address */
int sd, sd2; /* socket descriptors */
int port; /* protocol port number */
int alen; /* length of address */
char buf[1000]; /* buffer for string the server sends */
int n; /* number of characters received */
int optval = 1; /* options set by setsockopt */
#ifdef WIN32
WSADATA wsaData;
WSAStartup(0x0101, &wsaData);
#endif
memset((char *)&sad, 0, sizeof(sad)); /* clear sockaddr structure */
sad.sin_family = AF_INET; /* set family to Internet */
sad.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */
/* Check command-line argument for protocol port and extract */
/* port number if one is specified. Otherwise, use the default */
/* port value given by constant PROTOPORT */
if (argc > 1) { /* if argument specified */
port = atoi(argv[1]); /* convert argument to binary */
} else {
port = PROTOPORT; /* use default port number */
}
if (port > 0) /* test for illegal value */
sad.sin_port = htons((u_short)port);
else { /* print error message and exit */
fprintf(stderr,"Bad port number %s\n",argv[1]);
exit(1);
}
/* Map TCP transport protocol name to protocol number */
if ( ((int)(ptrp = getprotobyname("tcp"))) == 0) {
fprintf(stderr, "cannot map \"tcp\" to protocol number");
exit(1);
}
/* Create a socket */
sd = socket(AF_INET, SOCK_STREAM, ptrp->p_proto);
if (sd < 0) {
fprintf(stderr, "Socket creation failed\n");
exit(1);
}
/* Eliminate "Address already in use" error from bind. */
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int)) < 0)
return -1;
/* Bind a local address to the socket */
if (bind(sd, (struct sockaddr *)&sad, sizeof(sad)) < 0) {
fprintf(stderr,"Bind failed\n");
exit(1);
}
/* Specify size of request queue */
if (listen(sd, QLEN) < 0) {
fprintf(stderr,"Listen failed\n");
exit(1);
}
/* Main server loop - accept and handle requests */
while (1) {
alen = sizeof(cad);
if ( (sd2 = accept(sd, (struct sockaddr *)&cad, &alen)) < 0) {
fprintf(stderr, "Accept failed\n");
exit(1);
}
n = recv(sd2, buf, sizeof(buf), 0);
while (n > 0)
{
send(sd2, buf, n, 0);
n = recv(sd2, buf, sizeof(buf), 0);
}
closesocket(sd2);
}
}
----------