BSD Sockets: A Quick And Dirty Primer

来源:百度文库 编辑:神马文学网 时间:2024/04/29 03:50:34
BSD Sockets: A Quick And Dirty Primer
Jim Frost
Software Tool & Die
Copyright (c) 1988, 1994 Jim Frost
All Rights Reserved
Last changed December 8, 1996
Table Of Contents
Introduction
The Analogy (or: What is a socket, anyway?)
Installing Your New Phone (or: How to listen for socket connections)
Dialing (or: How to call a socket)
Conversation (or: How to talk between sockets)
Hanging Up (or: What to do when you're done with a socket)
Speaking The Language (or: Byte-order is important)
The Future Is In Your Hands (or: What to do now)
As you delve into the mysteries of UNIX, you find more and morethings that are difficult to understand immediately. One of thesethings, at least for most people, is the BSD socket concept. This isa short tutorial that explains what they are, how they work, and givessample code showing how to use them.

The socket is the BSD method for accomplishing interprocesscommunication (IPC). What this means is a socket is used to allow oneprocess to speak to another, very much like the telephone is used toallow one person to speak to another.
The telephone analogy is a very good one, and will be usedrepeatedly to describe socket behavior.
In order for a person to receive telephone calls, he must firsthave a telephone installed. Likewise you must create a socket tolisten for connections. This process involves several steps. Firstyou must make a new socket, which is similar to having a telephoneline installed. The socket() command is used to do this.
Since sockets can have several types, you mustspecify what type of socket you want when you create one. One optionthat you have is the addressing format of a socket. Just as the mailservice uses a different scheme to deliver mail than the telephonecompany uses to complete calls, so can sockets differ. The two mostcommon addressing schemes are AF_UNIX and AF_INET . AF_UNIX addressing usesUNIX pathnames to identify sockets; these sockets are very useful forIPC between processes on the same machine. AF_INETaddressing uses Internet addresses which are four-byte numbers usuallywritten as four decimal numbers separated by periods (such as192.9.200.10). In addition to the machine address, there is also aport number which allows more than one AF_INET socketon each machine. AF_INET addresses are what we willdeal with here, as they are the most useful and widely used.
Another option which you must supply when creating a socket is thetype of socket. The two most common types are SOCK_STREAM and SOCK_DGRAM . SOCK_STREAM indicates that data will come across thesocket as a stream of characters, while SOCK_DGRAMindicates that data will come in bunches (called datagrams ).We will be dealing with SOCK_STREAM sockets, whichare the most common and easiest to use.
After creating a socket, we must give the socket an address tolisten to, just as you get a telephone number so that you can receivecalls. The bind() function is used to do this (it binds asocket to an address, hence the name).
SOCK_STREAM type sockets have the ability to queue incomingconnection requests, which is a lot like having "call waiting" foryour telephone. If you are busy handling a connection, the connectionrequest will wait until you can deal with it. Thelisten() function is used to set the maximum number ofrequests (up to a maximum of five, usually) that will be queued beforerequests start being denied. While it is not necessary to use thelisten() function, it's good practice.
The following function shows how to use the socket(),bind(), and listen() functions to establisha socket which can accept calls:
/* code to establish a socket; originally from bzs@bu-cs.bu.edu*/int establish(unsigned short portnum){ char myname[MAXHOSTNAME+1];int s;struct sockaddr_in sa;struct hostent *hp;memset(&sa, 0, sizeof(struct sockaddr_in)); /* clear our address */gethostname(myname, MAXHOSTNAME); /* who are we? */hp= gethostbyname(myname); /* get our address info */if (hp == NULL) /* we don't exist !? */return(-1);sa.sin_family= hp->h_addrtype; /* this is our host address */sa.sin_port= htons(portnum); /* this is our port number */if ((s= socket(AF_INET, SOCK_STREAM, 0)) < 0) /* create socket */return(-1);if (bind(s,(struct sockaddr *)&sa,sizeof(struct sockaddr_in)) < 0) {close(s);return(-1); /* bind address to socket */}listen(s, 3); /* max # of queued connects */return(s);}
After you create a socket to get calls, you must wait for calls tothat socket. The accept() function is used to do this.Calling accept() is analogous to picking up the telephoneif it's ringing. Accept() returns a new socket which isconnected to the caller.
The following function can be used to accept a connection on a socketthat has been created using the establish() function above:
/* wait for a connection to occur on a socket created with establish()*/int get_connection(int s){ int t; /* socket of connection */if ((t = accept(s,NULL,NULL)) < 0) /* accept connection if there is one */return(-1);return(t);}
Unlike with the telephone, you may still accept calls whileprocessing previous connections. For this reason you usually fork offjobs to handle each connection. The following code shows how to useestablish() and get_connection() to allowmultiple connections to be dealt with:
#include /* obligatory includes */#include #include #include #include #include #include #include #include #define PORTNUM 50000 /* random port number, we need something */void fireman(void);void do_something(int);main(){ int s, t;if ((s= establish(PORTNUM)) < 0) { /* plug in the phone */perror("establish");exit(1);}signal(SIGCHLD, fireman); /* this eliminates zombies */for (;;) { /* loop for phone calls */if ((t= get_connection(s)) < 0) { /* get a connection */if (errno == EINTR) /* EINTR might happen on accept(), */continue; /* try again */perror("accept"); /* bad */exit(1);}switch(fork()) { /* try to handle connection */case -1 : /* bad news. scream and die */perror("fork");close(s);close(t);exit(1);case 0 : /* we're the child, do something */close(s);do_something(t);exit(0);default : /* we're the parent so look for */close(t); /* another connection */continue;}}}/* as children die we should get catch their returns or else we get* zombies, A Bad Thing. fireman() catches falling children.*/void fireman(void){while (waitpid(-1, NULL, WNOHANG) > 0);}/* this is the function that plays with the socket. it will be called* after getting a connection.*/void do_something(int s){/* do your thing with the socket here::*/}

You now know how to create a socket that will accept incomingcalls. So how do you call it? As with the telephone, you must firsthave the phone before using it to call. You use thesocket() function to do this, exactly as you establish asocket to listen to.
After getting a socket to make the call with, and giving it anaddress, you use the connect() function to try to connectto a listening socket. The following function calls a particular portnumber on a particular host:
int call_socket(char *hostname, unsigned short portnum){ struct sockaddr_in sa;struct hostent *hp;int a, s;if ((hp= gethostbyname(hostname)) == NULL) { /* do we know the host's */errno= ECONNREFUSED; /* address? */return(-1); /* no */}memset(&sa,0,sizeof(sa));memcpy((char *)&sa.sin_addr,hp->h_addr,hp->h_length); /* set address */sa.sin_family= hp->h_addrtype;sa.sin_port= htons((u_short)portnum);if ((s= socket(hp->h_addrtype,SOCK_STREAM,0)) < 0) /* get socket */return(-1);if (connect(s,(struct sockaddr *)&sa,sizeof sa) < 0) { /* connect */close(s);return(-1);}return(s);}
This function returns a connected socket through which data canflow.
Now that you have a connection between sockets you want to send data between them. The read() and write() functions are used to do this, just as they are for normal files. There is only one major difference between socket reading and writing and file reading and writing: you don't usually get back the same number of characters that you asked for, so you must loop until you have read the number of characters that you want. A simple function to read a given number of characters into a buffer is:
int read_data(int s, /* connected socket */char *buf, /* pointer to the buffer */int n /* number of characters (bytes) we want */){ int bcount; /* counts bytes read */int br; /* bytes read this pass */bcount= 0;br= 0;while (bcount < n) { /* loop until full buffer */if ((br= read(s,buf,n-bcount)) > 0) {bcount += br; /* increment byte counter */buf += br; /* move buffer ptr for next read */}else if (br < 0) /* signal an error to the caller */return(-1);}return(bcount);}
A very similar function should be used to write data; we leavethat function as an exercise to the reader.
Just as you hang up when you're through speaking to someone overthe telephone, so must you close a connection between sockets. Thenormal close() function is used to close each end of asocket connection. If one end of a socket is closed and the othertries to write to its end, the write will return an error.
Now that you can talk between machines, you have to be careful whatyou say. Many machines use differing dialects, such as ASCII versus(yech) EBCDIC. More commonly there are byte-order problems. Unlessyou always pass text, you'll run up against the byte-order problem.Luckily people have already figured out what to do about it.
Once upon a time in the dark ages someone decided which byte orderwas "right". Now there exist functions that convert one to the otherif necessary. Some of these functions are htons() (hostto network short integer), ntohs() (network to host shortinteger), htonl() (host to network long integer), andntohl() (network to host long integer). Before sendingan integer through a socket, you should first massage it with thehtonl() function:
i= htonl(i);write_data(s, &i, sizeof(i));
and after reading data you should convert it back withntohl():
read_data(s, &i, sizeof(i));i= ntohl(i);
If you keep in the habit of using these functions you'll be lesslikely to goof it up in those circumstances where it is necessary.
Using just what's been discussed here, you should be able to buildyour own programs that communicate with sockets. As with all newthings, however, it would be a good idea to look at what's alreadybeen done. While there are not a lot of books describing BSD sockets,one good reference is Unix Network Programming byW. Richard Stevens (Prentice-Hall 1990, ISBN0-13-949876-1). In addition, you should look at some of the manypublic-domain applications which make use of sockets, since realapplications are the best teachers. One such application ismsend, autility used to send messages between users on different hosts.
Beware that the examples given here leave out a lot of errorchecking which should be used in a real application. You should checkthe manual pages for each of the functions discussed here for furtherinformation. If you have specific questions regarding sockets, pleasefeel free to ask me atjimf@world.std.com.