A TCP socket is defined as an endpoint for communication. A
socket consists of the pair <IP Address,Port>.
For our purposes,
a port will be defined as an integer
number between 1024 and 65535. This is because all port numbers smaller than
1024 are considered well-known -- for example, telnet uses port 23,
http uses 80, ftp uses 21, and so on. On Unix machines, the file /etc/services
contains a list of services provided by that machine, along with their well-known ports
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>
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()
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.
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.
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.
To test the echo client-server application, start the server by entering (in Unix)
Now you can test the
server using both telnet and the client.
./EchoServer <port number>
a) Using telnet: in a separate window, enter the command:
telnet localhost <port number>
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 <port number>
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).
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.