1. UNIX Network Programming
By W. Richard Stevens
Chapter-6, on Berkeley Sockets
2. Manuals socket(2), ip(7),
tcp(7) and udp(7)
Square brackets are used to separate keywords, such as filenames,
programs names etc etc from lines of text. They have no other meaning.
Go to your home directory
$ cd
Verify the current directory
$ pwd
Create directory [socket1] in your home directory.
$ mkdir socket1
Change into [socket1] directory
$ cd socket1
Bring the file [socket1.txt] in this directory using NFS
$ mount /ftp-server
$ cd /ftp-server
.
.
You may also use [ftp] to bring the file.
SOCKET:
A socket is an endpoint for communication. Sockets are the
primary means of communicating with other hosts; telnet, rlogin, ftp,
talk and the other familiar network programs use sockets.
Different types of [socket]s can be created by [socket()] system call.
Consult the manual of this system call from section 2 of manual.
$ man 2 socket
[socket()] call takes three arguments. An unsigned integer is returned
when the call is successful. This integer is the file descriptor of
the socket. As with regular file descriptors, writing in the socket,
reading from the socket, closing the socket etc etc can be done, using
this descriptor.
Type of the created socket depends on the arguments used. In this
laboratory, we are going to use sockets suitable for network communic-
ation using IPv4 Internet protocols. From the above manual we find
that we have to consult the manual of [ip] from section 7 of the UNIX
manual, for the arguments to be used for creating IPv4 sockets.
$ man 7 ip
A TCP socket ( IPv4 stream socket ) can be created with...
tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
An UDP socket ( IPv4 datagram socket ) can be created with...
udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
Zero may be used to replace IPPROTO_TCP and IPPROTO_UDP. Zero is IP
pseudo protocol number.
We are not going to use [raw_socket] in this experiment.
Save the following program as [f1.c].
---------------------------------------------------------------------
// file-name f1.c
// One TCP and one UDP socket are created
// usage: program-name
#include
#include
#include
#include
int main ( void )
{
int sd1, sd2 ;
// sd1 and sd2 would be used as socket-file-descriptors
// Values of two symbolic constants are printed
printf("IPPROTO_TCP = %u \n", IPPROTO_TCP );
printf("IPPROTO_UDP = %u \n", IPPROTO_UDP );
// process number of this program
printf("PID = %u \n", getpid() );
// TCP ( IPv4 stream ) socket is created
sd1 = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
// Numerical value of the socket descriptor is printed
printf("value of TCP socket descriptor = %u \n", sd1 );
// UDP ( IPv4 datagram ) socket is created
sd2 = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP );
// Numerical value of the socket descriptor is printed
printf("value of UDP socket descriptor = %u \n", sd2 );
// If the program exits, operating system closes (removes) the
// descriptors. The following endless loop is used for checking
// the creation of the two sockets.
while( 1 ) { sleep(1); }
return 0;
} //end of main
---------------------------------------------------------------------
Assignment-1:
Compile the program
$ gcc -Wall ./f1.c -o ./one
Run the executable program, do not terminate the program and record
the following from the output of the program:
$ ./one
IPPROTO_TCP = ___
IPPROTO_UDP = ___
PID = ________
value of TCP socket descriptor = ___
value of UDP socket descriptor = ___
As the program runs in this terminal, this terminal is unusable now.
Note: The program can be started in background also.
$ ./one &
If the program runs in the background, this terminal becomes
usable.
Verify the PID of your program
$ ps ax | grep one
In another terminal, examine the file [/etc/protocols] and record the
following
$ less /etc/protocols
protocols-name protocols-number
IP ------------> ___
TCP -----------> ___
UDP -----------> ___
Check the creation of the sockets with the following command
$ ls -l /proc/value-of-PID/fd
Were the two sockets created ? ( Y / n )
Terminate the running program [one] with CTRL-C.
End of Assignment-1.
5-tuple association for network communication may be stated as:
(protocol,local-address,local-process,foreign-address,foreign-process)
Assignment-2.
In this assignment, a 5-tuple association for network
communication is to be verified. The [echo] server process using TCP,
running in your friend's host, are the [ foreign-address ], [ foreign-
process ] and [ protocol ].
Examine file [/etc/services] and record:
$ less /etc/services
[echo] server, using TCP protocol, runs on port number ____ .
[foreign-process] is NOT the PID of the [echo] server process, but it
is the port number, the [echo] server process associates with. If the
[ echo ] server process is restarted, its PID would change but its
association with port number - 7 would be unaltered. This port number
is well-known to the [ client/clients ].
At present we do not have any TCP [echo] client program. Later in this
experiment we are going to write a TCP [echo] client program.
[telnet] is a [client] program used to connect to the [telnetd] server
program. [ telnet ] connects to port-23 of the specified host, if port
number is not specified. If port number is specified, [ telnet ]
connects to that port.
The two commands should give the same result
$ telnet 192.168.5.145
or
$ telnet 172.16.2.145 23
If port number is specified as 7, [ telnet ] program may be used to
communicate with the [echo] server. Replace 172.16.2.145 with the IP
address of your friend's host.
$ telnet 172.16.2.145 7
Enter any string. It should be echoed on your terminal.
[ telnet ] program used in your host, is the [ local-process ]. PID of
[telnet] process is NOT the [[local-process], it is the port number it
is associated with. This port number ( in the range 1024 - 65535 ) is
alloted to that instance of [telnet] process, by the operating system
and it is guaranteed to be free, no other process using TCP protocol
was using it. The port assigned to the [ client ] process is called
ephemeral ( short lived ) port. The PID and the ephemeral port might
change with each invocation of the [client] process.
----------- ----------------
| | | |
| Your host |_____________________| Friend's Host |
| client | | echo server(7) |
| | | |
----------- ----------------
Connect to the [ echo ] server of your friend but do not enter any
string. In this condition a TCP connection is established.
$ telnet 172.16.2.145 7
In another terminal, login in your account ( or login as [root] ) and
use the following command.
$ ps ax
PID of the [telnet] process -->
Use the following command...
$ netstat -atnp
You may read manual of this command [ $ man 8 netstat ].
The meaning of the options are reproduced from the manual:
a all
t TCP
n numeric output
p show PID and program name
If the output is more than one screenful, you can store the result in
a file and examine the file with a pager, such as [less].
$ netstat -atnp >tmp 2>&1
$ less ./tmp
Fill up the following table.
proto loc-add:loc-port for-add:for-port state PID/prog-name
tcp :7 ESTABLISHED
The above is the 5-tuple association.
End of Assignment-2.
The system calls used by the [server] and the [client] for connection
oriented protocol are shown below ( From "UNIX Network Programming" by
W.Richard Stevens ). TCP is a connection oriented protocol.
SERVER
socket()
|
bind()
|
listen()
|
accept() CLIENT
|
Blocks until connection socket()
from client |
| connection establishment |
|<-----------------------------------------> connect()
| |
read() <------------------------------------- write()
| data (request from client) |
| |
process client's request |
| |
write() --------------------------------------> read()
data (reply from server)
A newly created socket is not useful for communication. An address is
to be assigned to it.
The format of a IPv4 address is reproduces from manual [ip(7)].
struct sockaddr_in
{
sa_family_t sin_family; // address family: AF_INET
u_int16_t sin_port; // port in network byte
struct in_addr sin_addr; // internet address
};
// Internet address.
struct in_addr {
u_int32_t s_addr; // address in network byte order
};
Internet address and port number are to be in a format, called the
"network byte order".
When the members of a IPv4 address have been initialised, the newly
created socket is associated with this address, with [ bind ] system
call. [ bind ] call sets a local IP address and port number of the
socket. When it is needed to assign a specific port ( well-known) to a
socket, [bind] is used. Well-known ports are used by servers. [ bind ]
call is mainly used by servers.
[bind] call takes three arguments
int = bind( socket-descriptor,
address of local IPv4 address,
size of the IPv4 address in bytes )
You may read manual of bind(2)] for the exact syntax of the call.
[listen] system call makes a [socket] ready to accept incoming client
requests. [ listen ] call also sets a limit of the incoming connection
queue. [listen] call is valid for connection oriented protocols (TCP).
In the next program [f2.c] following are done:
One TCP and one UDP
socket are created. Members of the structure of one IPv4 address are
then initialised. This address is assigned to the UDP and TCP sockets.
Although the same IP address and port number are used, the two sockets
are bound to two different addresses, as two different protocols are
used. The TCP [socket] is put in listening state.
------------------------------------------------------------
// file-name f2.c
// bind() and listen() calls
// usage: program-name
#include
#include
#include
#include
#include
#define LOCAL_ADDRESS "127.0.0.1"
// 127.0.0.1 is the IP address of loopback interface
// Any free port ( 1024 to 65535 ) is chosen
#define LOCAL_PORT 65432
int main ( void )
{
int sd1, sd2 ;
int n;
struct sockaddr_in addr; // IPv4 address
// members of [addr] are initialised
addr.sin_family = AF_INET;
addr.sin_port = htons( LOCAL_PORT );
n = inet_aton( LOCAL_ADDRESS , &(addr.sin_addr) );
if( n == 0 )
{
printf("invalid-addr\n");
exit(1);
}
// TCP ( IPv4 stream ) socket is created
sd1 = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
// TCP socket is bound to a local address
n = bind( sd1, ( struct sockaddr *) &addr, sizeof(addr) );
if( n == -1 ) { perror("bind-call-1"); exit(1); }
// TCP socket is put in listening state
n = listen( sd1, 5 );
if( n == -1 ) { perror("listen-call"); exit(1); }
// UDP ( IPv4 datagram ) socket is created
sd2 = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP );
// UDP socket is bound to a local address
n = bind( sd2, ( struct sockaddr *) &addr, sizeof(addr) );
if( n == -1 ) { perror("bind-call-2"); exit(1); }
// endless loop
while( 1 ) { sleep(1); }
return 0;
} //end of main
---------------1---------------------------------------------
htons() function:
This function converts the short integer from host
byte order to network byte order. Port numbers are 16 bit long.
Read its manual.
$ man 3 htons
inet_aton() function:
This function converts the Internet host address
from the standard numbers-and-dots notation into binary data and
stores it in the address structure. This function returns nonzero if
the address is valid, zero if not.
Read its manual.
$ man 3 inet_aton
Assignment-3:
Compile and run the program.
$ gcc -Wall ./f2.c -o ./two-a
$ ./two-a
Use the following command and examine the output
$ netstat -an
You should get the following two lines related to your program.
Proto Local Address Foreign Address State
tcp 127.0.0.1:65432 0.0.0.0:* LISTEN
udp 127.0.0.1:65432 0.0.0.0:*
In [ f2.c ], the sockets were bound with loopback interface address
127.0.0.1 . If a server uses such a local address, only the users of
that host would be able to access the server.
Terminate the program with CTRL-C.
Replace the string "127.0.0.1" in [f2.c ] with IP address of ethernet
interface of your host.
Compile and run the program
$ gcc -Wall ./f2.c -o two-b
$ ./two-b
In another terminal, record the protocol, local-address and local-port
used by your program.
$ netstat -an
protocol local-address:local-port State
tcp _____________:65432
udp _____________:65432
Terminate the program with CTRL-C.
Use IP address of ethernet interface of your friend's host.
Compile the program
$ gcc -Wall ./f2.c -o ./two-c
Try to run the program
$ ./two-c
[bind] call should fail as such address was not found on the [ eth0 ]
interface of your host.
End of Assignment-3.
A server usually accepts service request, on all available interfaces
of a host. A server binds the socket with addresses of all interfaces
as local addresses.
The program [f3.c] shows such use.
------------------------------------------------------------
// file-name f3.c
// binding with all interfaces
// usage: program-name
#include
#include
#include
#include
#include
#define LOCAL_PORT 65432
int main ( void )
{
int sd1, sd2 ;
int n;
struct sockaddr_in addr; // IPv4 address
// members of [addr] are initialised
addr.sin_family = AF_INET;
addr.sin_port = htons( LOCAL_PORT );
addr.sin_addr.s_addr = htonl( INADDR_ANY );
// TCP ( IPv4 stream ) socket is created
sd1 = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
// TCP socket is bound to a local address
n = bind( sd1, ( struct sockaddr *) &addr, sizeof(addr) );
if( n == -1 ) { perror("bind-call-1"); exit(1); }
// TCP socket is put in listening state
n = listen( sd1, 5 );
if( n == -1 ) { perror("listen-call"); exit(1); }
// UDP ( IPv4 datagram ) socket is created
sd2 = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP );
// UDP socket is bound to a local address
n = bind( sd2, ( struct sockaddr *) &addr, sizeof(addr) );
if( n == -1 ) { perror("bind-call-2"); exit(1); }
// endless loop
while( 1 ) { sleep(1); }
return 0;
} //end of main
------------------------------------------------------------
htonl() function:
This function converts the long integer from host
byte order to network byte order.
Read its manual.
$ man 3 htonl
Assignment-4:
Compile and run the program
$ gcc -Wall ./f3.c -o ./three
$ ./three
In another terminal, find out local [socket] addresses.
$ netstat -an
tcp 0.0.0.0:65432
udp 0.0.0.0:65432
The address 0.0.0.0 means that the socket is bound to all interfaces
of the host.
Verify this by scanning port 65432 of [lo] interface for TCP and UDP.
# nmap -sTU 127.0.0.1 -p 65432
Likely output is shown:
Interesting ports on localhost (127.0.0.1):
Port State Service
65432/tcp open unknown
65432/udp open unknown
[ eth0 ] interface is also scanned. Replace 172.16.2.146 with the IP
address of the [eth0] interface of your host.
# nmap -sTU 172.16.2.146 -p 65432
Likely output is shown:
Interesting ports on cc-146.cc.cemk.ac.in (172.16.2.146):
Port State Service
65432/tcp open unknown
65432/udp open unknown
Terminate the running program with CTRL-C
End of Assignment-4.
The next program [f4.c] is a TCP [echo] server. This [echo] server is
not using the well-known port ( 7 ), as this is an experiment.
---------------------------------------------------------------------
// file-name f4.c
// TCP ECHO server( iterative )
// usage: program-name
#include
#include
#include
#include
#include
#define LOCAL_PORT 65432
#define BUFFERSIZE 1024 // arbitrary
int main ( void )
{
int serv_sd; // socket descriptor
int temp_sd; // socket descriptor
int n;
struct sockaddr_in serv_addr; // IPv4 address
char buffer[BUFFERSIZE];
ssize_t i,j;
// members of [serv_addr] are initialised
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons( LOCAL_PORT );
serv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
// TCP ( IPv4 stream ) socket is created
serv_sd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
if( serv_sd == -1 ) { perror("socket-call"); exit(1);}
// TCP socket is bound to a local address
n = bind( serv_sd,
( struct sockaddr *) &serv_addr,
sizeof(serv_addr) );
if( n == -1 ) { perror("bind-call"); exit(1); }
// TCP socket is put in listening state
n = listen( serv_sd, 5 );
if( n == -1 ) { perror("listen-call"); exit(1); }
// server accepts client's request in an endless loop
while( 1 )
{
printf( "server: accepting request on TCP port %u\n",
LOCAL_PORT );
temp_sd = accept( serv_sd, NULL, 0 );
if( temp_sd == -1 ) { perror("accept-call"); }
// fill up the buffer with null characters before storing into it
memset( buffer, '\0', BUFFERSIZE );
// read clients request string in the buffer
i = read( temp_sd, buffer, BUFFERSIZE );
printf("server: %u bytes received from client\n", i );
// write the contents of the buffer in the [temp_sd]
j = write( temp_sd, buffer, i );
if( j == -1 ) { perror("write-call"); }
// shutdown both way communication on [temp_sd]
shutdown( temp_sd, SHUT_RDWR );
// remove the now useless [temp_sd]
close( temp_sd );
} //end of endless loop
return 0;
} //end of main
----------------------------------------------------------------------
accept() system call:
This call has three arguments. This call blocks
until a client request arrives on the server's socket. When a request
comes, the call returns a new socket file descriptor. This descriptor
is used for communication with the client.
Read its manual for full description of the call.
$ man 2 accept
In [f4.c], use of 2nd and 3rd arguments are not shown.
Assignment-5:
Compile and run the program.
$ gcc -Wall ./f4.c -o ./four
$ ./four
Request your friend to connect to your [echo] server, using [ telnet ]
client program. Replace 172.16.2.146 with IP address of [ eth0 ]
interface of your host.
friend $ telnet 172.16.2.146 65432
Did your friend get the echo ? ( Y / n )
Stop the [echo] server with CTRL-C.
End of Assignment-5.
TCP [echo] client:
The system calls to be executed by the client and
server, for connection oriented protocol, are repeated below.
SERVER
| CLIENT
socket(), bind(), listen(), accept() |
socket()
| connection establishment |
|<-----------------------------------> connect()
| |
read() <-------------------------------- write()
| data (request from client) |
| |
process client's request |
| |
write()-----------------------------------> read()
data (reply from server)
Figure-1
The program [f5.c] is the TCP [echo] client. This program has to call
[socket] and [connect] calls to establish connection with a server.
Then the client sends request to server and reads reply from server.
In [ f5.c ] the IP address of the [ echo ] server and TCP port of the
[echo] server are supplied as arguments to the program. We know that
assigned number of [echo] server is 7. Still we are using port number
as an argument to the client program. This would enable us to connect
to experimental [echo] servers or to any standard [echo] server.
Save the following file as [f5.c]
-------------------------------------------------------------------
// file-name f5.c
// TCP echo client
// usage -> program-name server-address server-port
#include
#include
#include
#include
#include
#include
#include
#define BUFFERSIZE 1024 // arbitrary
int main(int argc, char *argv[])
{
int sd; // used as socket descriptor
ssize_t i,j;
int n; // used to check returned value
char buffer[BUFFERSIZE];
struct sockaddr_in server_addr; // An IPv4 address
if( argc != 3 )
{
printf("Usage: %s server-address server-port \n", argv[0] );
printf("Example: %s 172.16.2.148 12345 \n", argv[0] );
exit(1);
}
// A TCP ( IPv4 stream ) socket is created.
sd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
if( sd == -1 ) { perror("socket-call"); exit( 1 ); }
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( atoi(argv[2]) );
n = inet_aton( argv[1], &(server_addr.sin_addr) );
if( n == 0 ) { printf("Invalid address\n"); exit(1); }
n = connect( sd, (struct sockaddr *) &server_addr,
sizeof(server_addr) );
if( n == -1 ) { perror("connect-call"); exit(1); }
/*
The local address of the socket was not set.
[bind()] system call was not called on [sd] before [ connect() ]
system call. As [connect()] was called on a unbound socket, [sd]
was automatically bound to a random free port ( ephemeral port )
with the local address set to [INADDR_ANY].
*/
// generate a prompt
write( STDOUT_FILENO, "Enter the string:", 17 );
// clear buffer before reading
memset( buffer, '\0', BUFFERSIZE );
// read from keyboard in the buffer
i = read( STDIN_FILENO, buffer, BUFFERSIZE );
if( i == -1 ) { perror("read1"); exit(1); }
printf( "bytes read from keyboard=%u\n", i );
// write contents of buffer on server's socket
j = write( sd, buffer, i );
if( j == -1 ) { perror("write1"); exit(1); }
printf("bytes written in server's socket=%u\n",j);
// clear buffer before reading
memset( buffer, '\0', BUFFERSIZE );
// read from server's socket into buffer
i = read( sd, buffer, BUFFERSIZE );
if( i == -1 ) { perror("read2"); exit(1); }
printf("bytes read from server's socket=%un", i );
write( STDOUT_FILENO,"Reply from echo server->", 24 );
j = write( STDOUT_FILENO, buffer, i );
if( j == -1 ) { perror("write2"); exit(1); }
// Shutdown the both-way ( duplex ) connection.
shutdown(sd, SHUT_RDWR);
exit(0);
}
-------------------------------------------------------------------
Function [htons()] takes integer argument. The port number ( argv[2] )
entered as character, is converted into integer.
Assignment-6:
Compile the program
$ gcc -Wall ./f5.c -o ./five
Run the program using wrong number of arguments to see usage message
$ ./five
$ ./five 172.16.2.251
Run the program correctly. Replace 172.16.2.251 with any standard
[echo] server's address. Do not enter a string now.
$ ./five 172.16.2.251 7
From another terminal use [ # netstat -tnp ] command. From the output
of the command, record the line related to your program
Proto Local Address Foreign Address State PID/Program name
Enter a string in the [ echo ] client program. It should be echoed on
your terminal.
Try to connect to a non-existent TCP server in localhost or in another
host. The [ connect ] call of your program should fail with a suitable
error message.
$ ./five 127.0.0.1 2000
$ ./five 172.16.2.251 7777
Try to connect to TCP [echo] servers running on different hosts in the
laboratory or outside the laboratory. Your TCP [echo] client program
should work if TCP [echo] servers are enabled in the specified hosts.
$ ./five 127.0.0.1 7
$ ./five 172.16.2.251 7
$ ./five 172.16.2.252 7
$ ./five 172.16.2.160 7
$ ./five 172.16.1.100 7
$ ./five 210.212.4.3 7
Try to connect to TCP [ echo ] servers running on friend's ECHO server
$ ./five 172.16.2.157 65432
End of Assignment-6.
----------------------------
Optional Assignment-1:
Rename the executable [five] as [tcp-echo-client].
$ mv ./five ./tcp-echo-client
Create a directory [bin] under your home directory
$ mkdir $HOME/bin
Copy the executable in [bin] directory
$ cp ./tcp-echo-client $HOME/bin/
Include the [bin] directory in [PATH] environmental variable
$ PATH=$PATH:$HOME/bin/
Check the intended inclusion with any of the following two commands.
$ echo $PATH
$ env
If the inclusion is correct, you should be able to run the TCP client
from any directory of your account. If you intend to include the [bin]
directory in PATH variable, on every login, include the following line
at the end of file [.bash_profile], in your home directory.
#-- .bash_profile file --
.
.
.
PATH=$PATH:$HOME/bin/
#---------------------------
