Sockets are a method of IPC that allow data to be exchanged between applications, either on the same host (computer) or on different hosts connected by a network.
—Michael Kerrisk, The Linux Programming Interface, Chapter 56, pp. 1149.
Sockets provide efficient ways to do file-like input and output between processes on the same host, or on different hosts. Their biggest benefit is that they allow bi-directional communication as well as the ability to treat the endpoints as formatted input and output streams, just like you would read and write from files, or from streams like standard input and output.
In this studio, you will:
Please complete the required exercises below. We encourage you to please work in groups of 2 or 3 people on each studio (and the groups are allowed to change from studio to studio) though if you would prefer to complete any studio by yourself that is allowed.
As you work through these exercises, please record your answers, and when finished upload them along with the relevant source code to the appropriate spot on Canvas. If you work in a group with other people, only one of you should please upload the answers (and any other files requested) for the studio, and if you need to resubmit (e.g., if a studio were to be marked incomplete when we grade it) the same person who submitted the studio originally should please do that.
Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.
man 2 bind
under the section
labeled EXAMPLE.
Your server should perform the following actions:
socket()
system call. To
create a local connection use the domain AF_LOCAL
(or equivalently AF_UNIX
), the connection type
SOCK_STREAM
, and protocol zero.
bind()
system call. This associates your socket from step
one with a machine-visible address. In the case of
AF_LOCAL
, this address is a path in the file system, and
your local server program should use the first argument passed to it
as a string containing that path. Directions on how to specify a local
address in this way (and which header files you'll need in order to make
that work) can be found at man 7 unix
, and there
is a good example showing how to do this on page 1166 of the LPI
(Kerrisk) text book.
listen()
system
call. Sockets only allow one process to connect at a time, so the second parameter
determines how many connection requests can wait (queue up) before subsequent
ones are rejected.
accept()
system call to accept a connection
over the socket interface, passing in NULL and NULL for the second and third arguments. If no connection is immediately available then this
system call will, by default, cause your program to block until another program
attempts to connect. When your program successfully returns from the call to accept()
the new communication socket connection has been established, and the return value from the call is a file descriptor for the new socket.
fdopen
function and then using fscanf()
. When
creating the stream, be sure to use the socket descriptor that was
returned by the call to accept()
(the communication endpoint),
not the one created by the initial call to socket()
(the connection establishment endpoint). As long as the communication
endpoint socket remains open,
the server should print out all unsigned integers that it receives, to its standard
output stream.
unlink()
system call in order to destroy
the original local socket (connection establishment endpoint) file before
the program returns.Note: Many things can go wrong with socket-based
communication. As usual, you should always check function
return codes to detect errors. Recall that most functions
return error codes that allow you to print a descriptive error
statement with a line of code such as:
printf("Program error, reason: %s\n", strerror(errno))
.
This will require including the headers string.h
and
errno.h
.
Now you will write a client program that should do the following:
socket()
system call.
connect()
system call. This
requires using the same local socket address as was used previously in your
local server program's call
to bind()
.
Compile your client and server programs, and run them in separate terminal windows on your Raspberry Pi. Copy and paste the client and server programs' output as the answer to this exercise.
accept()
(but not to listen()
or
bind()
).
Your server program should also print a message announcing each time a
new connection is established and should then print out the unsigned integer values
it reads from the socket each time, to the standard output stream.
Rather than having your server stay alive forever, now modify your
server program so that it terminates when it recieves a special unsigned integer
value (e.g., the http status code
418
).
Modify your client so that it can accept zero or one command line arguments, and if it is given the command line argument "quit" it sends that special code to the server after it has sent its other unsigned integers (thus
terminating the server).
Compile your refactored server code and run it in its own terminal window. In another window, run your client program several times along with that server to validate that it can stay alive and keep accepting new connections until it is told to quit. Copy and paste the server output as the answer to this exercise.
shell.cec.wustl.edu
host.
Note that for this exercise, your server program will run directly on
shell.cec.wustl.edu
.
You should not qlogin
into a Linux Lab node.
Make copies of your client and server programs, and modify the new copies
to use an internet connection rather than a local connection. Choose a port
number between 30000 and 40000 that both of your programs will use wherever a port
number is needed in the instructions below. Run the command
ping shell.cec.wustl.edu
(on the Raspberry Pi you may need to preface that command with
sudo
)
from a terminal window to obtain the numeric IP address of the
shell.cec.wustl.edu
machine, which your client
program should use.
netinet/ip.h
and arpa/inet.h
in both programs.
socket()
(and bind()
and connect()
, respectively) to specify
an internet connection type with AF_INET
(instead of AF_LOCAL
or AF_UNIX
). Also remove any
artifacts of the local domain socket from both programs, including removing
the call to unlink()
from the server code.
sockaddr_in
(instead of sockaddr_un
) to specify the internet
address. This struct is documented at man 7 ip
. In both
programs, set the sin_family
field to AF_INET
, and
the sin_port
field to htons(port_num)
,
where htons()
is a function that translates between the
numeric representations used on your computer and in the network, and
port_num
is the port number that both your server and
client programs are using. In the server program assign the
sin_addr.s_addr
field the value INADDR_ANY, and in
the client program use the function inet_aton()
to translate a c-style string containing the target IP address and
store it in the sin_addr
field of the
sockaddr_in
struct.
Compile and run your server on shell.cec.wustl.edu
.
Compile your client on your Raspberry Pi and run it there twice, once without
telling the server to quit and the second time telling the server to quit.
As the answer to this exercise, copy and paste the output from your client
and server terminal windows. If you cannot connect for whatever reason,
give a descriptive error message using strerror()
and
errno
.
shell.cec.wustl.edu
rather than just shell
).
After its socket is connected, the client program should also print out that
socket's IP address and port. The server program should also print out the
IP address and port of the socket on which it is listening, and should also
print out the IP address and port of each additional socket that it accepts.
As the answer to this exercise please copy and paste all of that output from
your client and server terminal windows.
Using the gethostname()
, getnameinfo()
, and/or
getaddrinfo()
functions may be helpful for this exercise.
Please Note: If your Raspberry Pi is not on the school's network, you will need to first establish a remote connection, either with a VPN connection or through an SSH tunnel.
After you have selected all 3 options, click OK to install the packages (you will be prompted to enter the new password you created on your Pi above).
These instructions have been updated to use the new WashU 2FA (two-factor authentication) based VPN. If you have any problems with enrolling in or using WashU 2FA please call the WashU IT ServiceDesk at (314) 933-3333, or email ithelp@wustl.edu.
Once you have installed the VPN, and have enrolled in WashU 2FA and have chosen which authentication method to use, please open up a terminal window on your Raspberry Pi, and
type:
sudo openconnect danforthvpn.wustl.edu
At the Username:
prompt please enter you WUSTL key ID. After that you should get a Password:
prompt, where you should enter your WUSTL key password. You will then get a second prompt that also says Password:
which is where you should enter one of the following keywords (depending on which authentication method(s) you set up when you enrolled in WashU 2FA):
push
-- enter this if you have set up DUO mobile notification
(be aware that if you have more than one device enrolled, you may receive the push on only one device)
phone
-- enter this if you want to authenticate via a phone call to the number you have registered with the WashU 2FA system (this may only work if you only have registered a single number there).
(note that other methods like entering sms
also may work but involve other details that we have not tested out -
please contact the WashU IT ServiceDesk at (314) 933-3333 or ithelp@wustl.edu, or contact Engineering IT for help using those other methods.)
Note: you will need to go through this process to start the VPN every time you reboot your Raspberry Pi and want to access the linuxlab machines directly from it.
An SSH tunnel allows you to forward traffic on a specific port to a remote server via SSH.
To establish a tunnel, simply run the following command on your Raspberry Pi:
ssh -f -N your-username@shell.cec.wustl.edu -L port-number:shell.cec.wustl.edu:port-number
You must replace your-username
with your WUSTL Key username, and port-number
with the port you selected to establish the socket connection.
For example, your command might look like: ssh -f -N msudvarg@shell.cec.wustl.edu -L 31522:shell.cec.wustl.edu:31522
After issuing the command, you will be prompted for your WUSTL Key password.
Alternatively, if you are already set up to log in with an SSH key,
you may replace your-username@shell.cec.wustl.edu
with the server nickname,
as detailed on this page.
If you use the SSH Tunnel approach,
your socket code should be configured to instead connect to the local loopback IP address
(i.e. 127.0.0.1
) instead of the IP address you obtained for shell.cec.wustl.edu
.