Search code examples
c++socketstcpnetwork-programmingclient-server

C++ data sent by client doesn't reach server correctly


I have 2 programs: client and server. Both programs use the same functions to send and receive data. My server sometimes doesn't receive the data sent from client(both run on the same machine). My protocol functions:

void sendString(int fd, string str) //for sending strings
{
  unsigned long sz = str.size()+1;
  int res = write(fd, &sz, sizeof(sz));
  if (res == -1)
      throw ProtocolException("SENDSTRING SEND SIZE TROUBLE");

  res = write(fd, (void *)str.c_str(), sz);
  if (res == -1)
      throw ProtocolException("SENDSTRING SEND STRING TROUBLE");
}

void sendFlag(int fd, MessageType flag)
{
  write(fd, (void *)&flag, sizeof(MessageType));
}

string receiveString(int fd)
{
  unsigned long sz = 0;
  int readVal;
  if((readVal = read(fd, &sz, sizeof(sz))) <= 0){
    cout << "Read string size trouble " << readVal << endl;
    throw ProtocolException("Read string size trouble");
  }
  char *buf = new char[sz];
  buf[sz - 1] = '\0';
  if((readVal = read(fd, buf, sz)) <= 0){
    cout << "Read chars trouble "  << readVal << endl;
  }
  string res(buf);
  delete[] buf;
  return res;
}

MessageType receiveFlag(int fd)
{
  MessageType flag;
  read(fd, &flag, sizeof(flag));
  return flag;
}

This error occurs when client tries to login on server after failing the first time. Login functions:

Client

string receiveOKError(){
  switch(receiveFlag(sd)){
    case MessageType::OK: return receiveString(sd) + "\n";
    case MessageType::ERROR: return "ERROR: " + receiveString(sd) + "\n";
  }
}

string sendLogin(string usrname, string password)
{
  sendFlag(sd, MessageType::LOGIN);
  sendString(sd, usrname);
  sendString(sd, password);
  return receiveOKError();
}

Server(Note: MessageType::LOGIN gets matched by a receiveFlag before calling login function on server side)

void sendError(int fd, string message)
{
  cout << message;
  sendFlag(fd, MessageType::ERROR);
  sendString(fd, message);
}

void sendConfirm(int fd, string message) 
{
  cout << message;
  sendFlag(fd, MessageType::OK);
  sendString(fd, message);
}


void executeLogin(int fd,  ClientPoll & worker){ // server function
  string username = receiveString(fd);
  string password = receiveString(fd);
  if(dbManager.isRegistered(username)){
    ClientInfo *cl = dbManager.getClient(username, password);
    if(cl != nullptr){
        //unimportant code
        sendConfirm(fd, "Successful login");
    }
    else
        sendError(fd, "Wrong password");
  }
  else
    sendError(fd, "User doesn't exist.");

}

Server throws a ProtocolException while receiving first string on second login attempt. Server uses non-blocking sockets with poll, client uses blocking socket.


Solution

  • It seems that read() and write() functions used along with nonblocking sockets don't wait at all for data to be read/written even if you are sure that data will be ready at the moment of their call, so enclosing them in a while loop solves the issue:

    while(read(fd, &flag, sizeof(flag))==-1);
    

    It also would be useful to check for 0 value and close the connection, since these functions return 0 when the client is disconnected.

    int sz;
    while((sz = read(fd, &flag, sizeof(flag))) <= 0)
      if(sz == 0){
       //close fd or throw an exception
      }