Link to home
Start Free TrialLog in
Avatar of logangsta
logangsta

asked on

C++ Class Help (WinSock)

I am currently trying to learn (and program with) the WinSock API.   Currently I have a socket class that has limited functionality (but functional none-the-less) and can create sockets, a server, and a client.  I am attempting to add the ability to grab the connecting client's IP Address or name.  I would assume I need to use the "gethostbyaddr( )" however am lost as to where to put it in my code and return it.  I think my problem is more a lack of advanced class building/use skills than it is understanding the WinSock API, however I could be wrong.

Here's some source I am working with.  the first here is my class definition in my header:
_________________________________________________________________________
using namespace std;

enum TypeSocket {BlockingSocket, NonBlockingSocket};

class Socket {
public:

  virtual ~Socket();
  Socket(const Socket&);
  Socket& operator=(Socket&);

  string rxData();
  //std::string ReceiveBytes();

  void   Close();
  void   txData (std::string);

protected:
  friend class SocketServer;
  friend class SocketSelect;

  Socket(SOCKET s);
  Socket();

  SOCKET s_;

private:
  static void Start();
  static void End();
};

class SocketClient : public Socket {
public:
  SocketClient(const std::string& host, int port);
};

class SocketServer : public Socket {
public:
  SocketServer(int port, int connections, TypeSocket type=BlockingSocket);

  Socket* Accept();
//***GetHostByAddr( ) here***
};

To specify the question a tad more, I would like to know the most efficient way to add methods (i.e. gethostbyaddr(), gethostbyname(), etc...) to this class, and return their values from it to my client.  Code snippets always welcome.  Like I said, more of a class question than sockets :)  

Here are the method descriptions:
___________________________________________________________________________

using namespace std;

void Socket::Start() {

    WSADATA info;
      int wsaret=WSAStartup(0x101,&info);
    if (wsaret!=0) {
      throw "WinSock1.1 WSA Startup Failure";//throws err to console
    }
}

void Socket::End() {
  WSACleanup();
}

//socket "s_" construction
Socket::Socket(SOCKET s) : s_(s) {
  Start();
};

void Socket::Close() {//doesn't destruct obj of "Socket"
  closesocket(s_);
}

//socket "s_" destruction
Socket::~Socket() {
  Close();
}

//main constructor/destructor of socket "s_"
Socket::Socket() : s_(0) {//defining socket "s_"
  Start();
  //UDP: use SOCK_DGRAM instead of SOCK_STREAM
  //Will allow for less OH, more err prone
  s_ = socket(AF_INET,SOCK_STREAM,0);

  if (s_ == INVALID_SOCKET) {
    throw "INVALID_SOCKET";//throws err to console
  }

}

string Socket::rxData() {
  std::string ret;
   while (1) {
     char r;

     switch(recv(s_, &r, 1, 0)) {
       case 0: //indicates broken conn
         return "";
       case -1:
          if (WSAGetLastError() == EAGAIN) {
                    return ret;
          } else {  //indicates broken conn
           return "";
         }
     }

     ret += r;
     if (r == '\n')  return ret;
   }
}

void Socket::txData(std::string s) {
  s += '\n';
  send(s_,s.c_str(),s.length(),0);
}

SocketServer::SocketServer(int port, int connections, TypeSocket type) {
  sockaddr_in local;
  std::string error;
  std::string hostIP;
 
  memset(&local, 0, sizeof(local));

  local.sin_family = PF_INET;            
  local.sin_port = htons(port);//htons - Host to Network Short Int

  s_ = socket(AF_INET, SOCK_STREAM, 0);
  if (s_ == INVALID_SOCKET) {
    throw "INVALID_SOCKET";
  }

  if(type==NonBlockingSocket) {
    u_long arg = 1;
    ioctlsocket(s_, FIONBIO, &arg);
  }

  /*      if(bind(server, (sockaddr*)&local, sizeof(local))!=0) //connect address with socket
      {
            return 0;
      }*/
  if (bind(s_, (sockaddr *)&local, sizeof(local)) == SOCKET_ERROR) {
    closesocket(s_);
    throw "INVALID_SOCKET";
  }
 
  //      if(listen(server, 10)!=0)
  //{
  //      return 0;
  //}
  if(listen(s_, connections)!=0){ //lstn for incming conns, conn is the bcklog
       error = strerror(WSAGetLastError());
       throw error;
  }                              
}

Socket* SocketServer::Accept() {
 
  SOCKET new_sock = accept(s_, 0, 0);
  if (new_sock == INVALID_SOCKET) {
          int rc = WSAGetLastError();
          if(rc==WSAEWOULDBLOCK) {
                  return 0; // non-blocking call, no request pending
          }
          else {
            throw "Invalid Socket";
      }
  }

   Socket* r = new Socket(new_sock);
  return r;
}

SocketClient::SocketClient(const std::string& host, int port) : Socket() {
  std::string error;

  hostent *he;
  if ((he = gethostbyname(host.c_str())) == 0) {//resolves from hostname
    error = strerror(WSAGetLastError());
    throw error;
  }

  sockaddr_in local;
  local.sin_family = AF_INET;//Address Family (internet)
  local.sin_port = htons(port);//htons - Host to Network Short Int
  local.sin_addr = *((in_addr *)he->h_addr);
  memset(&(local.sin_zero), 0, 8);

  if (::connect(s_, (sockaddr *) &local, sizeof(sockaddr))) {
    error = strerror(WSAGetLastError());//always use for error toss
    throw error;
  }
}

Any and all help is appreciated.  Thanks in advance, and feel free to make additional comments/advice on my current class.
Avatar of drichards
drichards

In your Accept method, you need to pass parameters in accept to receive the connecting client address:

  sockaddr_in addr;
  int addrLen = sizeof(sockaddr_in);
  SOCKET new_sock = accept(s_, (sockaddr*)(&addr), &addrLen);

You should also call WSACleanup once for each time you call WSAStartup - I didn't see where your code is calling it.
Avatar of logangsta

ASKER

drichards,

Thanks for the reply.  I think I follow your method to generate the connecting address in the Accept method.  What would be the appropriate means of passing this data out to another part of the program (seperate cpp) that is utilizing this class method?

Also, let's say I wanted to use the getHostByAddr( ) function, how would one incorporate this?  Make a seperate method??  

To answer your question, I do have a method to close an active socket (near the very start of my second source posting):

void Socket::End() {
  WSACleanup();
}

I was under the assumption this was sufficient to cleanup the WSAStartup garbage.  Let me know your thoughts.
ASKER CERTIFIED SOLUTION
Avatar of drichards
drichards

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
When I attempted to incorporate what you have, here is the error I am getting:

c:\vc projects\echoserver\socket.cpp(144) : error C2664: 'accept' : cannot convert parameter 2 from 'struct sockaddr_in *' to 'struct sockaddr *'
        Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

I attempted to make some changes to correct this error.  If I do get it to compile, the executable failes with an "abnormal program termination."  I'm no debugging expert, so I am somewhat lost with what is wrong as far as pointing to the member function.

>>The calling routine must pass a valid sockaddr_in pointer if it wants to receive the client address.

Could you show me/point me to an example that would be similar?

Thanks for all the help.  

Looking at this prototype:

string Socket::GetClientIP() {

sockaddr_in from;
  int fromlen = sizeof(from);
 
  return inet_ntoa(from.sin_addr);
}

Why would this give 204.204.204.204 as my return value??  No matter the connecting IP (127.0.0.1 or an actual remote) it still comes up with the same value.
As to the first problem, I left out a cast:

    SOCKET new_sock = accept(s_, (sockaddr*)pAddr, &addrLen);

Then you call the Accept method like:

    sockaddr_in clientAddr;
    pSock = server.Accept(&clientAddr);

where pSock is a SOCKET* and server is a SocketServer.

As to the 204.204.204.204, the GetClientIP method puts a sockaddr_in on the stack and then converts it (uninitialized) into a string.  In debug bulds, memory gets initialized to 0xcc, which is 204.  You need to initialize the address to a value befire converting it to a string.  GetClientIP should take the address as a parameter.  Then you can do:

    sockaddr_in clientAddr;
    pSock = server.Accept(&clientAddr);
    string clientIP = GetClientIP(clientAddr);
drichards,

Thanks for all the help.  You def. know your stuff.  Accept( ) is returning the IP as expected.  Now just have to look into catching the IP per socket.  I am somewhat working on a chat style peice of software, so would like the sending member of the chat group to broadcast its IP as well.  Not sure how to use what you've helped with to catch this, but it shouldn't be impossible, right?  Thanks again!
>> so would like the sending member of the chat group to broadcast its IP as well
I am not sure what you mean by that.  How are the clients finding each other?  The typical method is to have each client connect to a central server.  The current list of active clients is then available from the server.  Broadacst messages do not travel very far on a network and are hence not very useful for things like chat.
The clients do indeed connect to a central server.  From here, the server distributes the messages it receives to all connected clients.  For instance, let's say 192.168.1.1 is the server, and ips 192.168.1.2-4 connects.  If .2 sends a message, the server echos the received message to all ips but itself and the sending ip (in this case .2).  What I meant by broadcasting the ip is having all the clients that receive this echoed message, also receive the ip that sent the message initially.  This was why I attempted to create the seperate function to grab client IPs.  For example, when the Accept( ) is called, it will grab the IP and store it wherever I put it.  I am unsure of the best method to keep an array of connecting IPs, and then identify it with the appropriate socket.  Right now, I iterate through a list of connected sockets and send the echo message to all sockets != sendingSocket.  This works.  I did have a call to the getClientIP function in this iteration, but appearantly didn't pass the correct parameters over again.  I also tried (as stated) to keep an array of the connected IPs captured from Accept( ).  I was then to iterate through this list as i iterate through the list of sockets.  However, I had no method of matching one with the other.  I think (hopefully) I am overseeing the simple address capturing of WinSock functions.  :)
Without embedding the original sender IP in the relayed messages, there is no way in your current architecture for the receiving clients to retrieve the IP of the original sender.  This is a reasonably straightforward extension.  You just need to have the chat clients strip out the IP information before displaying the message.

You could also look at a multicast solution.

>>  I had no method of matching one with the other
You need to keep that mapping on the server, though I'm not sure what you'd do with the information.  Any time a message is received on a socket, the source IP/socket mapping information is implicitly available.  Mapping a user name or some more human-friendly identification to the socket would be more useful.