Друзья, имеется проблема при работе с сокетами под 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, от чего клиент напрочь отваливается. Что делаю не так?