C非阻塞发送不起作用

时间:2014-07-03 20:12:56

标签: c++ c sockets nonblocking

我正在尝试将大量数据发送到应接受数据并解析数据的服务器。据我所知,当你在一次调用中以阻塞模式send()数据时,它会将数据拆分为块,然后将块发送到目标。但是我需要在数据的开头用一个小标识符标记每个块(假设我在每个块中放置一个标头),所以我决定使用非阻塞发送。我想,当我进行非阻塞发送时,它会发送最大缓冲区允许然后返回,让分块为我工作,但似乎没有发生。

我的代码是:

    struct sockaddr_in target;

    SOCKET connection = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
    target.sin_family = AF_INET;
    target.sin_addr.s_addr = inet_addr(host);
    target.sin_port = htons(port);

     ULONG NonBlock;
     NonBlock = 1;
     if (ioctlsocket(connection, FIONBIO, &NonBlock) == SOCKET_ERROR)
     {

         return WSAGetLastError();

     }

      fd_set write_fds;

        FD_ZERO(&write_fds);            
        FD_SET(connection, &write_fds);
        struct timeval tv;
        tv.tv_sec=1;
        tv.tv_usec=0;
    int result = connect(connection,(SOCKADDR*)&target,sizeof(target));
    if(result==SOCKET_ERROR)
    {
        while(true)
        {
            result= select(connection+1,NULL,&write_fds,NULL,&tv);
            printf("connect: result=%d\r\n",result);
            if(result== -1)
            {
                return WSAGetLastError();
            }
            else break;
        }

    }

    //later on

fd_set write_set;
int bytes_sent= 0;
int total_sent = 0;
int length = 0;
char *tmp = malloc(sizeof(header)+data_size); //data_size is the size of the large buffer
memcpy(tmp,packet,sizeof(header));
memcpy(tmp+sizeof(header),data,data_size);


int result;

FD_ZERO(&write_set);
FD_SET(connection,&write_set);

struct timeval time_out;

time_out.tv_sec=0;
time_out.tv_usec=1500;

while(total_sent < data_size)
{
    length= (data_size+sizeof(my_header))-total_sent;

    result = select(connection+1,NULL,&write_set,NULL,&time_out);

    if(result== SOCKET_ERROR) return -1;
    if(result!=0 && FD_ISSET(connection, &write_set))
    {
        bytes_sent = send(connection,tmp,length,0);
    }

    if(bytes_sent == SOCKET_ERROR)
    {
        return SOCKET_ERROR;
    }
    if(bytes_sent > 0)
    {
        //here i need to append a header to the new chunk
    }
    else break;


}

所以基本上我的问题是:为什么发送非阻塞套接字,仍然阻塞并且在发送第一个块后没有返回,并且就像常规阻塞发送一样?我想要实现的是send()发送一个系统允许的长度数据,所以我把整个数据的长度,假设非阻塞发送将在发送第一个块后返回,因为缓冲区很大,要作为一个块发送。

更新一些可运行的代码:

#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <types.h>

typedef struct hdr{
 uint8_t super_id;
}my_header,*pmy_header;

SOCKET connection;
int start_winsock()
{
    WSADATA check;

    int result = WSAStartup(MAKEWORD(2,2),&check);  
    return result;
}
int create_connection(char* host,int port)
{
     struct sockaddr_in target;

    connection = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    target.sin_family = AF_INET;
    target.sin_addr.s_addr = inet_addr(host);
    target.sin_port = htons(port);
    int result =    UnblockSocket();
    if(result!=0) return WSAGetLastError();

        fd_set write_fds;
        FD_ZERO(&write_fds);            
        FD_SET(connection, &write_fds);
         struct timeval tv;

        tv.tv_sec=1;
        tv.tv_usec=0;

        result = connect(connection,(SOCKADDR*)&target,sizeof(target));
        if(result==SOCKET_ERROR)
        {
          while(true)
          {
            result= select(connection+1,NULL,&write_fds,NULL,&tv);
            if(result== -1)
            {
                 return WSAGetLastError();
            }
             else break;
        }

    }

    return 0;

}


 int UnblockSocket()
 {
   ULONG NonBlock;
   NonBlock = 1;
   if (ioctlsocket(connection, FIONBIO, &NonBlock) == SOCKET_ERROR)
   {

      return WSAGetLastError();

   }
    return 0;
 }



 int SendMyData(pmy_header header,char * data,int data_size)
 {

    fd_set write_set;
    int bytes_sent= 0;
    int total_sent = 0;
    int length = 0;
    char *tmp = malloc(sizeof(my_header)+data_size);
    memcpy(tmp,packet,sizeof(my_header));
    memcpy(tmp+sizeof(my_header),data,data_size);

    int result;

    FD_ZERO(&write_set);
    FD_SET(connection,&write_set);

    struct timeval time_out;

    time_out.tv_sec=0;
    time_out.tv_usec=1500;

    header->super_id=0xdead;
    while(total_sent < data_size)
    {
        length= (data_size+sizeof(my_header))-total_sent;

        if(result== SOCKET_ERROR) return -1;
        if(result!=0 && FD_ISSET(connection, &write_set))
        {
            bytes_sent = send(connection,tmp,length,0);
        }

            printf("bytes sent per iteration=%d\n",bytes_sent);
        if(bytes_sent == SOCKET_ERROR)
        {
            return SOCKET_ERROR;
        }
        if(bytes_sent > 0)
        {
         total_sent+= bytes_sent-sizeof(my_header);

         tmp = realloc(tmp,sizeof(my_header)+(data_size-total_sent));


         memcpy(tmp,header,sizeof(my_header));
         memcpy(tmp+sizeof(my_header),data,data_size-total_sent);
        }
        else break;


    }
    free(tmp);
    return total_sent;
}

int main(int argc, char *argv[])
{
     start_winsock();
     int result = create_connection("2.2.2.2",88);
     if(result!=0) { printf("Cannot connect\n"); return 0; }

     pmy_header *header = malloc(sizeof(my_header));

     int buf_size = 500000;
     char buffer_test[buf_size];
     ZeroMemory(buffer_test,buf_size);
     int count=0;
     for(count;count<buf_size;count++)
     {
       strcat(buffer_test,"4");
     }
     result = SendMyData(header,buffer_test,buf_size);

} 

1 个答案:

答案 0 :(得分:2)

send()无法保证发送您要求发送的所有内容。它可能发送更少。您必须考虑返回值。如果它小于您请求的数量,则必须再次调用send()以重新发送剩余的字节,然后再发送新的字节。在非阻止的情况下,您还必须考虑WSAEWOULDBLOCK

并且您不会在send()发送的每个块上放置标题。你在每个块告诉send()发送一个标题。你做自己的分块,不要担心TCP内部的分块。这是一个网络实现,它不会影响您的协议。接收方应该注意你的块标题,根据需要多次调用recv()来接收完整的标头和数据以解决TCP的分块问题。

尝试更像这样的东西:

SOCKET connection = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
if (connection == INVALID_SOCKET)
{
     return WSAGetLastError();
}

ULONG NonBlock = 1;
in result = ioctlsocket(connection, FIONBIO, &NonBlock);
if (result == SOCKET_ERROR)
{
    result = WSAGetLastError();
    closesocket(connection);
    return result;
}

struct sockaddr_in target;
memset(&target, 0, sizeof(target));
target.sin_family = AF_INET;
target.sin_addr.s_addr = inet_addr(host);
target.sin_port = htons(port);

result = connect(connection, (SOCKADDR*)&target, sizeof(target));
if (result == SOCKET_ERROR)
{
    result = WSAGetLastError();
    if (result != WSAEWOULDBLOCK)
    {
        closesocket(connection);
        return result;
    }

    fd_set write_fds;
    FD_ZERO(&write_fds);            
    FD_SET(connection, &write_fds);

    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    result = select(0, NULL, &write_fds, NULL, &tv);
    if (result == SOCKET_ERROR)
    {
        result = WSAGetLastError();
        closesocket(connection);
        return result;
    }

    if (result == 0)
    {
        closesocket(connection);
        return WSAETIMEDOUT;
    }
}

char *tmp_data = data;
int data_remaining = data_size;

while (data_remaining > 0)
{
    int pkt_data_size = min(data_remaining, 1024); // replace 1024 with whatever maximum chunk size you want...

    int pkt_size = sizeof(header) + pkt_data_size;
    char *pkt = malloc(pkt_size);
    if (!pkt) return -1;

    // fill header as needed...
    memcpy(pkt+sizeof(header), tmp_data, pkt_data_size);

    tmp_data += pkt_data_size;
    data_remaining -= pkt_data_size;

    char *tmp_pkt = pkt;
    while (pkt_size > 0)
    {
        result = send(connection, tmp_pkt, pkt_size, 0);
        if (result == SOCKET_ERROR)
        {
            result = WSAGetLastError();
            if (result != WSAEWOULDBLOCK)
            {
                free(pkt);
                return -1;
            }

            fd_set write_set;
            FD_ZERO(&write_set);
            FD_SET(connection, &write_set);

            struct timeval time_out;
            time_out.tv_sec = 5;
            time_out.tv_usec = 0;

            result = select(0, NULL, &write_set, NULL, &time_out);
            if (result != 1)
            {
                free(pkt);
                return -1;
            }

            continue;
        }

        tmp_pkt += result;
        pkt_size -= result;
    }

    free(pkt);
}