Solved

C++ Class Help (WinSock)

Posted on 2004-10-29
1,043 Views
Last Modified: 2013-12-14
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.
0
Question by:logangsta
    10 Comments
     
    LVL 19

    Expert Comment

    by: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.
    0
     

    Author Comment

    by:logangsta
    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.
    0
     
    LVL 19

    Accepted Solution

    by:
    Your End() method is OK, but as yo're calling Start in the constructor, I would have expected End in the destructor.

    >> Also, let's say I wanted to use the getHostByAddr( ) function, how would one incorporate this?  Make a seperate method??
    Depends what you're doing with getHostByAddr.  Making a separate method will let you get at the functionality directly if that's what you want.  It's probably a static method of the socket class, however, since it's not directly related to a socket.

    As for passing the accepted address data out of the class, you can make it an optional output parameter of the Accept method.   If you are trying to hide all the Winsock structures, you will need to create some other type of data entity for te address information.  You coule turn it into a string, for example.  Depends on what you're going to do with it.  Here's one way to return the bare sockaddr.  The calling routine must pass a valid sockaddr_in pointer if it wants to receive the client address.
    --------------------------------------
    class SocketServer : public Socket {
    public:
      SocketServer(int port, int connections, TypeSocket type=BlockingSocket);

      Socket* Accept(sockaddr_in *pAddr = 0);
    //***GetHostByAddr( ) here***
    };

    Socket* SocketServer::Accept(sockaddr_in *pAddr) {
      int addrLen = 0;
      if ( pAddr != NULL ) addrLen = sizeof(sockaddr_in);
      SOCKET new_sock = accept(s_, pAddr, &addrLen);
      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;
    }
    0
     

    Author Comment

    by:logangsta
    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.  

    0
     

    Author Comment

    by:logangsta
    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.
    0
     
    LVL 19

    Expert Comment

    by:drichards
    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);
    0
     

    Author Comment

    by:logangsta
    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!
    0
     
    LVL 19

    Expert Comment

    by:drichards
    >> 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.
    0
     

    Author Comment

    by:logangsta
    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.  :)
    0
     
    LVL 19

    Expert Comment

    by:drichards
    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.
    0

    Write Comment

    Please enter a first name

    Please enter a last name

    We will never share this with anyone.

    Featured Post

    How to run any project with ease

    Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
    - Combine task lists, docs, spreadsheets, and chat in one
    - View and edit from mobile/offline
    - Cut down on emails

    When writing generic code, using template meta-programming techniques, it is sometimes useful to know if a type is convertible to another type. A good example of when this might be is if you are writing diagnostic instrumentation for code to generat…
    Programmer's Notepad is, one of the best free text editing tools available, simply because the developers appear to have second-guessed every weird problem or issue a programmer is likely to run into. One of these problems is selecting and deleti…
    The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…
    The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.

    856 members asked questions and received personalized solutions in the past 7 days.

    Join the community of 500,000 technology professionals and ask your questions.

    Join & Ask a Question

    Need Help in Real-Time?

    Connect with top rated Experts

    13 Experts available now in Live!

    Get 1:1 Help Now