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
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.