Sockets are the most general way of performing interprocess communication (usually abbreviated IPC) in Unix. We have seen other ways of performing IPC. For example, processes can communicate through files and through pipes. However, both of those are limited. For example, the only way that two processes can communicate through a pipe is if one process has been forked by the other. Often we would like to have two processes talk with each other when they are otherwise unrelated. For example, they can be created by different shells, different users, or on different machines. Sockets let us do this.
The Unix socket interface is one of the more arcane things you'll ever run into. It is for that reason that instead of using that directly, you'll be using the following three procedures from socketfun.c:
(On solaris, you have to use some special linking options if you use gcc and socketfun.c. When you make your final executable, specify ``-lsocket -lnsl''. This is done in the makefile for this lecture. When you are using cc, or an operating system like SunOS, you don't need to specify these.)
extern int serve_socket(char *hn, int port); /* This serves a socket at the given host name and port number. It returns an fd for that socket. Hn should be the name of the server's machine. */ extern int accept_connection(int s); /* This accepts a connection on the given socket (i.e. it should only be called by the server). It returns the fd for the connection. This fd is of course r/w */ extern int request_connection(char *hn, int port); /* This is what the client calls to connect to a server's port. It returns the fd for the connection. */Sockets work as follows:
One process ``serves'' a socket. By doing this, it tells the operating system to set up a ``port'' identified by a port number. This is the handle by which other processes may connect with the serving process. There is a nice analogy of sockets to a telephone. A socket is like a telephone, and the port number is like a telephone number. The machine name is like the area code. Thus, in order for someone to "call you up", they need to request a connection to your area code/port number, just like making a phone call.
When you "serve" a socket, you are setting yourself up with a telephone and a telephone number. The result of serving a socket is a file descriptor. However, it is not a normal file descriptor upon which you may read and write. Instead, the only things you can really do with it are close it (which tells Unix to not let other processes connect to that port), or accept connections on it.
The server accepts a connection on a socket fd by calling accept_connection(). Accept_connection() only returns when another process has connected to the socket. It returns a new file descriptor which is that connection. This descriptor is r/w. In other words if you write() to it, the bytes will go to the connected process, and if you read() from it, it will read bytes written by the other process.
"Non-serving processes connect with serving ones with request_connection(). It says to connect with the process that has served the socket with the given port number on the given host machine. This is like initiating a phone call. It returns a file descriptor for its end of the connection.
Once a socket has been served, it may be connected to multiple times. That is, the serving process may call accept_connection() any number of times, as long as there are other processes calling request_connection() to that machine and port number. This is kind of like having multiple phone lines with one telephone number. This will be useful when you write jtalk with more than 2 people.
Serve1.c takes two arguments -- the host name and port number of a socket to serve. The host name should be the name of the machine running serve1 (for example, if you are on cetus1c, then you can call serve1 with a first argument of "cetus1c" or "cetus1c.cs.utk.edu" -- you need to give enough to specify the machine). The port number should be greater than 5000. This is because smaller port numbers may be used for other Unix programs.
Serve1.c serves a socket and then waits to accept a connection. Once it receives that connection, it sends the string "Server: ", then the username of the owner of the serving process, and a newline to the other end of the connection. Then it reads bytes from the connection, and prints them out until it gets a newline. Once done, it exits.
Client1.c is similar. It takes a host name and a port number as arguments, but instead of serving a socket, it tries to connect to a socket at the host and port number. Once connected, it reads bytes from the connection and prints them until it receives a newline. Then it sends the string "Client: ", then the username of the owner process, and a newline to the serving process.
So, lets try them: Get two xterms on your screen and cd to the right directory. Then on one of them, type: (I'm assuming you are on the machine cetus1c")
UNIX1> serve1 cetus1c 5001It serves the socket, and is now waiting to accept a connection. Run client1 on the other xterm:
UNIX2> client1 cetus1c 5001When I do this, I get the following. On the first xterm, I get:
UNIX1> serve1 cetus1c 5001 Connection established. Sending `Server: plank' Receiving: Client: plank UNIX1>on the other, I get:
UNIX2> client1 cetus1c 5001 Connection established. Receiving: Server: plank Sending `Client: plank' UNIX2>Go over both programs until you understand all this. If you run "client1" before "serve1", that's ok. It will wait until the socket is served.
Now, suppose you try:
UNIX1> serve1 cetus2f 5001 bind(): Can't assign requested address UNIX1>That's serve_socket()'s way of saying that you can't serve a socket on cetus2f when you are currently running on cetus1c.
Client1 can be run from any machine and any user. First try running it from a different machine. For example, start up serve1 as before on one xterm:
UNIX1> serve1 cetus1c 5001and log into another machine to run client1:
UNIX2> rlogin cetus2e .... UNIX2> client1 cetus1c 5001 Connection established. Receiving: Server: plank Sending `Client: plank' UNIX2>It should work fine. Now, try doing this with another user. For example, run serve1 as before:
UNIX1> serve1 cetus1c 5001Then, get a friend to run client1. For example, I got Dr. Booth to try this from her machine:
UNIX> client1 cetus1c 5001She received:
UNIX> client1 cetus1c 5001 Connection established. Receiving: Server: plank Sending `Client: booth' UNIX>and I received:
UNIX1> serve1 cetus1c 5001 Connection established. Sending `Server: plank' Receiving: Client: booth UNIX1>
Try running
alternate
from two windows on the same machine, or from two different machines, or from two different users on different machines.
Try it out.
The only problem with minitalk is that once one of the processes dies, the others don't automatically follow suit. Think about why. When you run minitalk, after you finish, type "ps" and kill extraneous "cat" processes.