为什么套接字不能正确处理缓冲区大小?

时间:2012-06-07 18:43:52

标签: c++ sockets

当我将BUF_SIZE设置为10000时工作正常,但50000则没有。 我只是试图将数据从客户端发送到服务器并测试缓冲区大小,但我发现在某些大小的情况下,应用程序无法正常工作。

为什么?我怎么能解决它?

例如,我运行服务器和客户端,第一次尝试工作正常,但是当我重新运行客户端时,启动交付问题。

这是服务器:

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <signal.h>

#define BUF_SIZE 50000

using namespace std;

void manejador(int signo);

int main()
{
    int     sservice, sclient,l,nbytes_read, err, nbytes_sent;
    bool    end;
    char    buf[BUF_SIZE];
    struct sockaddr_in sin, clientfsin;
    pid_t pid;
    int status;

    sservice=socket(PF_INET,SOCK_STREAM, 0); /*Open the socket*/
    if(sservice == -1)
    {
        perror("Server. Socket: ");
        exit(-1);
    }

    sin.sin_family      = AF_INET;  /*ARPANET address family*/
    sin.sin_addr.s_addr = INADDR_ANY;   /*Accept connections on any Interface*/ 
    sin.sin_port        = htons(4000);          /*Service TSAP > 1023. CHANGE IT!*/

    /*Register the server in the system*/
    err=bind(sservice, (struct sockaddr*)&sin, sizeof(sin));
    if(err == -1)
    {
        perror("Server. bind: ");
        exit(-1);
    }

    /*Up to 5 waiting connections*/
    err = listen(sservice,5);
    if(err == -1)
    {
        perror("Server. Listen: ");
        exit(-1);
    }

    /* Receiving requests loop */
    for(;;)
    {
        /*Accept a connection from a client*/
        l = sizeof(clientfsin);
        sclient = accept(sservice,(struct sockaddr *)&clientfsin, (socklen_t*) &l);
        if(sclient == -1)
        {
            perror("Server. Accept: ");
            continue;
        }

        signal(SIGCHLD,manejador);          //Quitar si ponemos waitpid
        pid = fork();

        if(pid == -1){
            printf("Error al crear el proceso hijo\n");
            exit(0);
        }

        if(pid){
            close(sclient);
            //waitpid(pid,&status,0); //Descomentar si usamos waitpid
        }else{

        close(sservice);    
        /*Give the service*/
        end = false;
        int i=1;
        while(!end && (i<=10)) 
        {   
            nbytes_read=recv(sclient,(char *)buf,sizeof(buf),0);


            if (nbytes_read > 0)
            {
                buf[nbytes_read]='\0'; 
                //cout << "SERVER>Server received: " << buf << endl;
                printf("Recepcion <%i>: Se han recibido <%i> bytes del cliente\n",i,nbytes_read);
                cout.flush();

                err = 0;
                //sprintf(buf,"%s_server",buf);
                nbytes_sent = send(sclient,(char *)buf,sizeof(buf),0);
                printf("Envio <%i>: Se han enviado <%i> bytes al cliente\n",i,nbytes_sent);
                i++;
            }
            else    
            {
                perror("Sever. Receive/read: ");
                end=true;
                err = -1;
            }
        }

        if(err >= 0)
            cout << "SERVER>Cliente Atendido" << endl;
        else
            cout << "SERVER>Finalizacion incorrecta del cliente" << endl;

        /*Never forget to close a socket!*/
        close(sclient);
        exit(0);
        }
    }

    close(sservice);
    printf("Fin server");

} /*main()*/

void manejador(int signo) //comentar si usamos waitpid
{
      int estado;
      wait(&estado);
}

这是客户:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sys/time.h>

#define BUF_SIZE 50000

using namespace std;

int main()
{
    int     sock, err;
    bool    end;
    char    buf[BUF_SIZE];
    struct sockaddr_in sout;

    sock=socket(PF_INET,SOCK_STREAM, 0); /*Open the socket*/
    if(sock == -1)
    {
        perror("Client. Socket: ");
        exit(-1);
    }


    sout.sin_family      = AF_INET;                       /*ARPANET address family*/
    sout.sin_addr.s_addr = inet_addr("127.0.0.1");     /*Which server?*/
    sout.sin_port        = htons(4000);                       /*Output port*/

    /*Connect to the server*/
    err = connect(sock,(struct sockaddr *)&sout, sizeof(sout));
    if(err == -1)
    {
        perror("Client. Connect: ");
        exit(-1);
    }

    end = false;
    double t1,t2;
    while(!end) 
    {   
        /*Ask for the service*/
        //cout << endl << "CLIENT> Send a message...: " ;    cout.flush();
        //cin.getline(buf, 128);
        int i=0;
        for(i=0;i<10;i++){
            timeval tim;
                    gettimeofday(&tim, NULL);
                    t1=tim.tv_sec+(tim.tv_usec/1000000.0);

            err = send(sock,(char *)buf,sizeof(buf),0);

            if(err == -1)
            {
            perror("Client. Send/write: ");
            exit(-1);
            }
            printf("Envio <%i>: Se han enviado <%i> bytes\n",i+1,err);

            gettimeofday(&tim, NULL);
                    t2=tim.tv_sec+(tim.tv_usec/1000000.0);
                    printf("%.6lf para el envio de <%i>\n", t2-t1,i+1);

            err = recv(sock,(char *)buf,sizeof(buf),0);
            printf("Recepcion <%i>: Se han recibido <%i> bytes\n",i+1,err);
            //cout << "CLIENT> Server response: " << buf;   
            cout.flush();
        }
        end=true;
    }

    close(sock);

} /*main()*/

Offtopic:对不起西班牙语的评论;)

2 个答案:

答案 0 :(得分:3)

错误:

1)您的服务器不会尝试确保收到完整的消息。

2)无论实际需要发送多少字节,您的服务器都会发送50,000个字节。

3)如果您的服务器实际读取50,000个字节,则尝试添加终止零将溢出缓冲区。

3)您的客户端发送50,000个不确定字节。

4)您的客户端忽略它收到的字节数。

5)客户端不确保它实际收到服务器发送的终止零字节。

但你最大的错误是:你没有合理的协议。如果消息以零字节终止,为什么要发送50,000字节?如果您的消息总是50,000字节,为什么接收器不会尝试接收50,000字节?

我会给你标准建议我给每个TCP程序员:开始制定协议并记录它。协议应指定谁发送和何时发送。协议应指定消息的框架方式。协议应指定有效和无效消息的规则。协议应指定如何检测和处理死连接。等等。

正确记录协议需要一个小时左右,但这非常值得。没有一个,我很难分辨出上面哪些错误确实是错误。 (例如,即使消息只是“hi”,也许你真的应该总是发送50,000个字节。服务器应该通过字节计数还是终止零来查找消息的结尾?依此类推。)

答案 1 :(得分:1)

我运行了你的程序。问题在于您对recv的工作方式的期望。您期望接收器始终能够在单次读取中读取所有BUF_SIZE个字节。如果这是您的期望,那么您应该在MSG_WAITALL来电的最后一个参数中设置recv标记。虽然在阻止I / O时send调用将一直被阻塞,直到发送完所有字节为止,但默认情况下recv不是这样。然而,它将接收到套接字输入队列中的大量数据,因此recv可能比没有MSG_WAITALL标志时的预期短。

至于为什么较小的BUF_SIZE值起作用而较大的值不起作用,这可以通过套接字输入队列的大小来解释。您可以尝试将setsockoptSO_RCVBUF选项一起使用,将其设置为匹配或超过BUF_SIZE的内容,并查看它是否适合您。但是,在现实生活中,网络条件会决定输入缓冲区是否保持满,所以短读取只是程序应该处理的事情。

答案的其余部分解决了一些风格问题和一种&#34; off by one&#34;代码中的错误。

我注意到你正在堆栈中创建一个相当大的数组。您可以考虑动态分配它。这样做的一种方法是使用矢量。

std::vector<char> buf;

buf.resize(BUF_SIZE);
nbytes_read = recv(sclient, &buf[0], buf.size(), 0);

注意另一件事(@ Linux_iOS.rb.cpp.c.lisp.m.sh注意到)如果nbytes_read的值为BUF_SIZE,那么尝试{{1}终止输入是错误的事情。它将访问缓冲区域之外的数据。如果你真的必须NUL终止,那么使用向量,你可以使用NUL

push_back

但你应该发现设置缓冲区的大小更容易。

if (nbytes_read == buf.size()) buf.push_back('\0');
else buf[nbytes_read] = '\0';

然后,当您回显数据时,您只会回显读取的内容。

buf.resize(nbytes_read);