LINUX.ORG.RU

Проблема с сокетами


0

1

Друзья, имеется проблема при работе с сокетами под Linux'ами.

Создаю сокет:

bool CTcpClient::createSocket()
{
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

if(sock == INVALID_SOCKET)
{
LOG_SOCKET_ERROR("create socket failed");
return false;
}

int opt;
int optlen = sizeof(opt);
int iRet;

opt = 64*1024;
iRet = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&opt, optlen);
if (iRet != 0)
{
LOG_SOCKET_ERROR("setsockopt SO_SNDBUF failed");
return false;
}

iRet = setsockopt( sock, SOL_SOCKET, SO_RCVBUF, (char*)&opt, optlen );
if (iRet != 0)
{
LOG_SOCKET_ERROR("setsockopt SO_RCVBUF failed");
return false;
}

setSock(sock); /// возвращаю созданный сокет в класс CTcpClient

return true;
}

Задаём опции и подключаемся к удалённой машине по локальной сети:

void CConnection::connect_to_service()
{
COptionsSP options_w; /// класс для чтения конфигурационного файла, в котором задаются номера портов и т. д.
TMultiConfigWraper multi_options;
if (INodeSP parent = m_wpParent.lock())
options_w = multi_options->getOptions(parent->GetCfgFile());
else
options_w = multi_options->getOptions("");

m_conn_state = connecting;
/*
enum EConnState { connecting, worked, aborted };
EConnState m_conn_state;
*/
fd_set set;
struct timeval timeout;
int numsocks = 0;

timeout.tv_sec = 0;
timeout.tv_usec = 100000;

// к кому подключаемся
unsigned short usPort = options_w->getListenPort();

SOCKADDR_IN ServAddr;
ServAddr.sin_family = AF_INET;
ServAddr.sin_port = htons(usPort);
ServAddr.sin_addr.s_addr = GetAddress();

for (unsigned long i = 0; i < m_conn_try; ++i)
{
if (connect(m_apClient->getSock(), (SOCKADDR *)&ServAddr, sizeof(SOCKADDR_IN)) != SOCKET_ERROR) /// boost::shared_ptr<CTcpClient> m_apClient;
{
FD_ZERO(&set);
FD_SET(m_apClient->getSock(), &set);

numsocks = select(m_apClient->getSock(), 0, &set, 0, &timeout );

if (numsocks == SOCKET_ERROR)
{
LOG_SOCKET_ERROR("select failed");
work_tools::sleep(100);
/*
inline void sleep(int ms)
{
boost::xtime delay;
to_time(ms, delay);
boost::thread().sleep(delay);
}
*/
continue;
}

SOCKADDR_IN local_addr;
int len = sizeof(SOCKADDR_IN);

if (getsockname( m_apClient->getSock(), (SOCKADDR *)&local_addr, (socklen_t*)&len) == SOCKET_ERROR)
{
LOG_SOCKET_ERROR("getsockname failed");
}

///делаем неблокирующим
unsigned long ulNonBlockingMode = 1;
if (ioctlsocket(m_apClient->getSock(), FIONBIO, &ulNonBlockingMode) < 0)
{
LOG_SOCKET_ERROR("ioctlsocket failed");
}

m_apClient->setPort(ntohs(local_addr.sin_port)); // можно отправлять
m_connect_event.set(connected);
m_conn_state = worked;

return;
}
else
{
LOG_SOCKET_ERROR("connect failed");
}

work_tools::sleep(100);
}
m_conn_state = aborted;
}

Ну, и, собственно, функции отсылки:

bool CConnection::send_packet_impl(const char *pHead, unsigned long ulHeadLen, const char *pData, unsigned long ulLen)
{
if( !m_apClient )
return false;

#ifdef WIN32
DWORD sended = 0;
WSABUF wbuf[2];

wbuf[0].buf = const_cast<char*>(pHead);
wbuf[0].len = ulHeadLen;

wbuf[1].buf = const_cast<char*>(pData);
wbuf[1].len = ulLen;

while (true)
{
if(WSASend(m_apClient->getSock(), wbuf, 2, &sended, 0, NULL, NULL  ) < 0)
{
LOG_SOCKET_ERROR("send failed");
if(WSAGetLastError() == WSAECONNRESET)
{
m_conn_state = connecting;
return false;
}

work_tools::sleep(5);
continue;
}
if(sended < ulHeadLen + ulLen)
LOG( "data loss");

return true;
}

#else
send_to_service( pHead, ulHeadLen );
send_to_service( pData, ulLen );
#endif
}

#ifndef WIN32
bool CConnection::send_to_service(const char *pData, unsigned long ulLen)
{
if( !m_apClient )
return false;

unsigned long total = 0;
int n;

while(total < ulLen)
{
struct timeval tv;
int retval;
tv.tv_sec = 1;
tv.tv_usec = 0;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(m_apClient->getSock(), &rfds);

retval = select(m_apClient->getSock() + 1, NULL, &rfds, NULL, &tv);

if (retval > 0) /// есть дескрипторы, готовые принять данные
{
if (FD_ISSET(m_apClient->getSock(), &rfds)
{
n = send(m_apClient->getSock(), pData + total, ulLen - total, MSG_CONFIRM);
}
if (n == SOCKET_ERROR)
{
if (total > 0)
LOG_ERROR("send failed on pos" << total);

LOG_SOCKET_ERROR("send failed");

if (errno == EWOULDBLOCK)
{
work_tools::sleep(100); /// почему-то всё время заходит сюда
}
if(errno == ECONNRESET)
{
m_conn_state = connecting;
return false;
}
}
else
{
total += n;
}
}
if (retval == 0) /// вернули управление по таймауту, дескрипторов нет
{
work_tools::sleep(100);
}
if (retval < 0)
{
if ( errno == EWOULDBLOCK )
{
work_tools::sleep(100);
}
if(errno == ECONNRESET)
{
m_conn_state = connecting;
return false;
}
}
}
}

А теперь суть проблемы. Если код под Windows работает без проблем, то реализация для Linux страдает. Хотя функция select в send_to_service возвращает управление со значением больше 0, при попытке сразу же вызвать send валится исключение EAGAIN. Если данных послать очень много (например, 500 Мбайт для локального хоста и всего 100 Кбайт для локальной сети), то в течение минуты сокет дохнет, а ядро шлёт connection timed out, от чего клиент напрочь отваливается. Что делаю не так?

Что делаю не так?

Не используешь кучу готовых библиотек, в которых работа с сетью упрощена дальше некуда.

anonymous ()

После вызова LOG_ERROR/LOG_SOCKET_ERROR в errno у тебя будет не ошибка, которую вернула send(), а неопределенный мусор.

Deleted ()
Ответ на: комментарий от Deleted

Если MSG_CONFIRM в send() убрать, то эффект будет тот же. Переводить на другие библиотеки сейчас времени нет. Нужно быстро поправить тот код, который наработан вообще другим разработчиком.

Отступы, простите, форум съел.

Vitaliy69 ()
Ответ на: комментарий от Rzhepish

браток, я что-то ни разу не видел, чтоб ты тут хоть что-то полезное написал. Ты вообще с какого сраёна?

anonymous ()
Ответ на: комментарий от Jetty

тем более что есть выбор: libcurl libsdl sfml и т.д.

anonymous ()

а ничего, что вы сами же выставили размер буфера отправки и приёмки в 64К?

EAGAIN вам говорит, что устройство (сокет) занят и затолкать ещё данные в него нужно чуть-чуть погодя (когда хоть чуть-чуть свободнее буфер станет).

почему вы отправляете все данные одной пачкой? где цикл отправки по чуть-чуть? вы ребёнку сразу всю тарелку в рот заливаете? или всё же по чайной ложечке в рот кладёте?

думайте, где вы сами же себя ограничили и как обходить проблему

gunja ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.