OPENSTEP Sockets
Volume Number: 13
Issue Number: 7
Column Tag: Rhapsody
OPENSTEP Sockets
by Jason Proctor, BroadQuest, Inc.
Network programming in the brave new world of BSD
Sockets, a networking interface under Rhapsody
Welcome to the Pleasure Dome
The Macintosh is about to take a large step into a brave new world. It's not quite the
brave new world you or I expected or perhaps wanted, but it's going to happen
nevertheless. Despite Apple's assurances to the contrary, I expect the first
manifestation of the next-generation Macintosh OS to bear more resemblance to
NEXTSTEP than to the current MacOS. Hence, it's a fair bet that we'll be getting
unix-style networking. Indeed, a large clue has appeared with the news that Open
Transport is on the list of projects that Apple is no longer continuing to develop.
I've been doing networking stuff for a good while, for MacOS and NEXTSTEP as well as
other environments; so, it seemed a good idea (at the time) to write an article
explaining how to use the new (actually rather old) APIs to do useful things.
Assumed Knowledge
The purpose of this article is to describe in detail the BSD sockets API, rather than the
ins and outs of TCP/IP. Hence, I'll assume you've done a fair bit of network hacking
before. You should be comfortable with IP addresses and port numbers, binding and
connecting, and that kind of thing.
What's in this article
To start with, I'll describe a few major things that are different between the good old
Mac APIs we're used to and the even older BSD sockets APIs you're probably going to
have to get used to. I'll then provide a library of routines that make dealing with the
socket library a touch easier, and finally I'll use the library to construct a simple
server application.
Differences Between MacOS and BSD Sockets
Synchronicity
MacTCP, and to a lesser extent OT, were developed for the MacOS environment, which
is essentially co-operatively scheduled. Hence, making synchronous I/O calls is a bad
idea. Establishing a connection to a slow or distant site could take a good few seconds,
during which time the machine can not respond to any user action. For this reason,
these calls normally should be called asynchronously with one of a variety of
strategies used to detect and respond to completion. It's beyond the scope of this article
to go into those strategies, but suffice it to say that they boil down to completion
routine chaining, checking during idle time, or using a Thread Manager thread to spin
in a yielding loop.
In complete contrast however, the socket library was initially developed for Berkeley
unix, a pre-emptively scheduled environment, and therefore synchronous calls are
the norm. Indeed, there are no well-developed asynchronous facilities such as you
would find under MacOS, because there is generally no need for them. Provided
Rhapsody delivers on its pre-emptive multitasking promise, we can all use
synchronous calls and get away with it.
Of course, that's not to say that there aren't any asynchronous facilities at all. Whilst
being universally panned for having a dearth of industrial-grade real-time features,
unix does provide signals as a means of being informed when something happens.
Portability
Hitherto, Mac developers have not had to consider portability issues when writing
MacTCP or Open Transport code. Code written to these APIs is not going much further
than MacOS, even though OT is loosely based on the XTI standard.
In contrast again, the socket library has been ported to many different environments,
and one should always write socket code to be portable. One never knows when one
might want to take one's precious code to unix, or anywhere else for that matter.
Fortunately however, we only have one major portability concern to worry about --
the socket library expects all its IP addresses and port numbers in network (i.e.,
big-endian) format. By virtue of being initially hosted on the 68k processor, MacOS
has its bytes the correct way round, which is why we haven't had to worry about it
until now.
So, look out for the convertor routines in the code samples. I've illustrated their use
with comments.
Protocol Independence
The socket library was designed with a certain degree of protocol independence in
mind. This manifests in the API as socket calls taking several configuration
parameters, and as other calls, if appropriate, taking pointers to generic address
structures -- pointers to protocol-specific address structures must be cast
appropriately.
However, implementing new transport protocols (such as UDP and TCP) generally
means writing kernel servers, not a trivial task, and the competing XTI standard (on
which OT is based) provides a slightly better interface for alternative protocols, hence
other protocols which use the sockets interface are rare.
One extra protocol is normally available under standard BSD sockets, that of unix
domain sockets. Although certain unix systems use these for IPC, they're generally
used by normal people about as often as PowerTalk, so I won't reference them further.
Details are available in the unix manual pages.
Network API Concepts
The BSD socket library API calls are substantially different to MacTCP and, to a lesser
extent, OT, but the actual concepts one deals with are similar, as at the end of the day
it's all just TCP/IP. Hence, anyone used to sockets, ports, connections, and the
difference between stream-based and datagram-based communication will be off to a
flying start. Basically you just do this:
• Open a socket.
• Bind to a port.
• Establish a connection (optional for best-effort datagram-based service).
• Send and receive some data.
• Shut down the connection gracefully.
Making the Transition
We're all engineers, aren't we? On to the code.
Step 0: Dealing with IP Addresses
As previously indicated, the socket library requires its IP number and port values in
network (that is, big-endian) format. It's easy to forget to convert backwards and
forwards all the time. Also, the IP address structure is rather arcane, so a convenient
way of accessing that structure is a big help. So, I use the following routines to make
dealing with the socket address structure a touch more convenient.
Listing 1: SocketCalls.c
SetAddress
This routine configures an internet address structure from the passed
in
IP number and port number parameters.
/* assumption: sizeof(long) == sizeof(IP address) */
/* this is not true for IPv6 */
void
SetAddress (struct sockaddr_in *aAddress,
unsigned long aIPNumber, unsigned short aPort)
{
/* set address family */
aAddress->sin_family = PF_INET;
/* IP number is assumed to be in network format */
/* this is because IP numbers are rarely constructed manually */
/* and are normally obtained via name lookup calls, etc */
aAddress->sin_addr.S_un.S_addr = aIPNumber;
/* port number is assumed to be in host format */
aAddress->sin_port = htons (aPort);
}
GetAddress
This routine configures an internet address structure from the passed
in
IP number and port number parameters.
/* assumption: sizeof(long) == sizeof(IP address) */
/* this is not true for IPv6 */
void
GetAddress (struct sockaddr_in *aAddress,
unsigned long *aIPNumber, unsigned short *aPortNumber)
{
/* the IP number is returned in network format */
*aIPNumber = aAddress->sin_addr.S_un.S_addr;
/* the port number is returned in host format */
*aPortNumber = ntohs (aAddress->sin_port);
}
Step 1: Opening A Socket
To do anything, you must open a socket and tell the socket library what kind of
communication you're ultimately going to be doing. When writing to the socket
library, you call socket() and supply the appropriate arguments.
The socket() call takes three arguments -- the protocol family, the type of
communication to be used, and the protocol within the protocol family. If we're
intending to communicate TCP/IP, the first argument will always be PF_INET,
signifying the internet domain protocol family. The second argument will be
SOCK_STREAM or SOCK_DGRAM, according to whether we will be communicating via
streams or datagrams. The third argument will be IPPROTO_TCP or IPPROTO_UDP,
according to whether we will be communicating with the TCP or UDP protocol.
Note that the latter two arguments are of course linked. If you want to communicate