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

CServerSocket.c

/***************************************************************************

  CServerSocket.c

  Network component

  (c) 2003-2004 Daniel Campos Fernández <danielcampos@netcourrier.com>

  This is the implementation of the SocketServer class

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 1, or (at your option)
  any later version.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

***************************************************************************/

#define __CSERVERSOCKET_C


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include "main.h"
#include "tools.h"

#include "CServerSocket.h"
#include "CSocket.h"

#define UNIXPATHMAX 108


DECLARE_EVENT (Connection);
DECLARE_EVENT (CSERVERSOCKET_Error);

void srvsock_post_error(CSERVERSOCKET* mythis)
{
      GB.Raise((void*)mythis,CSERVERSOCKET_Error,0);
      GB.Unref((void**)&mythis);
}

void CServerSocket_NewChild(CSERVERSOCKET *mythis,void *cli_obj)
{
      if (mythis->nchildren++)
            GB.Realloc ( (void**)&mythis->children,mythis->nchildren);
      else
            GB.Alloc   ( (void**)&mythis->children,mythis->nchildren);
      
      mythis->children[mythis->nchildren-1]=cli_obj;
}

void CServerSocket_DeleteChild(CSERVERSOCKET *mythis,void *cli_obj)
{
      int myloop;
      int myloop2;
      
      if (!mythis->nchildren) return;
      
      for (myloop=0;myloop<mythis->nchildren;myloop++)
      {
            if (mythis->children[myloop]==cli_obj)
            {
                  for (myloop2=myloop;myloop2<(mythis->nchildren-1);myloop2++)
                        mythis->children[myloop2]=mythis->children[myloop2+1];
                  if ( --mythis->nchildren)
                  {
                        GB.Realloc ( (void**)&mythis->children,mythis->nchildren);
                  }
                  else
                  {
                        GB.Free ((void**)&mythis->children);
                        mythis->children=NULL;
                  }
                  return;
            }
      }
}

void CServerSocket_OnClose(void *sck)
{
      CSOCKET *chd=(CSOCKET*)sck;
      CSERVERSOCKET *mythis;

      if (!chd) return;
      if (!chd->c_parent) return;
      CServerSocket_DeleteChild(chd->c_parent,sck);
      mythis=(CSERVERSOCKET*)chd->c_parent;
      mythis->iCurConn--;
      
}

void CServerSocket_CallBack(int fd,int type,long lParam)
{
      int okval=0;
      char *rem_ip_buf;
      int ClientLen;
      CSERVERSOCKET *mythis;

      mythis=(CSERVERSOCKET*)lParam;
      if ( mythis->iStatus != 1) return;
      
      mythis->iStatus=2;
      ClientLen=sizeof(struct sockaddr_in);
      mythis->Client=accept(mythis->ServerSocket,(struct sockaddr*)&mythis->so_client.in,&ClientLen);
      if (mythis->Client == -1)
      {
            close(mythis->Client);
            mythis->iStatus=1;
            return;
      }
      rem_ip_buf=inet_ntoa(mythis->so_client.in.sin_addr);
      if ( (!mythis->iMaxConn) || (mythis->iCurConn < mythis->iMaxConn) ) okval=1;
      if ( (!mythis->iPause) && (okval) )
            GB.Raise(mythis,Connection,1,GB_T_STRING,rem_ip_buf,0);
      if  ( mythis->iStatus == 2) close(mythis->Client);
      mythis->iStatus=1;
}

void CServerSocket_CallBackUnix(int fd,int type,long lParam)
{
      //int position=0;
      int okval=0;
      int ClientLen;
      CSERVERSOCKET *mythis;

      mythis=(CSERVERSOCKET*)lParam;
      if ( mythis->iStatus != 1) return;
      
      mythis->iStatus=2;
      ClientLen=sizeof(struct sockaddr_un);
      mythis->Client=accept(mythis->ServerSocket,(struct sockaddr*)&mythis->so_client.un,&ClientLen);
      if (mythis->Client == -1)
      {
            close(mythis->Client);
            mythis->iStatus=1;
            return;
      }
      if ( (!mythis->iMaxConn) || (mythis->iCurConn < mythis->iMaxConn) ) okval=1;
      if ( (!mythis->iPause) && (okval) )
            GB.Raise(mythis,Connection,1,GB_T_STRING,NULL,0);
      if  ( mythis->iStatus == 2) close(mythis->Client);
      mythis->iStatus=1;

}


/***************************************************
 This property reflects current status of the
 socket (closed, listening...)
 ***************************************************/
BEGIN_PROPERTY ( CSERVERSOCKET_MaxPathLength )

      GB.ReturnInteger(UNIXPATHMAX);

END_PROPERTY
/***************************************************
 This property reflects current status of the
 socket (closed, listening...)
 ***************************************************/
BEGIN_PROPERTY ( CSERVERSOCKET_Status )

      GB.ReturnInteger(THIS->iStatus);

END_PROPERTY

/******************************************************************
 This property gets/sets the port to listen to (TCP or UDP sockets)
 ******************************************************************/
BEGIN_PROPERTY ( CSERVERSOCKET_Port )

      if (READ_PROPERTY)
      {
            GB.ReturnInteger(THIS->iPort);
            return;
      }
      if (THIS->iStatus>0)
      {
            GB.Error("Port value can not be changed when socket is active");
            return;
      }
      if ( (VPROP(GB_INTEGER)<1) || (VPROP(GB_INTEGER)>65535) )
      {
            GB.Error("Invalid Port Value");
            return;
      }
      THIS->iPort=VPROP(GB_INTEGER);

END_PROPERTY


/***************************************************************
 This property gets/sets the address to listen to (UNIX socket)
 ***************************************************************/
BEGIN_PROPERTY ( CSERVERSOCKET_Path )

      char *tmpstr=NULL;

      if (READ_PROPERTY)
      {
            GB.ReturnString(THIS->sPath);
            return;
      }
      if (THIS->iStatus>0)
      {
            GB.Error("Path value can not be changed when socket is active");
            return;
      }
      tmpstr=GB.ToZeroString ( PROP(GB_STRING) );
      if ( (strlen(tmpstr)<1) || (strlen(tmpstr)>UNIXPATHMAX) )
      {
            GB.Error ("Invalid path length");
            return;
      }
      GB.StoreString(PROP(GB_STRING), &THIS->sPath);
      
END_PROPERTY


/***************************************************************
 This property gets/sets the socket type (0 -> TCP, 1 -> UNIX)
 ***************************************************************/
BEGIN_PROPERTY ( CSERVERSOCKET_Type )

      if (READ_PROPERTY)
      {
            GB.ReturnInteger(THIS->iSockType);
            return;
      }
      if (THIS->iStatus>0)
      {
            GB.Error("Socket Type can not be changed when socket is active");
            return;
      }
      if ( (VPROP(GB_INTEGER)<0) || (VPROP(GB_INTEGER)>1) )
      {
            GB.Error("Invalid Socket Type Value");
            return;
      }
      THIS->iSockType=VPROP(GB_INTEGER);

END_PROPERTY
/***********************************************
 Gambas object "Constructor"
 ***********************************************/
BEGIN_METHOD(CSERVERSOCKET_new,GB_STRING sPath;GB_INTEGER iMaxConn;)

      int retval;
      char *buf=NULL;
      int nport=0;
      int iMax=0;

      THIS->iPort=0;
      THIS->iStatus=0;
      THIS->sPath=NULL;
      THIS->iPause=0;
      THIS->iMaxConn=0;
      THIS->iCurConn=0;
      THIS->iSockType=1;
      THIS->children=NULL;
      THIS->nchildren=0;

      if (MISSING(sPath)) return;
      if (!STRING(sPath)) return;

      if (!MISSING(iMaxConn)) iMax=VARG(iMaxConn);
      retval=IsHostPath(STRING(sPath),&buf,&nport);
      if (!retval)
      {
            GB.Error("Invalid Host / Path string");
            return;
      }
      if (retval==2)
      {
            THIS->iSockType=0;
            buf=GB.ToZeroString ( (GB_STRING*)STRING(sPath) );
            if ( (strlen(buf)<1) || (strlen(buf)>UNIXPATHMAX) )
            {
                  GB.Error ("Invalid path length");
                  return;
            }
            GB.StoreString((GB_STRING*)STRING(sPath),&THIS->sPath);
            return;
      }
      else
      {
            if (buf)
            {
                  GB.Free((void**)&buf);
                  GB.Error("Invalid Host String");
                  return;
            }
            if (nport<1)
            {
                  GB.Error("Invalid Port value");
                  return;
            }

            THIS->iSockType=1;
            THIS->iPort=nport;
      }

      switch(srvsock_listen(THIS,iMax))
      {
            case 1:
                  GB.Error("Socket is already listening");
                  return;
            case 7:
                  GB.Error ("You must define Path");
                  return;
            case 8:
                  GB.Error ("Error. You must define port");
                  return;
            case 13:
                  GB.Error ("Invalid maximun connections value");
                  return;
      }


END_METHOD



void close_server(CSERVERSOCKET *mythis)
{
      CSOCKET *chd;
      
      if (mythis->iStatus <= 0) return;
      
      GB.Watch (mythis->ServerSocket , GB_WATCH_NONE , (void *)CServerSocket_CallBack,0);
      close(mythis->ServerSocket);
      mythis->iStatus=0;
      
      if (!mythis->nchildren) return;
      
      while(mythis->nchildren)
      {
            chd=(CSOCKET*)mythis->children[0];
            if (chd->stream.desc) CSocket_stream_close(&chd->stream);
            CServerSocket_DeleteChild(mythis,(void*)chd);
      }

}
/*************************************************
 Gambas object "Destructor"
 *************************************************/
BEGIN_METHOD_VOID(CSERVERSOCKET_free)

      
      close_server(THIS);
      GB.FreeString(&THIS->sPath);

END_METHOD

/********************************************************
 Stops listening (TCP/UDP/UNIX), and closes all client sockets associated
 to this server
 *********************************************************/
BEGIN_METHOD_VOID(CSERVERSOCKET_Close)

      close_server(THIS);


END_METHOD
/********************************************************
 Do not accept more connections until Resume is used
 *********************************************************/
BEGIN_METHOD_VOID(CSERVERSOCKET_Pause)

      THIS->iPause=1;


END_METHOD
/********************************************************
 Accept connections again
 *********************************************************/
BEGIN_METHOD_VOID(CSERVERSOCKET_Resume)

      THIS->iPause=0;


END_METHOD

/*********************************************************
 Starts listening (TCP/UDP/UNIX)
 **********************************************************/
int srvsock_listen(CSERVERSOCKET* mythis,int mymax)
{
      int NoBlock=1;
      int retval;
      int auth = 1;

      if ( (!mythis->iPort) && (mythis->iSockType) ) return 8;

      if ( mythis->iStatus >0 ) return 1;

      if (mymax<0) return 13;

      if ( (!mythis->iSockType) && (!mythis->sPath) ) return 7;


      if (mythis->iSockType)
      {
            mythis->so_server.in.sin_family=AF_INET;
            mythis->so_server.in.sin_addr.s_addr=INADDR_ANY;
            mythis->so_server.in.sin_port=htons(mythis->iPort);
            mythis->ServerSocket=socket(PF_INET,SOCK_STREAM,0);
      }
      else
      {
            unlink(mythis->sPath);
            mythis->so_server.un.sun_family=AF_UNIX;
            strcpy(mythis->so_server.un.sun_path,mythis->sPath);
            mythis->ServerSocket=socket(AF_UNIX,SOCK_STREAM,0);
      }

      if ( mythis->ServerSocket==-1 )
      {
            mythis->iStatus=-2;
            GB.Ref(mythis);
            GB.Post(srvsock_post_error,(long)mythis);
            return 2;
      }
      // thanks to Benoit : this option allows non-root users to reuse the
      // port after closing it and reopening it in a short interval of time.
      // However, If you are porting this component to other O.S., be careful,
      // as this is not a standard unix option 
      setsockopt(mythis->ServerSocket, SOL_SOCKET, SO_REUSEADDR, &auth, sizeof(int));
      //
      if (mythis->iSockType)
            retval=bind(mythis->ServerSocket,(struct sockaddr*)&mythis->so_server.in, \
                  sizeof(struct sockaddr_in));
      else
            retval=bind(mythis->ServerSocket,(struct sockaddr*)&mythis->so_server.un, \
                  sizeof(struct sockaddr_un));
      if (retval==-1)
      {
            close(mythis->ServerSocket);
            mythis->iStatus=-10;
            GB.Ref(mythis);
            GB.Post(srvsock_post_error,(long)mythis);
            return 10;
      }

      ioctl(mythis->ServerSocket,FIONBIO,&NoBlock);

      if ( listen(mythis->ServerSocket,mymax) == -1 )
      {
            close(mythis->ServerSocket);
            mythis->iStatus=-14;
            GB.Ref(mythis);
            GB.Post(srvsock_post_error,(long)mythis);
            return 14;
      }
      mythis->iCurConn=0;
      mythis->iMaxConn=mymax;
      mythis->iStatus=1;
      
      //CServerSocket_AssignCallBack((long)mythis,mythis->ServerSocket);
      if (mythis->iSockType)
            GB.Watch (mythis->ServerSocket , GB_WATCH_READ , (void *)CServerSocket_CallBack,(long)mythis);
      else
            GB.Watch (mythis->ServerSocket , GB_WATCH_READ , (void *)CServerSocket_CallBackUnix,(long)mythis);
      return 0;
}

BEGIN_METHOD(CSERVERSOCKET_Listen,GB_INTEGER MaxConn;)

      int retval;
      int mymax=0;
      if (!MISSING(MaxConn)) mymax=VARG(MaxConn);
      retval=srvsock_listen(THIS,mymax);
      switch(retval)
      {
            case 1:
                  GB.Error("Socket is already listening");
                  return;
            case 7:
                  GB.Error ("You must define Path");
                  return;
            case 8:
                  GB.Error ("Error. You must define port");
                  return;
            case 13:
                  GB.Error ("Invalid maximun connections value");
                  return;
      }

END_METHOD

/******************************************************************
 To accept a pending connection and delegate it to a Socket object
*******************************************************************/
BEGIN_METHOD_VOID(CSERVERSOCKET_Accept)

 CSOCKET *cli_obj;
 struct sockaddr_in myhost;
 int mylen;

 if ( THIS->iStatus != 2){ GB.Error("No connection to accept");return; }

 GB.New((void**)&cli_obj,GB.FindClass("Socket"),"Socket",NULL);
 cli_obj->Socket=THIS->Client;
 cli_obj->iStatus=7;
 cli_obj->c_parent=(void*)THIS;
 cli_obj->OnClose=CServerSocket_OnClose;
 THIS->iCurConn++;
 GB.FreeString ( &cli_obj->sRemoteHostIP);
 GB.FreeString ( &cli_obj->sLocalHostIP);
 GB.FreeString ( &cli_obj->sPath);
 cli_obj->iLocalPort=0;
 cli_obj->iPort=0;
 cli_obj->conn_type=0;
 if (THIS->iSockType)
 {
      GB.NewString ( &cli_obj->sRemoteHostIP , inet_ntoa(THIS->so_client.in.sin_addr) ,0);
      GB.NewString ( &cli_obj->Host, inet_ntoa(THIS->so_client.in.sin_addr) ,0);
      mylen=sizeof(struct sockaddr);
      getsockname (cli_obj->Socket,(struct sockaddr*)&myhost,&mylen);
      GB.NewString ( &cli_obj->sLocalHostIP , inet_ntoa(myhost.sin_addr) ,0);
      cli_obj->iLocalPort=ntohs(myhost.sin_port);
      cli_obj->iPort=ntohs(THIS->so_client.in.sin_port);
      cli_obj->iUsePort=ntohs(THIS->so_client.in.sin_port);
 }
 else
 {
      cli_obj->conn_type=1;
      GB.NewString ( &cli_obj->sPath,THIS->sPath,0);
      GB.NewString ( &cli_obj->Path,THIS->sPath,0);
 }
 
 cli_obj->stream.desc=&SocketStream;
 cli_obj->stream._free[0]=(long)cli_obj;
 GB.Watch (cli_obj->Socket,GB_WATCH_WRITE,(void *)CSocket_CallBack,(long)cli_obj);

 CServerSocket_NewChild(THIS,cli_obj);          
 
 GB.Ref(cli_obj);
 GB.Post(CSocket_post_connected,(long)cli_obj);
 THIS->iStatus=3;
 GB.ReturnObject((void*)cli_obj);

END_METHOD

/****************************************************************
 Here we declare public structure of the ServerSocket class
*****************************************************************/
GB_DESC CServerSocketDesc[] =
{

  GB_DECLARE("ServerSocket", sizeof(CSERVERSOCKET)),

  GB_EVENT("Connection", NULL, "(RemoteHostIP)s", &Connection),
  GB_EVENT("Error", NULL,NULL, &CSERVERSOCKET_Error),
  
  GB_METHOD("_new", NULL, CSERVERSOCKET_new,"[(Path)s(MaxConn)i]"),
  GB_METHOD("_free", NULL, CSERVERSOCKET_free, NULL),
  GB_METHOD("Listen",NULL, CSERVERSOCKET_Listen, "[(MaxConn)i]"),
  GB_METHOD("Pause", NULL, CSERVERSOCKET_Pause, NULL),
  GB_METHOD("Resume", NULL, CSERVERSOCKET_Resume, NULL),
  GB_METHOD("Accept","Socket",CSERVERSOCKET_Accept,NULL),
  GB_METHOD("Close",NULL,CSERVERSOCKET_Close,NULL),

  GB_PROPERTY("Type","i<Net,Internet,Local>",CSERVERSOCKET_Type),
  GB_PROPERTY("Path","s",CSERVERSOCKET_Path),
  GB_PROPERTY("Port", "i", CSERVERSOCKET_Port),
  GB_PROPERTY_READ("Status","i",CSERVERSOCKET_Status),
  GB_STATIC_PROPERTY_READ("MaxPathLength","i",CSERVERSOCKET_MaxPathLength),
  
  GB_CONSTANT("_Properties", "s", "Type=0,Path,Port"),
  GB_CONSTANT("_DefaultEvent", "s", "Connection"),

  GB_END_DECLARE
};





Generated by  Doxygen 1.6.0   Back to index