Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Client.c
/* |
File: Client.c |
Contains: Client showing integration of CFSockets and UNIX domain sockets. |
Written by: DTS |
Copyright: Copyright (c) 2005 by Apple Computer, Inc., All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under Apple's |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): |
$Log: Client.c,v $ |
Revision 1.2 2005/05/18 13:36:28 eskimo1 |
Fixed various documentation/comment changes. |
Revision 1.1 2005/05/17 12:19:13 eskimo1 |
First checked in. |
*/ |
///////////////////////////////////////////////////////////////// |
// System interfaces |
#include <CoreServices/CoreServices.h> |
#include <stdlib.h> |
#include <assert.h> |
#include <unistd.h> |
#include <sys/socket.h> |
#include <sys/un.h> |
#include <signal.h> |
// Project interfaces |
#include "Protocol.h" |
#include "Common.h" |
///////////////////////////////////////////////////////////////// |
#pragma mark ***** Connection Abstraction |
// A ConnectionRef represents a connection from the client to the server. |
// The internals of this are opaque to external callers. All operations on |
// a connection are done via the routines in this section. |
enum { |
kConnectionStateMagic = 'LCCM' // Local Client Connection Magic |
}; |
typedef struct ConnectionState * ConnectionRef; |
// Pseudo-opaque reference to the connection. |
typedef Boolean (*ConnectionCallbackProcPtr)( |
ConnectionRef conn, |
const PacketHeader * packet, |
void * refCon |
); |
// When the client enables listening on a connection, it supplies a |
// function of this type as a callback. We call this function in |
// the context of the runloop specified by the client when they enable |
// listening. |
// |
// conn is a reference to the connection. It will not be NULL. |
// |
// packet is a pointer to the packet that arrived, or NULL if we've |
// detected that the connection to the server is broken. |
// |
// refCon is a value that the client specified when it registered this |
// callback. |
// |
// If the server sends you a bad packet, you can return false to |
// tell the connection management system to shut down the connection. |
// ConnectionState is the structure used to track a single connection to |
// the server. All fields after fSockFD are only relevant if the client |
// has enabled listening. |
struct ConnectionState { |
OSType fMagic; // kConnectionStateMagic |
int fSockFD; // UNIX domain socket to server |
CFSocketRef fSockCF; // CFSocket wrapper for the above |
CFRunLoopSourceRef fRunLoopSource; // runloop source for the above |
CFMutableDataRef fBufferedPackets; // buffer for incomplete packet data |
ConnectionCallbackProcPtr fCallback; // client's packet callback |
void * fCallbackRefCon; // refCon for the above. |
}; |
// Forward declarations. See the comments associated with the function definition. |
static void ConnectionShutdown(ConnectionRef conn); |
static void ConnectionCloseInternal(ConnectionRef conn, Boolean sayGoodbye); |
static int ConnectionOpen(ConnectionRef *connPtr) |
// Opens a connection to the server. |
// |
// On entry, connPtr must not be NULL |
// On entry, *connPtr must be NULL |
// Returns an errno-style error code |
// On success, *connPtr will not be NULL |
// On error, *connPtr will be NULL |
{ |
int err; |
ConnectionRef conn; |
Boolean sayGoodbye; |
assert( connPtr != NULL); |
assert(*connPtr == NULL); |
sayGoodbye = false; |
// Allocate a ConnectionState structure and fill out some basic fields. |
err = 0; |
conn = (ConnectionRef) calloc(1, sizeof(*conn)); |
if (conn == NULL) { |
err = ENOMEM; |
} |
if (err == 0) { |
conn->fMagic = kConnectionStateMagic; |
// For clean up to work properly, we must make sure that, if |
// the connection record is allocated successfully, we always |
// set fSockFD to -1. So, while the following line is redundant |
// in the current code, it's present to press home this point. |
conn->fSockFD = -1; |
} |
// Create a UNIX domain socket and connect to the server. |
if (err == 0) { |
conn->fSockFD = socket(AF_UNIX, SOCK_STREAM, 0); |
err = MoreUNIXErrno(conn->fSockFD); |
} |
if (err == 0) { |
struct sockaddr_un connReq; |
connReq.sun_len = sizeof(connReq); |
connReq.sun_family = AF_UNIX; |
strcpy(connReq.sun_path, kServerSocketPath); |
err = connect(conn->fSockFD, (struct sockaddr *) &connReq, SUN_LEN(&connReq)); |
err = MoreUNIXErrno(err); |
sayGoodbye = (err == 0); |
} |
// Clean up. |
if (err != 0) { |
ConnectionCloseInternal(conn, sayGoodbye); |
conn = NULL; |
} |
*connPtr = conn; |
assert( (err == 0) == (*connPtr != NULL) ); |
return err; |
} |
static int ConnectionSend(ConnectionRef conn, const PacketHeader *packet) |
// Send a packet to the server. Use this when you're not expecting a |
// reply. |
// |
// conn must be a valid connection |
// packet must be a valid, ready-to-send, packet |
// Returns an errno-style error code |
{ |
int err; |
assert(conn != NULL); |
assert(conn->fSockFD != -1); // connection must not be shut down |
// conn->fSockCF may or may not be NULL; it's OK to send a packet when listening |
// because there's no reply; OTOH, you can't do an RPC while listening because |
// an unsolicited packet might get mixed up with the RPC reply. |
assert(packet != NULL); |
assert(packet->fMagic == kPacketMagic); |
assert(packet->fSize >= sizeof(PacketHeader)); |
// Simply send the packet down the socket. |
err = MoreUNIXWrite(conn->fSockFD, packet, packet->fSize, NULL); |
return err; |
} |
static int ConnectionRPC( |
ConnectionRef conn, |
const PacketHeader * request, |
PacketHeader * reply, |
size_t replySize |
) |
// Perform an RPC (Remote Procedure Call) with the server. That is, send |
// the server a packet and wait for a reply. You can only use this on |
// connections that are not in listening mode. |
// |
// conn must be a valid connection |
// |
// packet must be a valid, ready-to-send, packet |
// |
// reply and replySize specify a buffer where the reply packet is placed; |
// reply size must not be NULL; replySize must not be less that the |
// packet header size (sizeof(PacketHeader)); if the reply packet is bigger |
// than replySize, the data that won't fit is discarded; you can detect this |
// by looking at reply->fSize |
// |
// Returns an errno-style error code |
// On success, the buffer specified by reply and replySize will contain the |
// reply packet; on error, the contents of that buffer is invalid; also, |
// if this routine errors the connection is no longer useful (conn is still |
// valid, but you can't use it to transmit any more data) |
{ |
int err; |
assert(conn != NULL); |
assert(conn->fSockFD != -1); // connection must not be shut down |
assert(conn->fSockCF == NULL); // RPC and listening are mutually exclusive |
// because unsolicited packet might get mixed up |
// with the reply |
assert(request != NULL); |
assert(request->fMagic == kPacketMagic); |
assert(request->fSize >= sizeof(PacketHeader)); |
assert(reply != NULL); |
assert(replySize >= sizeof(PacketHeader)); |
// Send the request. |
err = ConnectionSend(conn, request); |
// Read and validate the reply header. |
if (err == 0) { |
err = MoreUNIXRead(conn->fSockFD, reply, sizeof(PacketHeader), NULL); |
} |
if ( (err == 0) && (reply->fMagic != kPacketMagic) ) { |
fprintf(stderr, "ConnectionRPC: Bad magic (%.4s).\n", (char *) &reply->fMagic); |
err = EINVAL; |
} |
if ( (err == 0) && (reply->fType != kPacketTypeReply) ) { |
fprintf(stderr, "ConnectionRPC: Type wrong (%.4s).\n", (char *) &reply->fType); |
err = EINVAL; |
} |
if ( (err == 0) && (reply->fID != request->fID) ) { |
fprintf(stderr, "ConnectionRPC: ID mismatch (%" PRId32 ").\n", reply->fID); |
err = EINVAL; |
} |
if ( (err == 0) && ( (reply->fSize < sizeof(PacketHeader)) || (reply->fSize > kPacketMaximumSize) ) ) { |
fprintf(stderr, "ConnectionRPC: Bogus packet size (%" PRIu32 ").\n", reply->fSize); |
err = EINVAL; |
} |
// Read the packet payload that will fit in the reply buffer. |
if ( (err == 0) && (reply->fSize > sizeof(PacketHeader)) ) { |
uint32_t payloadToRead; |
if (reply->fSize > replySize) { |
payloadToRead = replySize; |
} else { |
payloadToRead = reply->fSize; |
} |
payloadToRead -= sizeof(PacketHeader); |
err = MoreUNIXRead(conn->fSockFD, ((char *) reply) + sizeof(PacketHeader), payloadToRead, NULL); |
} |
// Discard any remaining packet payload that will fit in the reply buffer. |
// The addition check in the next line is necessary to avoid the undefined behaviour |
// of malloc(0) in the dependent block. |
if ( (err == 0) && (reply->fSize > replySize) ) { |
uint32_t payloadToJunk; |
void * junkBuf; |
payloadToJunk = reply->fSize - replySize; |
junkBuf = malloc(payloadToJunk); |
if (junkBuf == NULL) { |
err = ENOMEM; |
} |
if (err == 0) { |
err = MoreUNIXRead(conn->fSockFD, junkBuf, payloadToJunk, NULL); |
} |
free(junkBuf); |
} |
// Any errors cause us to immediately shut down our connection because we |
// we're no longer sure of the state of the channel (that is, did we leave |
// half a packet stuck in the pipe). |
if (err != 0) { |
ConnectionShutdown(conn); |
} |
return err; |
} |
static void ConnectionGotData( |
CFSocketRef s, |
CFSocketCallBackType type, |
CFDataRef address, |
const void * data, |
void * info |
) |
// This is a a CFSocket callback indicating that data has arrived on the |
// socket. It's only called if the user has registered the associated |
// connection for listening. The parameter are as per the CFSocket |
// documentation. As this is a callback of type kCFSocketDataCallBack, |
// data contains newly arrived data that CFSocket has already read for us. |
{ |
#pragma unused(address) |
CFDataRef newData; |
ConnectionRef conn; |
assert(s != NULL); |
assert(type == kCFSocketDataCallBack); |
// Cast data to a CFDataRef, newData. |
newData = (CFDataRef) data; |
assert(newData != NULL); |
assert( CFGetTypeID(newData) == CFDataGetTypeID() ); |
// Cast info to a ConnectionRef. |
conn = (ConnectionRef) info; |
assert(conn->fMagic == kConnectionStateMagic); |
if ( CFDataGetLength(newData) == 0 ) { |
// End of data stream; the server is dead. |
fprintf(stderr, "ConnectionGotData: Server died unexpectedly.\n"); |
// Tell the client. |
(void) conn->fCallback(conn, NULL, conn->fCallbackRefCon); |
// Shut 'er down Clancy, she's pumping mud! |
ConnectionShutdown(conn); |
} else { |
// We have new data from the server. Appending to our buffer. |
CFDataAppendBytes(conn->fBufferedPackets, CFDataGetBytePtr(newData), CFDataGetLength(newData)); |
// Now see if there are any complete packets in the buffer; and, |
// if so, deliver them to the client. |
do { |
PacketHeader * thisPacket; |
Boolean success; |
if ( CFDataGetLength(conn->fBufferedPackets) < sizeof(PacketHeader) ) { |
// Not enough data for the packet header; we're done. |
break; |
} |
thisPacket = (PacketHeader *) CFDataGetBytePtr(conn->fBufferedPackets); |
if ( thisPacket->fMagic != kPacketMagic ) { |
fprintf(stderr, "ConnectionGotData: Server sent us a packet with bad magic (%.4s).\n", (char *) &thisPacket->fMagic); |
ConnectionShutdown(conn); |
break; |
} |
if (thisPacket->fSize > kPacketMaximumSize) { |
fprintf(stderr, "ConnectionGotData: Server sent us a packet that's just too big (%" PRIu32 ").\n", thisPacket->fSize); |
ConnectionShutdown(conn); |
break; |
} |
if ( CFDataGetLength(conn->fBufferedPackets) < thisPacket->fSize ) { |
// Not enough data for the packet body; we're done. |
break; |
} |
// Tell the client about the packet. |
success = conn->fCallback(conn, thisPacket, conn->fCallbackRefCon); |
if ( ! success ) { |
ConnectionShutdown(conn); |
break; |
} |
// Delete this packet from the front of our packet buffer. I horror at |
// the inefficiency of this, but it is sample code after all. |
CFDataDeleteBytes(conn->fBufferedPackets, CFRangeMake(0, thisPacket->fSize)); |
} while (true); |
} |
} |
static int ConnectionRegisterListener( |
ConnectionRef conn, |
CFRunLoopRef runLoop, |
CFStringRef runLoopMode, |
ConnectionCallbackProcPtr callback, |
void * refCon |
) |
// Register a listener to be called when packets arrive. Once you've done |
// this, you can no longer use conn for RPCs. |
// |
// conn must be a valid connection |
// |
// runLoop and runLoopMode specify the context in which the callback will |
// be called; in most cases you specify CFRunLoopGetCurrent() and |
// kCFRunLoopDefaultMode |
// |
// callback is the function you want to be called when packets arrive; it |
// must not be NULL |
// |
// refCon is passed to callback |
// |
// Returns an errno-style error code |
// On success, the connection has been converted to a listener and your |
// callback will be called from the context of the specific runloop when |
// a packet arrives; on error, the connection is no longer useful (conn is |
// still valid, but you can't use it to transmit any more data) |
{ |
int err; |
assert(conn != NULL); |
assert(runLoop != NULL); |
assert(runLoopMode != NULL); |
assert(callback != NULL); |
assert(conn->fSockFD != -1); // connection must not be shut down |
assert(conn->fSockCF == NULL); // can't register twice |
// Create the packet buffer. |
err = 0; |
conn->fBufferedPackets = CFDataCreateMutable(NULL, 0); |
if (conn->fBufferedPackets == NULL) { |
err = ENOMEM; |
} |
// Add the source to the runloop. |
if (err == 0) { |
CFSocketContext context; |
memset(&context, 0, sizeof(context)); |
context.info = conn; |
conn->fSockCF = CFSocketCreateWithNative( |
NULL, |
(CFSocketNativeHandle) conn->fSockFD, |
kCFSocketDataCallBack, |
ConnectionGotData, |
&context |
); |
if (conn->fSockCF == NULL) { |
err = EINVAL; |
} |
} |
if (err == 0) { |
conn->fRunLoopSource = CFSocketCreateRunLoopSource(NULL, conn->fSockCF, 0); |
if (conn->fRunLoopSource == NULL) { |
err = EINVAL; |
} |
} |
if (err == 0) { |
conn->fCallback = callback; |
conn->fCallbackRefCon = refCon; |
CFRunLoopAddSource( runLoop, conn->fRunLoopSource, runLoopMode); |
} |
// Any failure means the entire connection is dead; again, this is the |
// draconian approach to error handling. But hey, connections are |
// (relatively) cheap. |
if (err != 0) { |
ConnectionShutdown(conn); |
} |
return err; |
} |
static void ConnectionShutdown(ConnectionRef conn) |
// This routine shuts down down the connection to the server |
// without saying goodbye; it leaves conn valid. This routine |
// is primarily used internally to the connection abstraction |
// where we notice that the connection has failed for some reason. |
// It's also called by the client after a successful quit RPC |
// because we know that the server has closed its end of the |
// connection. |
// |
// It's important to nil out the fields as we close them because |
// this routine is called if any messaging routine fails. If it |
// doesn't nil out the fields, two bad things might happen: |
// |
// o When the connection is eventually closed, ConnectionCloseInternal |
// will try to send a Goodbye, which fails triggering an assert. |
// |
// o If ConnectionShutdown is called twice on a particular connection |
// (which happens a lot; this is a belts and braces implementation |
// [that's "belts and suspenders" for the Americans reading this; |
// ever wonder why Monty Python's lumberjacks sing about "suspenders |
// and a bra"?; well look up "suspenders" in a non-American dictionary |
// for a quiet chuckle :-] ) |
{ |
int junk; |
Boolean hadSockCF; |
assert(conn != NULL); |
conn->fCallback = NULL; |
conn->fCallbackRefCon = NULL; |
if (conn->fRunLoopSource != NULL) { |
CFRunLoopSourceInvalidate(conn->fRunLoopSource); |
CFRelease(conn->fRunLoopSource); |
conn->fRunLoopSource = NULL; |
} |
// CFSocket will close conn->fSockFD when we invalidate conn->fSockCF, |
// so we remember whether we did this so that, later on, we know |
// whether to close the file descriptor ourselves. We need an extra |
// variable because we NULL out fSockCF as we release it, for the reason |
// described above. |
hadSockCF = (conn->fSockCF != NULL); |
if (conn->fSockCF != NULL) { |
CFSocketInvalidate(conn->fSockCF); |
CFRelease(conn->fSockCF); |
conn->fSockCF = NULL; |
} |
if (conn->fBufferedPackets != NULL) { |
CFRelease(conn->fBufferedPackets); |
conn->fBufferedPackets = NULL; |
} |
if ( (conn->fSockFD != -1) && ! hadSockCF ) { |
junk = close(conn->fSockFD); |
assert(junk == 0); |
} |
// We always set fSockFD to -1 because either we've closed it or |
// CFSocket has. |
conn->fSockFD = -1; |
} |
static void ConnectionCloseInternal(ConnectionRef conn, Boolean sayGoodbye) |
// The core of ConnectionClose. It's called by ConnectionClose |
// and by ConnectionOpen, if it fails for some reason. This exists |
// as a separate routine so that we can add the sayGoodbye parameter, |
// which controls whether we send a goodbye packet to the server. We |
// need this because we should always try to say goodbye if we're called |
// from ConnectionClose, but if we're called from ConnectionOpen we |
// should only try to say goodbye if we successfully connected the |
// socket. |
// |
// Regardless, the bulk of the work of this routine is done by |
// ConnectionShutdown. This routine exists to a) say goodbye, if |
// necessary, and b) free the memory associated with the connection. |
{ |
int junk; |
if (conn != NULL) { |
assert(conn->fMagic == kConnectionStateMagic); |
if ( (conn->fSockFD != -1) && sayGoodbye ) { |
PacketGoodbye goodbye; |
InitPacketHeader(&goodbye.fHeader, kPacketTypeGoodbye, sizeof(goodbye), false); |
snprintf(goodbye.fMessage, sizeof(goodbye.fMessage), "Process %ld signing off", (long) getpid()); |
junk = ConnectionSend(conn, &goodbye.fHeader); |
assert(junk == 0); |
} |
ConnectionShutdown(conn); |
free(conn); |
} |
} |
static void ConnectionClose(ConnectionRef conn) |
// Closes the connection. It's legal to pass conn as NULL, in which |
// case this does nothing (kinda like free'ing NULL). |
{ |
ConnectionCloseInternal(conn, true); |
} |
///////////////////////////////////////////////////////////////// |
#pragma mark ***** Command Line Tool |
// The following routines use the connection abstraction defined above |
// to implement a simple command line tool that exercises the server. |
enum { |
kResultColumnWidth = 10 |
}; |
static void PrintResult(const char *command, int errNum, const char *arg) |
// Prints the result of a command. command is the name of the |
// command, errNum is the errno-style error number, and arg |
// (if not NULL) is the command argument. |
{ |
if (errNum == 0) { |
if (arg == NULL) { |
fprintf(stderr, "%*s\n", kResultColumnWidth, command); |
} else { |
fprintf(stderr, "%*s \"%s\"\n", kResultColumnWidth, command, arg); |
} |
} else { |
fprintf(stderr, "%*s failed with error %d\n", kResultColumnWidth, command, errNum); |
} |
} |
static void DoNOP(ConnectionRef conn) |
// Implements the "nop" command by doing a NOP RPC with the server. |
{ |
int err; |
PacketNOP request; |
PacketReply reply; |
InitPacketHeader(&request.fHeader, kPacketTypeNOP, sizeof(request), true); |
err = ConnectionRPC(conn, &request.fHeader, &reply.fHeader, sizeof(reply)); |
if (err == 0) { |
err = reply.fErr; |
} |
PrintResult("nop", err, NULL); |
} |
static void DoWhisper(ConnectionRef conn, const char *message) |
// Implements the "whisper" command by doing a whisper RPC with the server. |
// |
// The server responds to this RPC by printing the message. |
{ |
int err; |
PacketWhisper request; |
PacketReply reply; |
InitPacketHeader(&request.fHeader, kPacketTypeWhisper, sizeof(request), true); |
snprintf(request.fMessage, sizeof(request.fMessage), "%s", message); |
err = ConnectionRPC(conn, &request.fHeader, &reply.fHeader, sizeof(reply)); |
if (err == 0) { |
err = reply.fErr; |
} |
PrintResult("whisper", err, message); |
} |
static void DoShout(ConnectionRef conn, const char *message) |
// Implements the "shout" command by sending a shout packet to the server. |
// Note that this is /not/ an RPC. |
// |
// The server responds to this packet by echoing it to each registered |
// listener. |
{ |
int err; |
PacketShout request; |
InitPacketHeader(&request.fHeader, kPacketTypeShout, sizeof(request), false); |
snprintf(request.fMessage, sizeof(request.fMessage), "%s", message); |
err = ConnectionSend(conn, &request.fHeader); |
PrintResult("shout", err, message); |
} |
static Boolean GotPacket(ConnectionRef conn, const PacketHeader *packet, void *refCon) |
// DoListen registers this routine with the connection abstraction layer |
// so that it is called when a packet arrives. For a description of the |
// parameters, see the comments next to ConnectionCallbackProcPtr. |
{ |
#pragma unused(conn) |
Boolean result; |
CFRunLoopRef runLoop; |
// When we register this callback, we pass a reference to the runloop |
// as the refCon. Extract that reference here. |
runLoop = (CFRunLoopRef) refCon; |
assert(runLoop != NULL); |
assert( CFGetTypeID(runLoop) == CFRunLoopGetTypeID() ); |
result = true; |
if (packet == NULL) { |
// Server connection has gone away. No need to return false because |
// the connection is torn anyway. |
CFRunLoopStop(runLoop); |
} else { |
// We got a packet from the server. Tell the user about it. |
switch (packet->fType) { |
case kPacketTypeShout: |
if (packet->fSize != sizeof(PacketShout)) { |
fprintf(stderr, "GotPacket: Server sent us a Shout with the wrong size (%" PRIu32 ").\n", packet->fSize); |
result = false; |
} |
if (result && (packet->fID != kPacketIDNone) ) { |
fprintf(stderr, "GotPacket: Server sent us a Shout with the wrong size (%" PRId32 ").\n", packet->fID); |
result = false; |
} |
if (result) { |
PacketShout * shoutPacket; |
shoutPacket = (PacketShout *) packet; |
fprintf(stderr, "%*s heard \"%.*s\"\n", kResultColumnWidth, "", (int) sizeof(shoutPacket->fMessage), shoutPacket->fMessage); |
} |
break; |
default: |
fprintf(stderr, "GotPacket: Server sent us a packet with an unexpected type (%.4s).\n", (char *) &packet->fType); |
result = false; |
break; |
} |
} |
return result; |
} |
static void DoListen(ConnectionRef conn) |
// Implements the "listen" command. First this does a listen RPC |
// to tell the server that this connection is now a listener. Next it |
// calls ConnectionRegisterListener to register a packet listener callback |
// (GotPacket, above) on the runloop. It then runs the runloop until its |
// stopped (by a SIGINT, via SIGINTRunLoopCallback, below). |
{ |
int err; |
PacketListen request; |
PacketReply reply; |
InitPacketHeader(&request.fHeader, kPacketTypeListen, sizeof(request), true); |
err = ConnectionRPC(conn, &request.fHeader, &reply.fHeader, sizeof(reply)); |
if (err == 0) { |
err = reply.fErr; |
} |
if (err == 0) { |
err = ConnectionRegisterListener( |
conn, |
CFRunLoopGetCurrent(), |
kCFRunLoopDefaultMode, |
GotPacket, |
CFRunLoopGetCurrent() |
); |
} |
if (err != 0) { |
PrintResult("listen", err, NULL); |
} else { |
fprintf(stderr, "%*s Press ^C to quit.\n", kResultColumnWidth, "listen"); |
CFRunLoopRun(); |
} |
} |
static void DoQuit(ConnectionRef conn) |
// Implements the "quit" command by doing a quit RPC with the server. |
// The server responds to this RPC by quitting. Cleverly, it sends us |
// the RPC reply right before quitting. |
{ |
int err; |
PacketQuit request; |
PacketReply reply; |
InitPacketHeader(&request.fHeader, kPacketTypeQuit, sizeof(request), true); |
err = ConnectionRPC(conn, &request.fHeader, &reply.fHeader, sizeof(reply)); |
if (err == 0) { |
err = reply.fErr; |
} |
if (err == 0) { |
// If the quit is successful, we shut down our end of the connection |
// because we know that the server has shut down its end. |
ConnectionShutdown(conn); |
} |
PrintResult("quit", err, NULL); |
} |
static void SIGINTRunLoopCallback(const siginfo_t *sigInfo, void *refCon) |
// This routine is called in response to a SIGINT signal. |
// It is not, however, a signal handler. Rather, we |
// orchestrate to have it called from the runloop (via |
// the magic of InstallSignalToSocket). It's purpose |
// is to stop the runloop when the user types ^C. |
{ |
#pragma unused(sigInfo) |
#pragma unused(refCon) |
// Stop the runloop. Note that we can get a reference to the runloop by |
// calling CFRunLoopGetCurrent because this is called from the runloop. |
CFRunLoopStop( CFRunLoopGetCurrent() ); |
// Print a bonus newline to ensure that the next command prompt isn't |
// printed on the same line as the echoed ^C. |
fprintf(stderr, "\n"); |
} |
static void PrintUsage(const char *argv0) |
// Print the program's usage. |
{ |
const char *command; |
command = strrchr(argv0, '/'); |
if (command == NULL) { |
command = argv0; |
} else { |
command += 1; |
} |
fprintf(stderr, "usage: %s command...\n", command); |
fprintf(stderr, " commands: nop\n"); |
fprintf(stderr, " whisper <message>\n"); |
fprintf(stderr, " shout <message>\n"); |
fprintf(stderr, " listen (must be last)\n"); |
fprintf(stderr, " quit\n"); |
} |
int main (int argc, const char * argv[]) |
// The primary entry point. |
{ |
int err; |
ConnectionRef conn; |
conn = NULL; |
// If we get no arguments, just print the usage and fail. |
err = 0; |
if (argc == 1) { |
PrintUsage(argv[0]); |
err = ECANCELED; |
} |
// SIGPIPE is evil, so tell the system not to send it to us. |
if (err == 0) { |
err = MoreUNIXIgnoreSIGPIPE(); |
} |
// Organise to have SIGINT delivered to a runloop callback. |
if (err == 0) { |
sigset_t justSIGINT; |
(void) sigemptyset(&justSIGINT); |
(void) sigaddset(&justSIGINT, SIGINT); |
err = InstallSignalToSocket( |
&justSIGINT, |
CFRunLoopGetCurrent(), |
kCFRunLoopDefaultMode, |
SIGINTRunLoopCallback, |
NULL |
); |
} |
// Connect to the server. |
if (err == 0) { |
err = ConnectionOpen(&conn); |
} |
// Process the command line arguments. Basically the arguments are a |
// sequence of commands, which we process in order. The logic is |
// a little convoluted because some commands have arguments and because |
// the "listen" command must come last. |
if (err == 0) { |
Boolean printTheUsage; |
int argIndex; |
printTheUsage = false; |
argIndex = 1; |
while ( (err == 0) && (argIndex < argc) ) { |
if ( strcmp(argv[argIndex], "nop") == 0 ) { |
DoNOP(conn); |
} else if ( strcmp(argv[argIndex], "whisper") == 0 ) { |
argIndex += 1; |
if (argIndex < argc) { |
DoWhisper(conn, argv[argIndex]); |
} else { |
printTheUsage = true; |
err = ECANCELED; |
} |
} else if ( strcmp(argv[argIndex], "shout") == 0 ) { |
argIndex += 1; |
if (argIndex < argc) { |
DoShout(conn, argv[argIndex]); |
} else { |
printTheUsage = true; |
err = ECANCELED; |
} |
} else if ( strcmp(argv[argIndex], "listen") == 0 ) { |
if ( (argIndex + 1) == argc ) { // if listen is the last argument |
DoListen(conn); |
} else { |
printTheUsage = true; |
err = ECANCELED; |
} |
} else if ( strcmp(argv[argIndex], "quit") == 0 ) { |
DoQuit(conn); |
} else { |
printTheUsage = true; |
err = ECANCELED; |
} |
argIndex += 1; |
} |
if (printTheUsage) { |
PrintUsage(argv[0]); |
} |
} |
// Clean up. |
ConnectionClose(conn); |
if ( (err != 0) && (err != ECANCELED) ) { |
fprintf(stderr, "SimpleClientCF: Failed with error %d.\n", err); |
} |
return (err == 0) ? EXIT_SUCCESS : EXIT_FAILURE; |
} |
Copyright © 2005 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2005-07-26