/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** 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 3 of the License, 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, see . */ /****************************************************************************\ TCP Neal Kettler neal@westwood.com ****************************************************************************** A general purpose TCP class that can be used in either CLIENT or SERVER mode. Note that this uses non-blocking sockets. The FD_* macros: FD_CLR(int fd, fd_set *set); // clear a single FD FD_ISSET(int fd, fd_set *set); // check whether a single FD is set FD_SET(int fd, fd_set *set); // set a single FD FD_ZERO(fd_set * set); // clear the entire set NOTE: The fd_set returned by 'Wait' is static, don't call delete on it! If you are writing a CLIENT: The last argument to many functions is an integer whichFD, this is used only by SERVER mode, so you can omit this argument. Sample Code: fd_set *fdSet; uint8 *buff=new uint8[1024]; int retval; TCP tcp(CLIENT); tcp.Bind((uint32)0,(uint16)0); // let system pick local IP and a Port for you tcp.Connect("tango",13); // can connect by name or "10.1.1.10" // or the integerßin host byte order fdSet=tcp.Wait(10,0); // wait for UP TO 10 sec and 0 microseconds if (FD_ISSET(tcp.GetFD(),fdSet)) // Is there something to read? { retval=tcp.Read(buff,1024); // Read something // Retval will contain the number of // bytes read, or... // 0 = remote end closed connection // -1 = nothing to read fprintf(stderr,"%s",buff); } else fprintf(stderr,"Nothing was read!\n"); If you are writing a SERVER: The structure called 'clientList' contains all the File Descriptors that have connected to the server. Make sure you look at the FD_* functions so you can use this sort of structure. When you are writing a server, you need to specify the 'whichFD' arguments to all the functions. Sample Code: fd_set *fdSet; uint8 *buff=new uint8[1024]; int retval; TCP tcp(SERVER); tcp.Bind((uint32)0,(uint16)2121); // You need to bind to a well defined // port number or nobody will know where // to connect to. while (1) { fdSet=tcp.Wait(-1,-1); // Wait until there is something on the socket if (FD_ISSET(tcp.GetFD(),fdSet)) // somebody must want a connection { retval=tcp.GetConnection(); // Get a connection if somebody's trying if (retval!=-1) { tcp.Write("Hello World!\n",strlen("Hello World!\n"),retval); tcp.Close(retval); } } } \****************************************************************************/ #include "tcp.h" #include #ifndef _WINDOWS #include #define closesocket close #endif // newMode should be either CLIENT or SERVER TCP::TCP(int new_mode) { mode=CLIENT; maxFD=0; fd = -1; clientCount=0; if ((new_mode==CLIENT)||(new_mode==SERVER)) mode=new_mode; FD_ZERO(&clientList); connectionState=CLOSED; inputDelay=5; outputDelay=5; } // Create a TCP object on a pre-existing socket TCP::TCP(int new_mode,sint16 socket) { sint32 retval; mode=CLIENT; maxFD= socket; fd = socket; clientCount=0; if ((new_mode==CLIENT)||(new_mode==SERVER)) mode=new_mode; FD_ZERO(&clientList); inputDelay=5; outputDelay=5; retval=SetBlocking(FALSE,socket); // set to NB mode //DBGMSG("Setblocking: "<0)&&(c!=0)) { string[i]=c; if (c=='\n') return(string); i++; } else if ((retval==0)&&(i==0)) { DBGMSG("Remote endpoint closed (1)"); return(NULL); } else if (retval==0) return(string); } return(string); } // only specify whichFD if this is a server sint32 TCP::Read(uint8 *msg,uint32 len,sint32 whichFD) { sint32 retval; //DBGMSG("In read, mode: "<0) bytes_read+=retval; // otherwise some error } return(bytes_read); } // only specify whichFD if this is a server // Peek at data in system buffer sint32 TCP::Peek(uint8 *msg,uint32 len,sint32 whichFD) { sint32 retval; if (mode==CLIENT) { retval=recv(fd,(char *)msg,len,MSG_PEEK); if (retval==0) Close(); return(retval); } else if (mode==SERVER) { if ((whichFD<=maxFD) && (FD_ISSET(whichFD,&clientList))) { retval=recv(whichFD,(char *)msg,len,MSG_PEEK); if (retval==0) Close(whichFD); return(retval); } else return(0); // closed } return(-1); } // only specify whichFD if this is a server // (this is used for non-8 bit clean pipes, you probably don't // want to use it!) sint32 TCP::EncapsulatedRead(uint8 *msg,uint32 len,sint32 whichFD) { sint32 retval,bytesRead=0; uint32 i; char data; if (mode==CLIENT) whichFD=fd; else if (mode==SERVER) { if ((whichFD>maxFD) || (!FD_ISSET(whichFD,&clientList))) return(0); } else return(-1); for (i=0; i=0; i--) if (FD_ISSET(i,&clientList)) { maxFD=i; break; } } FD_CLR((uint32)whichFD,&clientList); clientCount--; return(closesocket(whichFD)); } } return(-1); } // if 'sec' AND 'usec' are -1 then this will sleep until // there is socket activity int TCP::Wait(sint32 sec,sint32 usec,fd_set &returnSet,sint32 whichFD) { fd_set inputSet; FD_ZERO(&inputSet); if (mode==SERVER) { if (whichFD==0) { inputSet=clientList; if (fd > 0) FD_SET(fd,&inputSet); } else if (whichFD > 0) FD_SET(whichFD,&inputSet); } else if (mode==CLIENT) { if (whichFD==0) whichFD=fd; if (whichFD > 0) FD_SET(whichFD,&inputSet); } return(Wait(sec,usec,inputSet,returnSet)); } int TCP::Wait(sint32 sec,sint32 usec,fd_set &givenSet,fd_set &returnSet) { Wtime timeout; Wtime timenow; Wtime timethen; fd_set backupSet; int retval=0,done,givenMax; bit8 noTimeout=FALSE; timeval tv; returnSet=givenSet; backupSet=returnSet; if ((sec==-1)&&(usec==-1)) noTimeout=TRUE; timeout.SetSec(sec); timeout.SetUsec(usec); timethen+=timeout; givenMax=maxFD; for (uint32 i=0; i<(sizeof(fd_set)*8); i++) // i=maxFD+1 { if (FD_ISSET(i,&givenSet)) givenMax=i; } done=0; while( ! done) { if (noTimeout) retval=select(givenMax+1,&returnSet,0,0,NULL); else { timeout.GetTimevalMT(tv); retval=select(givenMax+1,&returnSet,0,0,&tv); } if (retval>=0) done=1; else if ((retval==-1)&&(errno==EINTR)) // in case of signal { if (noTimeout==FALSE) { timenow.Update(); timeout=timethen-timenow; } if ((noTimeout==FALSE)&&(timenow.GetSec()==0)&&(timenow.GetUsec()==0)) done=1; else returnSet=backupSet; } else // maybe out of memory? { done=1; } } return(retval); } void TCP::WaitWrite(sint32 whichFD) { fd_set backupSet; int retval=0,done; fd_set outputSet; if (whichFD==0) whichFD=fd; if (whichFD==-1) return; FD_ZERO(&outputSet); FD_SET(whichFD,&outputSet); backupSet=outputSet; done=0; while( ! done) { retval=select(maxFD+1,0,&outputSet,0,NULL); if (retval>=0) done=1; else if ((retval==-1)&&(errno==EINTR)) // in case of signal outputSet=backupSet; else // maybe out of memory? done=1; } } // Can a FD be written to? bit8 TCP::CanWrite(sint32 whichFD) { int retval=0; fd_set outputSet; Wtime timeout; timeval tv; timeout.SetSec(0); timeout.SetUsec(0); if (whichFD==0) whichFD=fd; FD_ZERO(&outputSet); FD_SET(whichFD,&outputSet); timeout.GetTimevalMT(tv); retval=select(whichFD+1,0,&outputSet,0,&tv); if (retval>0) return(TRUE); else return(FALSE); } bit8 TCP::Bind(char *Host,uint16 port,bit8 reuseAddr) { char hostName[100]; struct hostent *hostStruct; struct in_addr *hostNode; if (isdigit(Host[0])) return ( Bind( ntohl(inet_addr(Host)), port,reuseAddr) ); strcpy(hostName, Host); hostStruct = gethostbyname(Host); if (hostStruct == NULL) return (0); hostNode = (struct in_addr *) hostStruct->h_addr; return ( Bind(ntohl(hostNode->s_addr),port,reuseAddr) ); } // You must call bind, implicit binding is for sissies // Well... you can get implicit binding if you pass 0 for either arg bit8 TCP::Bind(uint32 IP,uint16 Port,bit8 reuseAddr) { int retval; int status; IP=htonl(IP); Port=htons(Port); addr.sin_family=AF_INET; addr.sin_port=Port; addr.sin_addr.s_addr=IP; fd=socket(AF_INET,SOCK_STREAM,DEFAULT_PROTOCOL); if (fd==-1) return(FALSE); retval=SetBlocking(FALSE,fd); if (retval==-1) ERRMSG("Couldn't set nonblocking mode!"); if (reuseAddr==TRUE) { uint32 opval; #ifdef SO_REUSEPORT /****************** this may make the socket get garbage data?? opval=1; retval=setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&opval,sizeof(opval)); if (retval!=0) fprintf(stderr,"Could not set socket to SO_REUSEPORT\n"); **********************/ #endif #ifdef SO_REUSEADDR opval=1; retval=setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&opval,sizeof(opval)); if (retval!=0) fprintf(stderr,"Could not set socket to SO_REUSEADDR\n"); #endif } retval=bind(fd,(struct sockaddr *)&addr,sizeof(addr)); #ifdef _WINDOWS if (retval==SOCKET_ERROR) retval=-1; #endif if (retval==-1) { status=GetStatus(); DBGMSG("Bind failure (" << status << ") IP "<< IP <<" PORT "<< ntohs(Port)); return(FALSE); } myIP=IP; myPort=Port; maxFD=fd; if (mode==SERVER) listen(fd,64); //Solaris needs lots of listen slots for some reason return(TRUE); } // This is only for clients bit8 TCP::Connect(char *Host,uint16 port) { char hostName[100]; struct hostent *hostStruct; struct in_addr *hostNode; if (isdigit(Host[0])) return ( Connect( ntohl(inet_addr(Host)), port) ); strcpy(hostName, Host); hostStruct = gethostbyname(Host); if (hostStruct == NULL) {ERRMSG("Can't resolve host");return (0);} hostNode = (struct in_addr *) hostStruct->h_addr; return ( Connect(ntohl(hostNode->s_addr),port) ); } bit8 TCP::Connect(uint32 IP,uint16 Port) { int tries,result; struct timeval sleep_time; struct sockaddr_in serverAddr; int status; IP=htonl(IP); Port=htons(Port); serverAddr.sin_family=AF_INET; serverAddr.sin_port=Port; serverAddr.sin_addr.s_addr=IP; if (mode!=CLIENT) {ERRMSG("Can't connect in server mode");return(FALSE);} tries=0; result=-1; // try 10 connects with a greater and greater sleep time after each one // this can go on for upto 5.4 seconds while ((tries < 10) && (result == -1)) { ClearStatus(); result = connect(fd,(struct sockaddr *)&serverAddr, sizeof(serverAddr)); status=GetStatus(); #ifdef _WINDOWS if (result==SOCKET_ERROR) result=-1; #endif if ((status == ISCONN) && (result == -1)) { result = 0; } if (result == -1) { if ((status!=INPROGRESS)&&(status!=ALREADY)&&(status!=AGAIN)&& (status!=WOULDBLOCK)) { Close(); Bind(myIP,myPort); } tries++; sleep_time.tv_sec = 0; sleep_time.tv_usec = (100000*(tries+1)); #ifdef WIN32 Sleep((sleep_time.tv_usec)/1000); #else select(0, 0, 0, 0, &sleep_time); #endif } } if (result == -1) { return(FALSE); } connectionState=CONNECTED; return (TRUE); } // Asynchronous Connection bit8 TCP::ConnectAsync(char *Host,uint16 port) { char hostName[100]; struct hostent *hostStruct; struct in_addr *hostNode; if (isdigit(Host[0])) return ( ConnectAsync( ntohl(inet_addr(Host)), port) ); strcpy(hostName, Host); hostStruct = gethostbyname(Host); if (hostStruct == NULL) return (0); hostNode = (struct in_addr *) hostStruct->h_addr; return ( ConnectAsync(ntohl(hostNode->s_addr),port) ); } // Asynchronous Connection bit8 TCP::ConnectAsync(uint32 IP,uint16 Port) { int result; struct sockaddr_in serverAddr; int status,connectErrno; int retval; IP=htonl(IP); Port=htons(Port); serverAddr.sin_family=AF_INET; serverAddr.sin_port=Port; serverAddr.sin_addr.s_addr=IP; if (mode!=CLIENT) return(FALSE); result=-1; if (connectionState==CONNECTING) { if (IsConnected(fd)) { DBGMSG("CONNECTION COMPLETE at point 1"); connectionState=CONNECTED; return(TRUE); } else return(TRUE); // Still trying } ClearStatus(); result = connect(fd,(struct sockaddr *)&serverAddr, sizeof(serverAddr)); connectErrno=errno; status=GetStatus(); #ifdef _WINDOWS if (result==SOCKET_ERROR) { DBGMSG("Socket error 1 " << status); result=-1; } #endif // If we have a bogus FD, try again after closing and re-binding if ((result==-1)&&((status==BADF)||(status==NOTSOCK)||(status==INVAL))) { Close(); retval=Bind(myIP,myPort); DBGMSG("BIND = "<maxFD) maxFD=clientFD; FD_SET(clientFD,&clientList); clientCount++; } return(clientFD); } sint32 TCP::GetConnection(struct sockaddr *clientAddr) { if (mode!=SERVER) return(-1); sint32 clientFD; int addrlen=sizeof(struct sockaddr); clientFD=accept(fd,(struct sockaddr *)clientAddr,&addrlen); if (clientFD!=-1) { if (clientFD>maxFD) maxFD=clientFD; FD_SET(clientFD,&clientList); clientCount++; } return(clientFD); }