closer.c

/********************************************************************************
 
 TCPClose
 
 this snippet shows how to close a connection gracefully without causing errors or forcing
 aborts on either the local or remote end.
 
 note: don't run this from the think debugger, as it doesn't like getting invoked at
       interrupt time via a DebugStr()...
       
 Steve Falkenburg
 MacDTS
 11/16/92
  
 ********************************************************************************/
 
 
#include <MacTCPCommonTypes.h>
#include <TCPPB.h>
 
// constants
 
#define kTimeout        30
#define kFTPServerIP    0x5a85342c          // change this to your ftp host !!
#define kFTPPort        21
 
// globals
 
short gDrvrRef;
Boolean gTerminated;
 
// prototypes
 
void main(void);
OSErr MakeStream(StreamPtr *stream);
OSErr DoFTPConnStuff(StreamPtr stream);
OSErr OpenConnection(StreamPtr stream,long address,short port);
OSErr CloseConnection(StreamPtr stream);
OSErr ReceiveAndJunkData(StreamPtr stream);
pascal void ASR(
        StreamPtr tcpStream,
        unsigned short eventCode,
        Ptr userDataPtr,
        unsigned short terminReason,
        struct ICMPReport *icmpMsg);
 
 
// main program...
//
void main(void)
{
    OSErr err;
    StreamPtr conn;
        
    err = OpenDriver("\p.IPP",&gDrvrRef);           // open TCP driver
    if (err!=noErr) {
        DebugStr("\pMacTCP not installed");
        return;
    }
    
    err = MakeStream(&conn);                        // create our connection stream
    if (err!=noErr) {
        DebugStr("\pcouldn't make stream");
        return;
    }
    
    err = OpenConnection(conn,kFTPServerIP,21);     // open a connection (change IP #)
    if (err!=noErr) {
        CloseConnection(conn);
        DebugStr("\pConnection not opened");
        return;
    }
    
    err = DoFTPConnStuff(conn);                     // do a short FTP session
    if (err!=noErr)
        DebugStr("\pFTP server not responding");
    
    err = CloseConnection(conn);                    // close the connection and stream
}
 
 
// opens a TCP stream and returns the stream ptr.  we use an 8k stream buffer
//
OSErr MakeStream(StreamPtr *stream)
{
    OSErr err;
    TCPiopb pBlock;
    Ptr connBuffer;
    long connBufferLen;
    
    connBufferLen = 8192;
    connBuffer = NewPtr(connBufferLen);
    if (MemError()!=noErr)
        return MemError();
        
    pBlock.ioCRefNum = gDrvrRef;
    pBlock.csCode = TCPCreate;
    pBlock.csParam.create.rcvBuff = connBuffer;
    pBlock.csParam.create.rcvBuffLen = connBufferLen;
    pBlock.csParam.create.notifyProc = ASR;
    err = PBControl(&pBlock,false);
    
    *stream = pBlock.tcpStream;
    
    if (err!=noErr)
        DisposPtr(connBuffer);
        
    return err;
}
 
 
// opens the connection to the ip # and tcp port passed in
//
 
OSErr OpenConnection(StreamPtr stream,long address,short port)
{
    OSErr err;
    TCPiopb pBlock;
 
    gTerminated = false;
 
    pBlock.ioCRefNum = gDrvrRef;
    pBlock.csCode = TCPActiveOpen;
    pBlock.tcpStream = stream;
    pBlock.csParam.open.ulpTimeoutValue = kTimeout;
    pBlock.csParam.open.ulpTimeoutAction = 1;
    pBlock.csParam.open.validityFlags = 0xC0;
    pBlock.csParam.open.commandTimeoutValue = kTimeout;
    pBlock.csParam.open.remoteHost = address;
    pBlock.csParam.open.remotePort = port;
    pBlock.csParam.open.localPort = 0;
    pBlock.csParam.open.tosFlags = 0;
    pBlock.csParam.open.precedence = 0;
    pBlock.csParam.open.dontFrag = 0;
    pBlock.csParam.open.timeToLive = 0;
    pBlock.csParam.open.security = 0;
    pBlock.csParam.open.optionCnt = 0;
    err = PBControl((ParmBlkPtr)&pBlock,false);
 
    return err;     
}
 
 
// does a short FTP session, consisting of receiving the welcome message,
// issuing a "QUIT" command, and receiving the response
//
OSErr DoFTPConnStuff(StreamPtr stream)
{
    OSErr err;
    TCPiopb pBlock;
    wdsEntry wds[2];
 
    wds[0].length = 6;
    wds[0].ptr = "QUIT\r\n";
    wds[1].length = 0;
    
    err = ReceiveAndJunkData(stream);
    if (err!=noErr)
        DebugStr("\precverror");
        
    pBlock.ioCRefNum = gDrvrRef;
    pBlock.csCode = TCPSend;
    pBlock.tcpStream = stream;
    pBlock.csParam.send.ulpTimeoutValue = kTimeout;
    pBlock.csParam.send.ulpTimeoutAction = 1;
    pBlock.csParam.send.validityFlags = 0xC0;
    pBlock.csParam.send.pushFlag = false;
    pBlock.csParam.send.urgentFlag = true;
    pBlock.csParam.send.wdsPtr = (Ptr)wds;
    err = PBControl((ParmBlkPtr)&pBlock,false);
    if (err!=noErr)
        DebugStr("\perrsend");
    
    err = ReceiveAndJunkData(stream);
    if (err!=noErr)
        DebugStr("\precverror");
    
    return err;
}
 
 
// close the connection gracefully.  this involves issuing the TCPClose, receiving data
// until we get an error (the remote end is closing) and issue a TCPRelease to get rid of
// the stream.
//
OSErr CloseConnection(StreamPtr stream)
{
    OSErr err;
    TCPiopb pBlock;
    
    pBlock.ioCRefNum = gDrvrRef;
    pBlock.csCode = TCPClose;
    pBlock.tcpStream = stream;
    pBlock.csParam.close.ulpTimeoutValue = kTimeout;
    pBlock.csParam.close.validityFlags = 0xC0;
    pBlock.csParam.close.ulpTimeoutAction = 1;
    err = PBControl((ParmBlkPtr)&pBlock,false);
    
    // receive data until the connection closes
    
    do {
        err = ReceiveAndJunkData(stream);
    } while (!gTerminated);
            
    pBlock.ioCRefNum = gDrvrRef;
    pBlock.csCode = TCPRelease;
    pBlock.tcpStream = stream;
    err = PBControl((ParmBlkPtr)&pBlock,false);
    
    if (err!=noErr)
        return err;
    
    DisposPtr(pBlock.csParam.create.rcvBuff);
    return err;
}
 
 
// receive a block of data from the remote connection, but don't even return what the data
// is.  we're using the no-copy form of the receive command for simplicity.
//
OSErr ReceiveAndJunkData(StreamPtr stream)
{
    OSErr err;
    TCPiopb pBlock;
    rdsEntry rds[3];
    
    // set up our 2-part rds
    
    rds[0].length = 0;
    rds[0].ptr = nil;
    rds[1].length = 0;
    rds[1].ptr = nil;
    rds[2].length = 0;
    rds[2].ptr = nil;
    
    // issue the receive
    
    pBlock.ioCRefNum = gDrvrRef;
    pBlock.csCode = TCPNoCopyRcv;
    pBlock.tcpStream = stream;
    pBlock.csParam.receive.commandTimeoutValue = kTimeout;
    pBlock.csParam.receive.rdsPtr = (Ptr)rds;
    pBlock.csParam.receive.rdsLength = 2;
    err = PBControl((ParmBlkPtr)&pBlock,false);
    
    // return the buffer
    
    if (err==noErr) {
        pBlock.csCode = TCPRcvBfrReturn;
        err = PBControl((ParmBlkPtr)&pBlock,false);
    }
    
    return err;
}
 
 
// our asynchronous notification routine.  this gets called several times, but we only
// look at the connection termination messages.  we do this to determine if the close
// was graceful, or if either we or the remote end had to abort.
//
pascal void ASR(
        StreamPtr tcpStream,
        unsigned short eventCode,
        Ptr userDataPtr,
        unsigned short terminReason,
        struct ICMPReport *icmpMsg)
{
    if (eventCode==TCPTerminate) {
        gTerminated = true;
        switch (terminReason) {
            case TCPULPClose:
                DebugStr("\pgraceful close on both ends");  // this is the one you want
                break;
            case TCPRemoteAbort:
                DebugStr("\premote connection forced abort");
                break;
            case TCPULPTimeoutTerminate:
                DebugStr("\pULP timeout reached so connection dropped");
                break;
            case TCPULPAbort:
                DebugStr("\pTCPRelease issued before remote end closed");
                break;
            default:
                DebugStr("\pconnection terminated non-gracefully");
                break;
        }
    }
}