"UNIX Network Programming," by W. Richard Stevens, Prentice Hall, 1990
ISBN 0-13-949876-1.
"An Introductory 4.3BSD Interprocess Communication Tutorial" in Unix
Programmer's Manual Supplementary Documents 1, PS1:7
Protocol spec. (such as CFSMs) Æ code
UNIX Workstation Network Interface
Relation to Message Formats of TCP/UDP/IP
Berkeley Socket--An Application Program Interface
A set of library function calls forms an abstraction called socket to facilitate the programming involving inter-process communication (IPC).
Idea
Æ
The socket mimics the file I/O operations:
sending message--write(socket, msgbuf, strlen(msgbuf))
receiving message--read(socket, msgbuf, Maxmsglength)
However the creation of sockets is different, depending on the types of IPC:
int socket(int family, int type, int protocol); // socket function call return fd
/* use man to see the description of each of the IPC routines and parameters.*/
Typical calls for Internet Domain sockets are
sockfd = socket(AF_INET, SOCK_STREAM, 0)
for byte stream connection-oriented service, default protocol is TCP.
sockfd=socket(AF_INET, SOCK_DGRAM, 0)
for datagram connectionless service, default protocol is UDP.
Typical calls for UNIX Domain sockets are
sockfd=socket(AF_UNIX, SOCK_STREAM, 0)
for byte stream connection-oriented service, default protocol is UNIX internal protocol.
sockfd=socket(AF_UNIX, SOCK_DGRAM, 0)
for datagram connectionless service, default protocol is UNIX internal protocol.
The return value is a file descriptor for later reference.
If socket() fails, return value is negative.
Bind Internet Domain Socket to Address
int bind(int sockfd , struct sockaddr * myaddr , int addrlen );
Before the bind() call, specify the proper socket address in struct sockaddr:
For a receiving Internet domain socket,
struct sockaddr_in from;
int length;
from.sin_family = AF_INET;
from.sin_addr.s_addr = INADDR_ANY; /* ask system to assign a port no.*/
from.sin_port = 0
/* 0< portno.< 1024 are reserved for privilege process */
/* to find the assigned port no., call getsockname()*/
bind(sockfd, &from, sizeof (from));
length = sizeof(from);
getsockname(sock, &from, &length)
printf("socket assigned port=%d\n", ntohs(from.sin_port)
Receiving Internet Datagram Messages
struct sockaddr_in from;
char frombuf[2048], toBuf[2048];
/* after bind the socket to address */
while (flag) {
n = recvfrom(sockfd, fromBuf, sizeof(fromBuf), 0, &from, &length);
fromBuf[n] = 0; /* null terminate */
printf("received msg=%s\n", fromBuf);
/* do some processing according to the request */
/* use the returned sender's socket address in the from structure to */
/* send the reply message */
sprintf(toBuf, "received message: %s\n", fromBuf);
if (sendto(sockfd, toBuf, strlen(toBuf), 0, &from, sizeof(from)) < 0)
perror("sending datagram message");
}
close(sockfd);
~cs522/project/ipc contains sample programs, idgr.c (receiver), idgs.c (sender for I386, Alpha, and idgs5.c (sender for Solaris `sys V' UNIX on SPARC, zeppo).
Sending Internet Datagram Messages
struct sockaddr_in to;
struct hostent *hp, *gethostbyname();
char msgbuf[1024];
sockfd=socket(AF_INET, SOCK_DGRAM, 0)
toaddr.sin_family = AF_INET;
/* hostname and portno of the receiver may be entered from command line */
hp = gethostbyname(receiver_hostname); /* host name of receiver process */
bcopy(hp->h_addr, &to.sin_addr, hp->h_length); /* copy the 4 byte addr*/
/* sys V unix use memcpy(&to.sin_addr, hp->h_addr, hp->h_length); */
to.sin_port = htons(toPort_no); /* port number of the receiver process */
strcpy(msgbuf, "how are you?");
if (sendto(sockfd, msgbuf, strlen(msgbuf), 0, &to, sizeof(to)) < 0)
perror("sending datagram message");
zeppo> cd ~cs522/project/ipc/SPARC
received msg=This is packet one!
rcvd sockaddr_in: Domain=2, Hostname=sanluis, Port=2727, Address=128.198.2.62,
sanluis> cd ~cs522/project/ipc/I386
sending message: This is packet one!
received ack msg=received message: This is packet one!
You can copy idgr.c, idgs.c, idgs5.c, and Makefile to you local directory.
sanluis> make //You have to create I386 and SPARC
zeppo> make idg-SPARC // subdirectories for the executable code.
Receiver: Bind UNIX Domain Socket to Address
struct sockaddr_un addr, to;
char frombuf[2048], toBuf[2048];
sprintf(addr.sun_path, "/tmp/cs522.%s.server", getlogin());
unlink(addr.sun_path); /* if previous incarnation of socket still exists, kill it*/
if (bind(sockfd, &addr, sizeof(struct sockaddr_un))) {
perror("binding name to datagram socket");
/* ls -F /tmp you will find a file with the same name there */
/* the socket file has srwxr-xr-x as access right and 0 length */
Why we put the login name in the socket pathname?
In what situation this will not uniquely identify the socket?
Receiver: Waiting/Reply UNIX Domain Datagram
sprintf(to.sun_path, "/tmp/cs522.%s.client", getlogin());
printf("unix domain datagram client uses sun_path=%s\n", to.sun_path);
n = recv(sock, fromBuf, sizeof(fromBuf), 0);
if (n < 0) perror("receiving datagram message");
fromBuf[n] = 0; /* null terminate */
printf("The no. of bytes received=%d\n", n);
printf("received msg=%s", fromBuf);
sprintf(toBuf, "received message: %s\n", fromBuf);
if (sendto(sock, toBuf, strlen(toBuf), 0, &to, sizeof(to)) < 0)
perror("sending datagram message");
unlink(addr.sun_path); /* clean up by removing the socket file descriptor */
Sender: Sending UNIX Domain Datagram
/* create name structure with wildcard using INADDR_ANY */
sprintf(addr.sun_path, "/tmp/cs522.%s.client", getlogin());
if (bind(sock, &addr, sizeof(struct sockaddr_un))) {
perror("binding name to datagram socket");
sprintf(toBuf, "This is packet one!\n");
if (sendto(sock, toBuf, strlen(toBuf), 0, &to, sizeof(to)) < 0) {
perror("sending datagram message"); exit(1);l}
n = recv(sock, fromBuf, 1024, 0);
if (n < 0) perror("receiving datagram message");
fromBuf[n] = 0; /* null terminate */
printf("received ack msg=%s\n", fromBuf);
Flow Chart of Stream Connection Setup
if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
perror("socket creation error");
if (bind(sockfd,...) <0) perror("binding error");
if (listen(sockfd, 5) < 0) perror("listen error");
/* allow 5 connection request in queue*/
newsockfd = accept(sockfd, peer, addrlen);
if (newsockfd < 0) perror("accept error");
if (fork() == 0) { /* child process */
close(sockfd);
doit(newsockfd); /* process the request, first use read()*/
exit(0);
}
close(newsockfd);
}
The decision of using fork() depends on whether the complexity of the task worth the overhead of forking process.
Sample programs, istr.c, ists.c, in ~cs522/project/ipc.
before getsockname socket has port #0
What is your reply ("$" to break connection)?ok
What is your reply ("$" to break connection)?$
msg for receiver("$" to exit):test
msg for receiver("$" to exit):hi
receiver breaks the connection, exiting...
Protocol Programming Project Phase 1
Exercise 1: Compare the performance of UNIX and Internet Domain IPC.
Goal: Learn how to write distributed programs using socket for IPC.
Task: Using SOCK_DGRAM. Write a sender program that takes the UNIX domain socket pathname of a receiver process, sends consecutive 10000 small messages, "packet 1" ... "packet 10000" to the receiver on the same machine, then wait for the first ack message, print out the ack message, then exit.
Write the receiver program that takes the UNIX domain socket pathname of the sender, enter a loop that receives a message, prints out the message content, then reply with an "ACK" message, (note it will send 10000 acks.)
Compile the sender and receiver on both wetterhorn and zeppo.
Time the execution of sender on the zeppo and wetterhorn using
time sender
Exercise 2: Repeat the above exercise on Internet Domain IPC, i.e., sender and receiver at different machine. The sender takes the hostname and port number of receiver as input. The receiver prints out the port it was allocated.
Time the execution of sender with the following four situations:
a) sender on zeppo; receiver on zeppo.
b) sender on zeppo; receiver on wetterhorn.
c) sender on wetterhorn; receiver on zeppo.
d) sender on zeppo; receiver on wetterhorn.
Exercise 3: Repeat the above task by redesigning sender & receiver using SOCK_STREAM.
Report and discuss your timing results on those ten cases.
Note that you may lose messages in some of these cases.
Exercise 4: Describe what happen if multiple senders try to send messages to a receiver in both the STREAM and DATAGRAM cases.
Exercise 5: Byte ordering problem: In ~cs522/project/ipc/pens.c and penr.c
a message structure is defined as follows:
char sender[10]; /* sender login */
int sessionID; /* starting from1 */}
unsigned char msg_type;
unsigned char size; /* 1x1, 2x2, 4x4, 8x8, or 16x16 pixel size dot */
short x;
pen.x = 2; pen.y=32; strcpy(pen.header.sender, getlogin()); pen.header.sessionID = 1; pen.msg_type = 3; pen.size =8;
Use pens to send out this pen message on wetterhorn,
Use penr receive and print out all the fields in this pen message on zeppo.
See if the pen.x and pen.y changed. Use script to document your result.
Discuss why there is such a change.
Mail me your timing results and analysis, and include the pathnames to your programs (change the access right so that I can read them).
Handle multiple inputs and time-out
There is a special bit-vector type, fd_set, allowing you to indicate which file descriptors you would like to pay attention to.
FD_ZERO(fd_set * fdset ); /* clear all bits in fdset */
FD_SET(int fd , fd_set * fdset ); /*turn the bit for fd on in fdset */
FD_CLR(int fd , fd_set * fdset ); /* turn the bit for fd off in fdset */
FD_ISSET(int fd , fd_set * fdset ); /* test the bit for fd in fdset */
long tv_usec; /* microsecond */
int select(int
maxfdpl
, fd_set *
readfds
, fd_set *
writefds
, fd_set *
exceptfds
,
struct timeval *
timeout
);
maxfdpl: the number of file descriptors to be checked.
readfds: the input channels (file descriptors) to receive incoming msgs.
writedfds: the output channels (file descriptors) to send msgs.
exceptfds: the execptional signal channels (file descriptors).
timeout: a pointer to data that specify the time-out value.
If (timeout == NULL) select() will wait indefinitely until some of the channels have "actions"
If (timeout NULL) select() will return when
I/O multiplexing using select()
/* assume you have create the above sockets properly */
timeout.tv_sec = 3; /* three seconds and */
timeout.tv_usec= atol("500000"); /* 500000
m
seconds, use atol() to convert str*/
maxfdpl=(unix_d_sockfd>inet_d_sockfd)?unix_d_sockfd+1: inet_d_sockfd+1;
while (quitflag) {
FD_ZERO(&readfds); FD_SET(0, &readfds); /* listen to stdin */
FD_SET(unix_d_sockfd, &readfds); /* fd_set need to be reset, why? */
FD_SET(inet_d_sockfd, &readfds); /* because select() overwrites the value*/
if ((i=select(maxfdpl, &readfds, 0, 0, &timeout)) < 0) {
perror("select error"); exit(1); }
if (i==0) { printf("time-out"); /* return value of select() is 0*/ }
if (FD_ISSET(0, &readfds)) { scanf("%s\n", buf); /* user input */ }
if (FD_ISSET(unix_d_sockfd, &readfds)) { /* unix_d_sockfd receives msg */}
if (FD_ISSET(inet_d_sockfd, &readfds)) { /* inet_d_sockfd receives msg */}
} /* See sample program ~cs522/project/gui/ua.c for more details */
How to implement the following simple protocol using datagram sockets.
Goal: Learn how to use select and time-out.
D represents any string user type in except `q'.
When time-out happens, print out "time out n time" where n the number of time-outs during the sending of a message.
Sample of C-code implements Machine M
case 1: /* state 1 */
fflush(stdin); scanf("%s", buf);
if (strcmp(buf, "q") ==0) {
/* userinput(q) */
state=4;
} else {/* userinput(D), Dq */
state=2;}
break;
case 3: /* state 3 */
/* set time-out value */
/* set fdest */
if ((i=select(sinfd+1, fdset, 0, 0, timeout)) < 0) {
perror("select"); exit(1);}
if (i==0) /* timeout */
if (retry == 5) state=4;
else {retry++; state=2;}
Sending/Receiving Different Message Types
union Record { /* see ~cs522/project/mins/msg.h for detail */
r.pr.msgType = PEN; r.pr.X=320, r.pr.Y=600; r.pr.size=4;
r.nr.msgType=CONN; strcpy(r.nr.originator, getlogin()); strcpy(r.nr.name, dst_usr)
How to know the type of received msgs?
n = recv(sockinet, &(r.pr.msgType), sizeof(r), 0);
printf("receiving pen msg, X=%d, Y=%d\n", r.pr.X, r.pr.Y);
if (sendto(socktoGui, &(r.pr.msgType), sizeof(r), 0, &toGui, sizeof(toGui)) < 0)
perror("sending datagram msg"); /* relay the msg to GUI process */
printf("receiving connect msg, originator =%s, dst user=%s\n",
/* update table, and send conn msg to Gui */
Protocol Programming Project Phase 2
Due day:
Implement the alternating-bit protocol using internet datagram sockets.
Goal: Learn how to use select and time-out.
Sender and receiver should print out the state changes and msg exchanged.
C1 and C2 can be the same program and take command line parameter to behave normally or lose every other message.
Simulate this protocol on four machines with C1 lose odd msgs. Use script to capture the state changes on Sender and the delivered msgs on receiver side.
Send path name of the code and script to me.
Possible declaration of msgs record:
Protocol Programming Phase 3, Final Phase
The preliminary version of the graphical user interface program, sd , for the protocol is ready for you to test your user agent and intelligent server programs.
The source of sd is in the directory ~cs522/project/sharedraw
It uses the InterViews graphic package to construct the graphic user interface.
In the same directory there are a simplified version of user agent program, ua.c, and a dummy datagram server, is.c, which receives and replies with simple ack message.
For testing, is is started first, followed by ua and sd .
sd
listens to a UNIX socket, which is identified by the path
/tmp/cs522.<login>.UA2Gui
ua
listens to a UNIX socket, which is identified by the path
/tmp/cs522.<login>.Gui2UA
After each session, you should remove those paths, otherwise next time you execute the sd and ua you will get socket in use error.
Send me email if you find bugs in sd .
The current version of sd did not support multiple sessions (to multiple users). It only support shared editing among two users.
Protocol Programming Phase 3.1
Document your design of UA and IS using CFSM model. Describe the message formats used among UAs and IS. Due day 10/24
The database updates, which are triggered by the reception of "login" or connect request messages, should be documented as internal transitions, e.g.,
(S0, S1, +CR(org=$u1, dst=$u2)) // Sn is the state of the CFSM represents IS.
// org and dst are the fields in the CR (connect request) message
// $u1 and $u2 are values of the fields.
(S1, S2, $u2 entry exists in database and db.host($u2)=$h, db.portId($u2)=$n)
// $h is the value in the host field of the $u2 database entry;
// $u2 uses the most recent context of the CFSM path for its value.
(S1, S3, $u2 entry does not exist in database)
(S2, S4, -CR(org=$u1, dst=$u2)/($h, $n)) // define your own notation
// here, I use /($h, $n) to specify the socket of destination UA
(S4, S5, timeout) // How to distinguish time-outs for different requests
(S4, S6, +ACK) // is a challenging problem.
(S4, S7, -NAK)...
When the user is connected and a third party tries to connect, UA should relay the connection request and let the user decide using sharedraw interface.
On 10/26, we will set up the MINS project demo schedule.
Lessons Learned in Exercises 1&2
sending message: blanca sessionID=1 msg_type=2 size=1 x=200 y=100
62 6c 61 6e 63 61 00 00 00 00 00 00 01 00 00 00 02 01 c8 00 64 00 00 00
62 6c 61 6e 63 61 00 00 00 00 00 00 01 00 00 00 02 01 ffffffc8 00
parse: get rcv_name=blanca, SessionID=256 msg_type=0, size=0 x=513 y=-1433