Simple Sockets v 1.1

Maintenance Release

by Michael Trent.

HTML by Chris Hanson <chanson@mcs.com>.

0. Manual Overview

SectionTopic
1Introduction
2Distribution
3Installation
4Using Simple Sockets
5Simple Socket Routines
6Making Your Own Spin Routines
7Error Handling
8Suggested Reading List
9Simple Sockets License Agreement

1. Introduction

Simple Sockets arose out of an effort to port Sun Microsystems's RPCSRC 4.0 to the Macintosh operating system, using MacTCP for TCP/IP and UDP/IP support. RPC relies on BSD UNIX socket calls for Inter Process Communication, like any good UNIX software should. After tinkering with a number of socket solutions for the Macintosh (GUSI, some weird BSD source I received from University of Toronto, and so on), I decided to bite the bullet and simulate BSD sockets myself.

BSD compatibility has not been my primary goal however. There are a number of facets of UNIX socket calls that are artifacts of earlier systems, remaining for backwards compatibility. Also socket calls are designed to be more flexible than MacTCP can be. So, some calls were simplified a bit for the sake of "progress".

As a result, most UNIX socket code won't compile without some modification (perhaps this makes the code useless to most people). As time permits, I hope to close the gap between Simple Sockets and BSD sockets a little more.

On the off chance that someone might find this code useful, I am willingly giving all this code up into the Public Domain (my friends say "Sell It! Sell It!", but some perverse sense of virtue prevents me from charging money for anything I think is valuable - see my notoriously unpopular shareware program "CPU Reporter" for the corrollary: charge money for useless software). If someone out there makes some changes to my code that are valuable additions or bug fixes, I'd love to hear about it, so I can update my master copies. I hope to post updates to the Info-Mac archive when-and-if changes occur.

Of course, there are other BSD Socket implementations out there, some of them much better than my humble project (again: GUSI). If you think this project has any value at all, please drop me a line, e- or snail- mail. I can be reached at:

	Michael D. Trent
	3302 Leopold Way #110
	Madison, WI 53713
	
	work e-mail:  mtrent@epicsys.com
	pleasure e-mail:  mtrent@msn.fullfeed.com

E-mail is preferred. Please try my "pleasure" e-mail first - I do check it daily.

Oh, and this is version 1.1. For a short list of changes, see the "Read Me First" file, if you haven't already. If you get a chance, glance at "To Do" as well. Enjoy!

2. Distribution

The Simple Sockets archive contains a number of important directories, including:

3. Installation

Projects that use Simple Sockets need to have access to the butil ƒ and ip ƒ directories, as well as the headers for MacTCP. Please refer to your compiler documentation for information on how to do this. With Metrowerks CodeWarrior, three different strategies leap to my mind:

  1. Go into your project's preferences and set the project's Access Path to include both directories. While this works, it's a little tedious to do once, never mind more several projects.
  2. Copy the directories into the application's project directory. Of course, if you are planning on writing lots of different Simple Sockets apps, you'll be doing a lot of copying.
  3. Move the directories into the same folder as your Metrowerks compiler. I am personally quite fond of this solution, personally.

    Note: Metrowerks Codewarrior 7.0 complicates this a little. The compiler no longer looks for code in it's directory. You'll need to bury the routines in one of the compiler's subdirectories. See the Metrowerks documentation for more details.

I believe these strategies will also work with the THINK compilers.

4. Using Simple Sockets

Using Simple Sockets is relatively easy, especially for those programmers with UNIX socket programming experience. As I have alluded to already, some Simple Socket routines are similar to, but not identical to, their UNIX counterparts. These differences will be explained later in this document.

The following files must be added to one's project: butil.c, ip.c, iperr.c, and iplow.c. ipdr.c is optional, and is probably only necessary if you are porting some existing UNIX code to the Macintosh.

The file MacTCPCommonTypes.h must be #included before any ip files are. For basic socket support, one would include:

	 #include <MacTCPCommonTypes.h>	 
	 #include "ip.h"
	 #include "iperr.h"

The Simple Sockets library must be initialized at run time before any socket routines are called. The routine "InitMacTCP" opens the MacTCP driver and performs some additional house-keeping. It shouldn't hurt to place InitMacTCP in your ToolBoxInit (or whatever) procedure.

Before the program terminates, it must make sure that all active sockets are closed. If a program quits without letting MacTCP know a stream is free, MacTCP can (and will!) crash the entire machine in a blaze of glory (if you don't believe me, force quit NCSA Telnet a few times). To be safe, you can call "DisposeMacTCP" before quitting; DisposeMacTCP will close all remaining streams for you. (Tip: Make sure you call this at the end of the program, and before any "ExitToShell" calls!)

All socket calls are asynchronous. While a program is waiting for some network transaction to take place, the computer continues multi-tasking away. This is provided in part by a secondary event loop (called a "Spin Routine"), which calls WaitNextEvent and provides basic event processing. In order to provide spinning cursors or Dialogs with "Cancel" buttons, you must define your own Spin Routine; see "Making Your Own Spin Routines" below for more information.

5. Simple Socket Routines

At this time, only the routines in ip.c and ipdr.c are documented in this manual. Routines in other files are either fairly straight forward (IMHO) or they shouldn't be of much use to other people (e.g., iplow.c). Other files may be documented here in the future.

GENERAL ROUTINES

InitMacTCP

	OSErr InitMacTCP(void)

InitMacTCP must be called by the application before making other calls to routines in the Simple Sockets library. It initializes the MacTCP driver, clears the socket list, and sets the default spin routine.

DisposeMacTCP

	void DisposeMacTCP(void)

DisposeMacTCP should be called before an application terminates, or when it is done with the MacTCP driver. DisposeMacTCP closes all initialized sockets, and disposes sockets' buffer memory.

SetSpin

	void SetSpin(Spin spinRoutine)

SetSpin sets the spin routine. The spin routine is a secondary event loop called repeatedly by iplow.c while waiting for MacTCP calls to complete. This allows other processes on the Macintosh to continue operating in their co-operative multi-tasking environment. For information on writing your own spin routine, see "Making Your Own Spin Routines" below.

ADDRESS CONVERSION AND LOOKUP ROUTINES

num2dot

	void num2dot(unsigned long ip, char *dot)

num2dot is a small routine to convert an unsigned long ip number into the human-readable dot notation (e.g., 2421762054 becomes "144.89.40.6"). To be safe, dot should point to at least 16 bytes of memory.

num2dot isn't part of BSD UNIX sockets.

ConvertStrToAddr

	OSErr ConvertStrToAddr(char *name, unsigned long *ipNum)

This routine converts strings to unsigned long ip numbers. The string can contain either a dot notation ip address (e.g., "144.89.40.6"), or a DNS host name (e.g., "stu.beloit.edu"). It returns an OSErr passed on from MacTCP.

ConvertStrToAddr isn't part of BSD UNIX sockets.

GetHostByName

	unsigned long GetHostByName(char *name)

GetHostByName emulates the UNIX routine by the same name (gethostbyname is #defined as GetHostByName). Given a string containing a DNS name or a dot separated number, GetHostByName will return that host's unsigned long ip number. If an error occurs, GetHostByName will return 0.

GetProtoByName

	int GetProtoByName(char *name)

GetProtoByName roughly emulates the UNIX routine by the same name (getprotobyname is #defined as GetProtoByName in ip.h). Given a string containing the name of a network protocol, GetProtoByName will return the integer corresponding to it's protocol; it will return 0 if it doesn't recognize the protocol. Currently, GetProtoByName only understands "tcp" and "udp".

This routine differs from the UNIX routine in that it returns a simple int, rather than a record of information. This is largely because MacTCP only understands TCP/IP and UDP/IP.

GetHostName

	int GetHostName(char *name, int namelen)

GetHostName emulates the UNIX routine by the same name (gethostname is #defined as GetHostName). GetHostName returns the name of the local computer in name. The routine returns 0 if successful; it will return -1 if an error occurred.

Note: Since any given Macintosh may not have a local name (unlike UNIX machines which DO have local names), GetHostName will return the local host's dot-separated IP number if no DNS name exists for the local machine.

Warning: Currently, namelen is unused, provided only for UNIX compatibility. name must point to a "sufficiently large" amount of memory. This should be fixed in the future.

GetHostNameOnly

	int GetHostNameOnly(char *name)

GetHostNameOnly is a spinoff of GetHostName. It is identical to GetHostName except that if the local host doesn't have a DNS name, GetHostNameOnly will fail, rather than returning a dot-separated IP number. name should point to a "sufficiently large" amount of memory. GetHostNameOnly returns 0 if successful; otherwise it returns -1.

GetMyIPDot

	int GetMyIPDot(char *num)

GetMyIPDot returns the dot-separated IP address of the local machine in num. num should point to at least 16 bytes of memory. GetMyIPDot returns 0 if successful; otherwise it returns -1.

GetMyIPDot isn't part of BSD UNIX sockets.

GetMyIPNum

	unsigned long GetMyIPNum(void)

GetMyIPNum returns the unsigned long IP address of the local machine; it returns -1 if an error occurs.

GetMyIPNum isn't part of BSD UNIX sockets.

getsockname

	int getsockname (int sock, struct sockaddr_in *localaddr, int *addrlen)

getsockname is identical to the UNIX function with the same name, except localaddr is a struct sockaddr_in * rather than a struct sockaddr *. localaddr points to a sockaddr_in struct containing the port and ip numbers of the local host. *addrlen will be set to the size of the sockaddr_in struct. getsockname returns 0 if successful; otherwise it returns -1.

This routine was originally contributed by Lim Wai Kong David <limwaiko@iscs.nus.sg>

GENERAL IP ROUTINES

socket

	int socket(int family, int type, int protocol)

socket roughly emulates the UNIX function with the same name. The variables family and type are not used, they are provided solely for compatibility with UNIX code. The variable protocol must be either IPPROTO_TCP or IPPROTO_UDP. socket also reserves some memory for use by MacTCP. socket returns a socket number (currently between 0 and 31 inclusive) if successful; otherwise it returns -1.

connect

	int connect(int sock, struct sockaddr_in *raddr, int alen)

connect roughly emulates the UNIX function with the same name, except localaddr is a struct sockaddr_in * rather than a struct sockaddr *. If sock is a TCP socket, connect will open a connection from the local host to the remote host specified in *raddr. If sock is a UDP socket, the destination specified in raddr is recorded for use in other routines. connect returns 0 if successful; it returns -1 if an error occurs.

Note: The variable alen is not used, it is provided solely for compatibility with UNIX code.

bind

	int bind (int sock, struct sockaddr_in *name, int alen)

bind roughly emulates the UNIX function with the same name. bind specifies a port for a server to listen to. sock may be either a UDP or TCP socket. A port may be bound to twice; once with a TCP socket, once with a UDP socket. bind returns 0 if successful; it returns -1 if an error occurs.

Note: bind differs from the UNIX bind in several ways. First of all, name is a struct sockaddr_in * rather than a struct sockaddr *. Secondly, name->sin_addr traditionally is set to INADDR_ANY or the local system address; name->sin_addr is ignored in this implementation. Thirdly, in UNIX systems, ports 0 - 1023 (inclusive) are reserved for privilaged processes; this implementation does not preserve the notion of reserved ports. Lastly, the variable alen is not used, it is provided solely for compatibility with UNIX code.

listen

	int listen(int socket, int queuelen)

listen is a dummy routine; provided only for UNIX compatibility. Both variables are completely ignored. listen always returns 0.

If anyone can think of a way to REALLY implement listen, I'd be glad to hear it.

write

	int write (int sock, Ptr data, int len)

write is identical to the UNIX function with the same name. write sends len bytes pointed to by data across the active connection referenced by sock. sock must be a valid, connected socket; it can be either a TCP or UDP socket. write returns the number of bytes successfully written to the remote host; it returns -1 if an error prevents any data from being sent.

read

	int read (int sock, Ptr buf, int len)

read is identical to the UNIX function with the same name. read reads at most len bytes pointed to by data from the active connection referenced by sock. sock must be a valid, connected socket; it can be either a TCP or UDP socket. If there is no data outstanding, read will block until new data is received. read returns the number of bytes successfully read from the remote host; it returns -1 if an error prevents any data from being read.

close

	int close(int sock)

close is identical to the UNIX function with the same name. close closes an active socket and releases its memory. close returns 0; it can return -1 if an error occurs.

select

	int select (int nfds, unsigned long *readfs, struct timeval *timeout)

select is used to find out if any unread data is outstanding on a socket before blocking the process with a read() or similar call. The argument list is a truncated version of the original UNIX; I've kept only the aspects of the call that are important to socket programmers (at least I believe I have).

nfds is the number of file descriptors available on the system. While this can vary from UNIX system to UNIX system, you can count on the number of file descriptors known by Simple Sockets being 32 - as defined in ip.h as kNumSockets. Always pass this constant in as nfds.

*readfs specifies the sockets one wants to query. It is no coincidence that while there are 32 socket descriptors in Simple Sockets, there are 32 bits in an unsigned long number. To specify a particular socket, set it's corresponding bit to 1; set all other bits to 0. When select returns, *readfs identifies sockets with incomming data outstanding in the same way.

One can specify a length of time to wait for via *timeout. If *timeout is nil, select will poll once before returning, whether or not there is unread data. If *timeout is not nil, its fields describe the maximum amount of time select should wait before giving up and returning to the program.

In addition to returning data via *readfs, select itself returns an integer for error detection. A return value of 0 indicates success, -1 indicates failure. Simple Socket's select will only return an error if a socket was flagged that is not an initialized socket.

select is really used by advanced socket programmers — it's not really something I would expect a novice to understand just from reading this file. For more enlightenment, I must refer you to my "Suggested Reading List" (section 8).

Note: In the source file ip.c, select is in a section of code labeled "TCP SECTION". This is in error; select does infact work with TCP and UDP sockets.

Warning: This version of select has one fatal flaw: it assumes that if you read from a socket which has data outstanding you intend to read ALL the data. I have made some modifications to read() in order to prevent this situation, but it only works for TCP sockets.

TCP ROUTINES

accept

	int accept (int sock, struct sockaddr_in *sin, int *alen)

accept roughly emulates the UNIX routine by the same name. accept passively waits for a TCP connection on socket sock. When a remote client connects to the local port described in sock, accept returns a socket number describing the local connection, and *sin contains the address and port of the client. Confused? Please consult Comer and other UNIX texts.

Note: sin is a struct sockaddr_in * rather than a struct sockaddr *. Also the variable alen is not used, it is provided solely for compatibility with UNIX code.

UDP ROUTINES

recvfrom

	int recvfrom (int sock, char *buf, int len, int flags, 
			      struct sockaddr_in *sin, int *alen)

recvfrom roughly emulates the UNIX routine by the same name. It allows a program to passively wait for UDP socket information. sock is a valid UDP socket, buf points to a number of bytes in memory, len is the size of memory pointed to by buf, and *sin contains the address and port of the client upon return. If *sin is nil, packets will be accepted from any client.

Note: sin is a struct sockaddr_in * rather than a struct sockaddr *. Also the variables alen and flags are not used, they are provided solely for compatibility with UNIX code.

sendto

	int sendto (int sock, char *data, int len, int flags, 
				struct sockaddr_in *sin, int alen)

sendto roughly emulates the UNIX routine by the same name. It allows a program to send data across a UDP stream. sock is a valid UDP socket, data points to a number of bytes in memory, len is the size of memory pointed to by data, and *sin contains the address and port of the server.

Note: sin is a struct sockaddr_in * rather than a struct sockaddr *. Also the variables alen and flags are not used, they are provided solely for compatibility with UNIX code.

DATA REPRESENTATION (ipdr.c)

	int htons(int x)
	int ntohs(int x)
	long htonl(long y)
	long ntohl(long y)

These routines are provided for UNIX compatibility. Since the Macintosh byte ordering is identical to "Network byte ordering" these routines do nothing. They simply return their arguments.

6. Making Your Own Spin Routines

Spin Routines provide simple event processing, allowing the Macintosh to continue multi-tasking while waiting for a MacTCP call to return. Rather than processing events in an event loop, Spin Routines should process only a few events at a time before returning.

A Spin Routine has the following header:

	OSErr MySpinRoutine (void)

Note: Because the routine will be called by the Simple Sockets library and not by the Macintosh operating system, it shouldn't be prefixed with the "pascal" keyword.

These routines can be used to poll for user input even while the program is waiting for a MacTCP call to return. For example, the default spin routine provided by Simple Sockets allows the user to press "esc", which will cause the current MacTCP call to fail. One could easily create a window with a Cancel button, make a socket call, and use a custom Spin Routine to handle that button.

For an example Spin Routine, see the file iplow.c; the default spin routine is named "SpinDefault".

7. Error Handling

UNIX programs rely on the standard errno variable and other standard functions to provide error reporting. Unfortunately, the Macintosh doesn't have anything quite like that. Currently, Simple Sockets provides similar error reporting with two global variables: gErrno and gMacErrno. gErrno is essentially the same as the UNIX errno variable; gMacErrno will contain the normal Macintosh OSErr error number if the error was caused by a Macintosh routine. If an error occurs, make sure you check both variables.

By my own admission, error reporting in Simple Sockets is pretty weak. This should improve in the future.

8. Suggested Reading List

The following is a list of works I suggest looking into if you are interested in socket or MacTCP programming. This list includes books, electronic documents, World Wide Web pages, and the like.

9. Simple Sockets License Agreement

Simple Sockets is offered directly to the public domain. I request that it is not modified in any way and that it is accompanied by all of the original documentation unaltered. This is only a request.

Disclaimer of warranty:

In using this software, you understand and agree that this software is provided “as is” without warranty of any kind. The entire risk as to the results and performance of using this software lies entirely with you, the user. The author does not make any warranties, either expressed or implied, including but not limited to implied warranties of merchantability and fitness for a particular purpose, with respect to this software.

In no event shall the author be liable for any consequential, incidental, or special damages whatsoever (including without limitation damages for loss of critical data, loss of profits, interruption of business, and the like) arising out of the use or inability to use this software. Because some states do not allow the exclusion or limitation of liability for consequential or incidental damages, the above limitations may not apply to you.

Although the author would appreciate any feedback and bug reports, the author shall not be responsible for correcting any problems which you discover or otherwise help you maintain and use this software. Furthermore, the author may at any time replace, modify, alter, improve, enhance or change this software.

Complete agreement:

This agreement constitutes the entire agreement and supersedes any prior agreements between you and the author concerning this software. This agreement cannot be amended, modified, or waived except in writing.

General:

If any provision of this agreement shall be found to be unenforceable, it shall be deemed severed from the remainder of this agreement.