Logo Search packages:      
Sourcecode: pantomime version File versions  Download package

TCPConnection.m

/*
**  TCPConnection.m
**
**  Copyright (c) 2001, 2002, 2003
**
**  Author: Ludovic Marcotte <ludovic@Sophos.ca>
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**  
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  Lesser General Public License for more details.
**  
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include <Pantomime/TCPConnection.h>

#include <Pantomime/Constants.h>

#include <Foundation/NSDebug.h>
#include <Foundation/NSException.h>
#include <Foundation/NSValue.h>

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>     /* For read() and write() and close() */

#ifdef MACOSX
#include <sys/uio.h>    /* For read() and write() */
#endif

#ifndef FIONBIO
#include <sys/filio.h>   /* For FIONBIO on Solaris */
#endif

#define DEFAULT_TIMEOUT 60
#define READ_BUFFER     4096

@implementation TCPConnection

//
//
//
- (void) dealloc
{
  RELEASE(name);

  TEST_RELEASE(stopTarget);

  [super dealloc];
}


//
//
//
- (id) initWithName: (NSString *) theName
             port: (int) thePort
{
  return [self initWithName: theName
             port: thePort
             connectionTimeout: DEFAULT_TIMEOUT
             readTimeout: DEFAULT_TIMEOUT
             writeTimeout: DEFAULT_TIMEOUT];
}


//
// This methods throws an exception if the connection timeout
// is exhausted and the connection hasn't been established yet.d
//
- (id) initWithName: (NSString *) theName
             port: (int) thePort
  connectionTimeout: (int) theConnectionTimeout
      readTimeout: (int) theReadTimeout
       writeTimeout: (int) theWriteTimeout
{
  struct sockaddr_in server;
  struct hostent *host_info;
  int nonblock = 1;

  stopTarget = nil;

  if ( theName == nil || thePort <= 0 )
    {
      AUTORELEASE(self);
      NSDebugLog(@"TCPConnection: Attempted to initialize with a nil name or a negative or zero port value.");
      return nil;
    }
  
  // We set our ivars through our mutation methods
  [self setName: theName];
  [self setPort: thePort];
  [self setConnectionTimeout: theConnectionTimeout];
  [self setReadTimeout: theReadTimeout];
  [self setWriteTimeout: theWriteTimeout];
  
  // We get the file descriptor associated to a socket
  fd = socket(PF_INET, SOCK_STREAM, 0);

  if ( [self fd] == -1 ) 
    {
      AUTORELEASE(self);
      NSDebugLog(@"TCPConnection: An error occured while creating the endpoint for communications");
      return nil;
    }

  // We get our hostent structure for our server name
  host_info = gethostbyname([[self name] cString]);
  
  if ( !host_info )
    {
      AUTORELEASE(self);
      NSDebugLog(@"TCPConnection: Unable to get the hostent structure.");
      return nil;
    }

  server.sin_family = host_info->h_addrtype;
  memcpy((char *)&server.sin_addr, host_info->h_addr, host_info->h_length);
  server.sin_port = htons( [self port] );

  // We set the non-blocking I/O flag on [self fd]
  if ( ioctl([self fd], FIONBIO, &nonblock) == -1 )
    {
      AUTORELEASE(self);
      NSDebugLog(@"TCPConnection: Unable to set the non-blocking I/O flag on the socket");
      return nil;
    }
  
  // We initiate our connection to the socket
  if ( connect([self fd], (struct sockaddr *)&server, sizeof(server)) == -1 )
    {
      if ( errno == EINPROGRESS )
        {
          // The socket is non-blocking and the connection cannot be completed immediately.
          fd_set fdset;
          struct timeval timeout;
          int value;
          
          // We remove all descriptors from the set fdset
          FD_ZERO(&fdset);
          
          // We add the descriptor [self fd] to the fdset set
          FD_SET([self fd], &fdset);
       
        // We set the timeout for our connection
          timeout.tv_sec = [self connectionTimeout];
          timeout.tv_usec = 0;
          
          value = select ([self fd] + 1, NULL, &fdset, NULL, &timeout);

        // An error occured..
          if ( value == -1 )
            {
            AUTORELEASE(self);
              NSDebugLog(@"TCPConnection: An error occured while calling select().");
              return nil;
            }
        // Our fdset has ready descriptors (for writability)
          else if ( value > 0 )
            {
              int soError, size;
              
            size = sizeof(soError);
             
              // We get the options at the socket level (so we use SOL_SOCKET)
              // returns -1 on error, 0 on success
              if ( getsockopt([self fd], SOL_SOCKET, SO_ERROR, &soError, &size) == -1 )
                {
              AUTORELEASE(self);
                  NSDebugLog(@"TCPConnection: An error occured while trying to get the socket options.");
                  return nil;
                }

              if ( soError != 0)
                {
              AUTORELEASE(self);
                  NSDebugLog(@"TCPConnection: connect failed.");
                  return nil;
                }
            }
        // select() has returned 0 which means that the timeout has expired.
          else
            {
            AUTORELEASE(self);
              NSDebugLog(@"TCPConnection: The connection timeout has expired.");
              return nil;
            }
        } // if ( errno == EINPROGRESS ) ...
      else
        {
        AUTORELEASE(self);
          NSDebugLog(@"TCPConnection: A general socket error occured.");
          return nil;
        }
    } // if ( connect(...) )
  
  return self;
}


//
// access / mutation methods
//

- (NSString *) name
{
  return name;
}

- (void) setName: (NSString *) theName
{
  RELEASE(name);
  RETAIN(theName);
  name = theName;
}

- (int) port
{
  return port;
}

- (void) setPort: (int) thePort
{
  port = thePort;
}

- (int) connectionTimeout
{
  return connectionTimeout;
}

- (void) setConnectionTimeout: (int) theConnectionTimeout
{
  if ( theConnectionTimeout > 0 )
    {
      connectionTimeout = theConnectionTimeout;
    }
  else
    {
      connectionTimeout = DEFAULT_TIMEOUT;
    }
}

- (int) readTimeout
{
  return readTimeout;
}

- (void) setReadTimeout: (int) theReadTimeout
{
  if ( theReadTimeout > 0 )
    {
      readTimeout = theReadTimeout;
    }
  else
    {
      readTimeout = DEFAULT_TIMEOUT;
    }
}

- (int) writeTimeout
{
  return writeTimeout;
}

- (void) setWriteTimeout: (int) theWriteTimeout
{
  if ( theWriteTimeout > 0 )
    {
      writeTimeout = theWriteTimeout;
    }
  else
    {
      writeTimeout = DEFAULT_TIMEOUT;
    }
}


//
// This method is used to return the file descriptor
// associated with our socket.
//
- (int) fd
{
  return fd;
}


//
// other methods
//
- (void) close
{
  if ( close([self fd]) < 0 )
    {
      NSDebugLog(@"TCPConnection: An error occured while closing the file descriptor associated with the socket");
    }
}


//
// Read "theLength" bytes.
//
- (NSData *) readDataOfLength: (int) theLength
{
  NSData *aData;
  char *buf;
  int len;
  
  [self _performStopSelector];

  buf = (char *) malloc( theLength * sizeof(char));
  memset(buf, 0, theLength);
  len = theLength;
  
  [self _readBytes: buf
      length: &len];

  aData = [[NSData alloc] initWithBytesNoCopy: buf  
                    length: theLength
                    freeWhenDone: YES];

  if ( [aData length] == 0 )
    {
      RELEASE(aData);
      return nil;
    }
  
  return AUTORELEASE(aData);
}


//
//
//
- (NSData *) readDataToEndOfLine
{
  NSData *aData;
 
  char *buf;
  int len;
  
  buf = (char *) malloc( READ_BUFFER * sizeof(char));
 
  [self _readBytesBySkippingCR: NO  
      buf: &buf  
      length: &len];

  aData = [NSData dataWithBytesNoCopy: buf
              length: len
              freeWhenDone: YES];
  
  return aData;
}


//
// Read a string of size theLenght (excluding the null char).
//
- (NSString *) readStringOfLength: (int) theLength
{
  NSString *aString;
  char *buf;
  int len;

  [self _performStopSelector];

  buf = (char *) malloc( (theLength + 1) * sizeof(char));
  memset(buf, 0, theLength + 1);
  len = theLength;
  
  [self _readBytes: buf
      length: &len];
  
  aString = [NSString stringWithCString: buf];
  free(buf);
  
  //NSLog(@"R: |%@|", aString);
  
  if ( [aString length] == 0 )
    {
      return nil;
    }
 
  return aString;
}


//
// The current line length limit that we read from a socket is 4096 bytes (READ_BUFFER)
// including the null char.
//
- (NSString *) readStringToEndOfLine
{
  return [self readStringToEndOfLineSkippingCR: NO];
}


//
//
//
- (NSString *) readStringToEndOfLineSkippingCR: (BOOL) aBOOL
{
  NSString *aString;

  char *buf;
  int len;
  
  buf = (char *) malloc( READ_BUFFER * sizeof(char));
 
  [self _readBytesBySkippingCR: aBOOL
      buf: &buf
      length: &len];

  aString = [NSString stringWithCString: buf];
  free(buf);

  //NSLog(@"R: |%@| %d", aString, len);

  if (aString == nil || [aString length] == 0)
    {
      return nil;
    }
  else
    {
      return aString;
    }
}


//
// Write a 'line' to the socket. A line is a simple string object
// terminated by a CRLF.
//
- (BOOL) writeLine: (NSString *) theLine
{
  return [self writeString: [NSString stringWithFormat: @"%@\r\n", theLine] ];
}


//
// Write a string to the socket. We currently write the cString representation
// of a Unicode string.
//
- (BOOL) writeString: (NSString *) theString
{
  char *cString;
  int len;

  //NSLog(@"S: |%@|", theString);

  [self _performStopSelector];

  cString = (char *)[theString cString];
  len = strlen( cString );
  
  [self _writeBytes: cString
      length: &len];

  return YES;
}


//
// Write bytes to the socket.
//
- (BOOL) writeData: (NSData *) theData
{
  char *bytes;
  int len;

  [self _performStopSelector];

  bytes = (char*)[theData bytes];
  len = [theData length];
  
  [self _writeBytes: bytes
      length: &len]; 
  
  return YES;
}


//
//
//
- (void) setStopTarget: (id) theTarget
{
  if ( theTarget )
    {
      RETAIN(theTarget);
      RELEASE(stopTarget);
      stopTarget = theTarget;
    }
}


//
//
//
- (void) setStopSelector: (SEL) theSelector
{
  stopSelector = theSelector;
}

@end


//
// private methods
// 
@implementation TCPConnection (Private)

- (void) _readBytes: (char *) theBytes
           length: (int *) theLength
{
  int tot, bytes;
  
  tot = bytes = 0;

  while ( tot < *theLength )
    {
      [self _performStopSelector];
      
      if ( (bytes = read([self fd], theBytes + tot, *theLength - tot)) == -1 )
      {    
        // The socket is non-blocking and there was no data immediately available for reading.
        if ( errno == EAGAIN )
          {
            fd_set fdset;
            struct timeval timeout;
            int value;
            
            // We remove all descriptors from the set fdset
            FD_ZERO(&fdset);
            
            // We add the descriptor [self fd] to the fdset set
            FD_SET([self fd], &fdset);
            
            // We set the timeout for our connection
            timeout.tv_sec = [self readTimeout];
            timeout.tv_usec = 0;
            
            value = select([self fd] + 1, &fdset, NULL, NULL, &timeout);
            
            // An error has occrured, we generate our read exception
            if ( value == -1 )
            {
              NSDebugLog(@"Error occured for read()");
            }
            // Our fdset has ready descriptors (for readability)
            else if ( value > 0 )
            {       
              bytes = read([self fd], theBytes + tot, *theLength - tot);
              tot += bytes;
            }
            // select() has returned 0 which means that the timeout has expired.
            // We generate an exception.
            else 
            {
              NSException *anException;
              
              NSDebugLog(@"Timeout has expired for read()");
              
              anException = [NSException exceptionWithName: PantomimeReadTimeoutException
                                   reason: @"Timeout has expired for read()"
                                   userInfo: nil];
              [anException raise];
              
            }
          } // if ( errno == EAGAIN )
      }
      else
      {
        tot += bytes;
      }
    } // while (...)
}


//
//
//
- (void) _readBytesBySkippingCR: (BOOL) aBOOL
                      buf: (char **) buf
                   length: (int *) theLength
{
  int i, len, size;
  char c;

  memset(*buf, 0, READ_BUFFER);
  size = READ_BUFFER;
  len = 1;  
  i = 0;

  while ( YES )
    {
      [self _performStopSelector];
      
      [self _readBytes: &c
          length: &len];

      // We verify if we must expand our buffer
      if ( (i+1) == (size - 2) )
      {
        size += READ_BUFFER;
        *buf = realloc(*buf, size); 
        memset(*buf+READ_BUFFER, 0, READ_BUFFER);
      }

      if ( !aBOOL )
        {
          (*buf) = c;
          i++;
        }

      if (c == '\n')
        {
          break;
        }
     
      // We skip the \r
      if (aBOOL && c != '\r' )
        {
          (*buf) = c;
          i++;
        }
    }

  *theLength = i;
}


//
//
//
- (void) _writeBytes: (char *) theBytes
            length: (int *) theLength
{
  int tot, bytes;
  
  tot = bytes = 0;

  while ( tot < *theLength )
    {
      [self _performStopSelector];

      if ( (bytes = write([self fd], theBytes + tot, *theLength - tot)) == -1 )
      {
        // The socket is non-blocking and there was no room in the socket
        // connected to fd to write the data immediately.
        if ( errno == EAGAIN )
          {
            fd_set fdset;
            struct timeval timeout;
            int value;
            
            // We remove all descriptors from the set fdset
            FD_ZERO(&fdset);
            
            // We add the descriptor [self fd] to the fdset set
            FD_SET([self fd], &fdset);
            
            // We set the timeout for our connection
            timeout.tv_sec = [self writeTimeout];
            timeout.tv_usec = 0;
            
            value = select([self fd] + 1, NULL, &fdset, NULL, &timeout);
            
            // An error has occrured, we generate our write exception
            if ( value == -1 )
            {
              NSDebugLog(@"Error occured for write()");
            }
            // Our fdset has ready descriptors (for writability)
            else if ( value > 0 )
            {
              bytes = write([self fd], theBytes + tot, *theLength - tot);
              tot += bytes;
            }
            // select() has returned 0 which means that the timeout has expired.
            // We generate an exception.
            else 
            {
              NSException *anException;
             
              NSDebugLog(@"Timeout has expired for write()");
              
              anException = [NSException exceptionWithName: PantomimeWriteTimeoutException
                                   reason: @"Timeout has expired for write()"
                                   userInfo: nil];
              [anException raise];
            }
          } // if ( errno == EAGAIN )
      }
      else
      {
        tot += bytes;
      }
    } // while (...)
}


//
//
//
- (void) _performStopSelector
{
  if ( stopTarget && [stopTarget respondsToSelector: stopSelector] )
    {
      NSNumber *aNumber;

      aNumber = [stopTarget performSelector: stopSelector];

      if (aNumber && [aNumber boolValue])
      {       
        NSException *anException;
        
        NSDebugLog(@"TCPConnection: Stopping all network operations.");
 
        anException = [NSException exceptionWithName: PantomimeStopException
                             reason: @"..."
                             userInfo: nil];
        [anException raise];
      }
    }
}

@end

Generated by  Doxygen 1.6.0   Back to index