2014-10-07 45 views
0

我有一台服务器使用双线程系统来管理100到200个并发连接。它使用TCP套接字,因为数据包传输保证很重要(这是一个通信系统,在这种系统中,未能通过远程API调用FUBAR客户端)。Windws C++间歇性套接字断开连接

我已经实现了一个自定义协议层,将传入的字节分成数据包并正确分发它们(库包含在下面)。我意识到使用MSG_PEEK的问题,但就我所知,它是唯一能够满足库实现需求的系统。我愿意接受建议,特别是如果这可能是问题的一部分。

基本上,问题是,尽管客户端每隔4次成功发送一次keepalive数据包,但由于缺少传入数据包的时间超过20秒,服务器将随机丢弃客户端的套接字。我可以验证服务器本身没有脱机,并且遇到问题的用户(包括我自己)的连接稳定。

用于发送该库/接收是在这里:

short ncsocket::send(wstring command, wstring data) { 
wstringstream ss; 
int datalen = ((int)command.length() * 2) + ((int)data.length() * 2) + 12; 
ss << zero_pad_int(datalen) << L"|" << command << L"|" << data;   
int tosend = datalen; 
short __rc = 0; 
do{ 
    int res = ::send(this->sock, (const char*)ss.str().c_str(), datalen, NULL); 
    if (res != SOCKET_ERROR) 
     tosend -= res; 
    else 
     return FALSE; 
    __rc++; 
    Sleep(10); 
} while (tosend != 0 && __rc < 10); 
if (tosend == 0) 
    return TRUE; 
return FALSE; 
} 

short ncsocket::recv(netcommand& nc) { 
vector<wchar_t> buffer(BUFFER_SIZE); 
int recvd = ::recv(this->sock, (char*)buffer.data(), BUFFER_SIZE, MSG_PEEK); 
if (recvd > 0) { 
    if (recvd > 8) { 
     wchar_t* lenstr = new wchar_t[4]; 
     memcpy(lenstr, buffer.data(), 8); 
     int fulllen = _wtoi(lenstr); 
     delete lenstr; 

     if (fulllen > 0) { 
      if (recvd >= fulllen) { 
       buffer.resize(fulllen/2); 
       recvd = ::recv(this->sock, (char*)buffer.data(), fulllen, NULL); 
       if (recvd >= fulllen) { 
        buffer.resize(buffer.size() + 2); 
        buffer.push_back((char)L'\0'); 
        vector<wstring> data = parsewstring(L"|", buffer.data(), 2); 
        if (data.size() == 3) { 
         nc.command = data[1]; 
         nc.payload = data[2]; 
         return TRUE; 
        } 
        else 
         return FALSE; 
       } 
       else 
        return FALSE; 
      } 
      else 
       return FALSE; 
     } 
     else { 
      ::recv(this->sock, (char*)buffer.data(), BUFFER_SIZE, NULL); 
      return FALSE; 
     } 
    } 
    else 
     return FALSE; 
} 
else 
    return FALSE; 

}

这是用于确定是否太多时间已经过去的代码:

if ((int)difftime(time(0), regusrs[i].last_recvd) > SERVER_TIMEOUT) { 
       regusrs[i].sock.end(); 
       regusrs[i].is_valid = FALSE; 
       send_to_all(L"removeuser", regusrs[i].server_user_id); 

       wstringstream log_entry; 
       log_entry << regusrs[i].firstname << L" " << regusrs[i].lastname << L" (suid:" << regusrs[i].server_user_id << L",p:" << regusrs[i].parent << L",pid:" << regusrs[i].parentid << L") was disconnected due to idle"; 
       write_to_log_file(server_log, log_entry.str()); 
      } 

的“regusrs [I ]“正在使用我用来描述套接字描述符和用户信息的向量的当前迭代成员。 'is_valid'检查可以告诉关联用户是否是实际用户 - 这样做是为了防止系统不得不释放向量的成员 - 它只是将其返回到可用槽的池中。没有线程访问/超出范围的问题。

无论如何,我开始怀疑是否服务器本身就是问题所在。我目前正在另一台服务器上进行测试,但我想知道是否有另一组眼睛可以阻止某些不合适的位置,或者提示我插入一个概念,并使用我不知道的扩展Keepalive。

在此先感谢!

+0

首先做循环是越野车;如果初始发送没有接收到所有数据,则从头开始再次发送,而不仅仅是第一次发送的数据。不确定这是否解释了问题。你如何发送Keepalive数据包?你是否检查过电线上的数据以确保它们实际上是及时传输的? – 2014-10-07 02:02:24

+0

我还没有检查线路上的线路,因为我无法可靠地重新创建线路。我已连接到服务器(因为我开始在不同的机器上托管它)连续几个小时。我认为服务器更改和切换MSG_PEEK的组合将有所帮助。 – 2014-10-07 04:21:06

回答

2

我想我看到你在做什么与MSG_PEEK,你等待,直到它看起来像你有足够的数据来读取一个完整的数据包。但是,我会怀疑这一点。 (很难仅通过查看源的这一小部分,而不是整个事情,以确定您的系统的动态行为。)

要避免使用MSG_PEEK,请遵循以下两个原则:

  1. 当你得到一个数据准备好的通知(我假设你使用的是select),然后读取全部来自recv()的等待数据。您可以使用多个recv()呼叫,因此您可以分段处理传入数据。

  2. 如果您只读取了部分数据包(长度或有效负载),然后将其保存在某处以便下次获得读取通知。将数据包和有效负载重新组合在一起,不要将它们留在套接字缓冲区中。

顺便说一句,使用new/memcpy/wtoi/delete悲惨是低效的。你根本不需要分配内存,你可以使用局部变量。然后你根本不需要memcpy,只是一个演员。

我假定你已经假设你的数据包的长度不能超过999个字节。

+0

我将切换到select()和一个内部缓冲区,但是,效率与否,我们使用字符串(UTF-16)字符串长度来与移动设备兼容。我不想和endian-ness一起争取像长度参数那样简单的事情。 你认为MSG_PEEK的不可靠性是为什么我有随机断线? – 2014-10-07 04:17:46

+0

我对'new'和'memcpy'的评论与endian-ness无关。我不知道'MSG_PEEK'是否被认为是不可靠的,只是我从来没有用过它,所以我不熟悉它的特点。 – 2014-10-07 06:20:59