У меня есть сервер, который использует двухпоточную систему для управления от 100 до 200 одновременных соединений. Он использует TCP-сокеты, так как гарантия доставки пакетов важна (это система связи, где пропущенные удаленные вызовы API могут FUBAR-клиент).
Я реализовал специальный уровень протокола для разделения входящих байтов на пакеты и их правильной отправки (библиотека включена ниже). Я понимаю проблемы использования MSG_PEEK, но, насколько мне известно, это единственная система, которая будет отвечать потребностям реализации библиотеки. Я открыт для предложений, особенно если это может быть частью проблемы.
По сути, проблема в том, что случайным образом сервер отбрасывает сокет клиента из-за отсутствия входящих пакетов в течение более 20 секунд, несмотря на то, что клиент успешно отправляет пакет keepalive каждые 4. Я могу убедиться, что сам сервер этого не делал выйдите из сети, и соединение пользователей (включая меня), испытывающих проблему, будет стабильным.
Библиотека для отправки / получения находится здесь:
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 предназначена для того, чтобы определить, является ли связанный пользователь действительным пользователем — это сделано для того, чтобы система не имела необходимости освобождать член вектора — она просто возвращает его в пул доступных слотов. Таким образом, нет проблем с доступом к потоку / вне диапазона.
Во всяком случае, я начал задаваться вопросом, был ли это сам сервер был проблемой. В настоящее время я тестирую на другом сервере, но я хотел посмотреть, сможет ли другой набор глаз остановить что-то неуместное или напомнить мне о концепции с сокетами и расширенной поддержкой активности, о которой я не знаю.
Заранее спасибо!
Я думаю, что я вижу, что вы делаете с MSG_PEEK
где вы ждете, пока у вас не появится достаточно данных для чтения полного пакета. Однако я бы с подозрением отнесся к этому. (Трудно определить динамическое поведение вашей системы, просто взглянув на эту небольшую часть источника, а не на всю.)
Чтобы избежать использования MSG_PEEK
следуйте этим двум принципам:
Когда вы получаете уведомление о том, что данные готовы (я предполагаю, что вы используете select
), затем прочитайте все данные ожидания от recv()
, Вы можете использовать более одного recv()
вызов, так что вы можете обрабатывать входящие данные по частям.
Если вы читаете только частичный пакет (длина или полезная нагрузка), сохраните его где-нибудь, чтобы в следующий раз вы получили уведомление о прочтении. Соберите вместе пакеты и полезные данные, не оставляйте их в буфере сокетов.
Кроме того, использование new
/memcpy
/wtoi
/delete
ужасно неэффективно. Вам вообще не нужно выделять память, вы можете использовать локальную переменную. И тогда вам даже не нужно memcpy
вообще, просто актерский состав.
Я предполагаю, что вы уже предполагаете, что ваши пакеты могут иметь длину не более 999 байт.