Berkeley Socket: системный вызов recv

//server side
void* s2(void *arg){
    info *s = (info*)arg;
    char buffer[MAXS];
    int k;
    sockaddr_in addr;
    socklen_t aSize = sizeof(sockaddr_in);
    int sfd = accept(s->fd,(sockaddr*)&addr,(socklen_t*)&aSize);
    if(sfd<0){
        s->current--;
        pthread_exit(0);
    }
    while(1){
        k = recv(sfd,buffer,MAXS,0);
        cout<<buffer<<"\n";
        //1. k ==0 socket has been closed by client 
        //2. k==-1 error in recv 
        //3. recv quit
        if((k==-1)||(!strncmp(buffer,"quit",4))||(k==0))break; 
        sprintf(buffer,"%d\n",(int)strlen(buffer)); //Convert length to  string using sprintf()
        send(sfd,buffer,strlen(buffer),0); //send buffer to client 
    }
    close(sfd);
    if(s->limit==s->current)
    FD_SET(s->fd,&sfds);
    s->current--; //decreament the client number 
    pthread_exit(0);
}

//client side
1. send(sockfd,"sadhdag",8,0);
2. send(sockfd,"ss",3,0);

Сервер recv sadhdag при первом вызове recv .

При втором вызове сервера recv recv

ss
dag

серверная часть:

функция s2 запускается потоком, и аргумент передается с информацией о сокете, соединение принимается там, а send и recv вызываются на вновь принятом клиенте.

Почему это происходит? или как этого избежать?


person sonus21    schedule 26.02.2015    source источник
comment
Можете ли вы опубликовать фактический блок кода на сервере, который выполняет два вызова recv()?   -  person Karthik Kalyanasundaram    schedule 26.02.2015
comment
@KarthikKalyanasundaram добавил.   -  person sonus21    schedule 26.02.2015
comment
немедленным решением будет memset buffer до 0 перед каждым recv вызовом. memset(buffer, 0, MAXS).   -  person Karthik Kalyanasundaram    schedule 26.02.2015
comment
@KarthikKalyanasundaram, зачем нам это нужно?   -  person sonus21    schedule 26.02.2015
comment
memset сбросит данные, которые сейчас хранятся в вашем buffer (т.е. удалит ранее считанные данные). В соответствии с вашим кодом вам не нужно вызывать memset, так как после 2-го recv ваш buffer должен выглядеть так |s|s|\0|h|d|a|g|\0|, и когда вы выполняете cout‹‹buffer‹‹\n, он должен печатать содержимое до первого нулевого символа и только сс должны быть напечатаны. Если вы используете Visual Studio, можете ли вы просто проверить содержимое в буфере с помощью Quick Watch и убедиться, что в вашем буфере есть «\ 0» после «S», «S»   -  person Karthik Kalyanasundaram    schedule 26.02.2015
comment
Вы не завершаете буфер нулем.   -  person mark4o    schedule 26.02.2015
comment
@KarthikKalyanasundaram, но это печатает всю строку, как я уже упоминал.   -  person sonus21    schedule 26.02.2015
comment
@sonukumar Если вы используете Visual Studio, можете ли вы просто проверить содержимое в буфере с помощью Quick Watch и убедиться, что в вашем буфере есть «\ 0» после «S», «S»   -  person Karthik Kalyanasundaram    schedule 26.02.2015
comment
@mark4o - это то, что я изначально подозревал, но от клиента он отправляет строку с нулевым завершением send(sockfd,"ss",3,0), он отправляет 3 байта, и я надеюсь, что это также должно отправлять нулевой терминатор.   -  person Karthik Kalyanasundaram    schedule 26.02.2015
comment
@KarthikKalyanasundaram Нет. Вы говорите ему отправить 3 байта: он отправляет 3 байта. Нулевое окончание строк — это C-изм. К сокетам отношения не имеет.   -  person user207421    schedule 26.02.2015
comment
@EJP, когда вы говорите ss (насколько я понимаю), это уже включает \0 в конце. Когда вы говорите отправить 3 байта, он должен отправить |s|s|\0|   -  person Karthik Kalyanasundaram    schedule 26.02.2015
comment
@KarthikKalyanasundaram Он отправляет 3 байта, но потому, что вы сказали 3 байта, а не потому, что он подчиняется какому-либо правилу замыкающего нуля. Если вы скажете ему отправить 4, он отправит 4, две буквы «а», ноль и все, что будет дальше в памяти.   -  person user207421    schedule 26.02.2015
comment
@EJP Хорошо. Мне интересно, если клиент отправляет 3 байта с данными |S|S|\0, recv также должен получать те же данные, верно? а данные в вызове buffer после recv должны были быть |s|s|\0|....   -  person Karthik Kalyanasundaram    schedule 26.02.2015


Ответы (2)


Вы игнорируете счетчик, возвращенный recv(). Вопреки предложениям в комментариях, нет необходимости memset() обнулять буфер до recv(), но необходимо использовать этот буфер только после до указанного количества. Например:

printf("%s", buffer);

неправильно, и

printf("%.*s", count, buffer);

верно.

NB

if((k==-1)||(!strncmp(buffer,"quit",4))||(k==0))break;

тоже не правильно. Должен быть

if((k==-1)||(k==0)||(k >= 4 && !strncmp(buffer,"quit",4))) break;

Недопустимо вообще просматривать буфер, если k не является положительным, и недопустимо сравнивать в нем четыре символа, если в нем есть четыре символа.

person user207421    schedule 26.02.2015
comment
Была проблема с telnet, который также отправлял 2 дополнительных байта (для ввода) символа. - person sonus21; 27.02.2015

Вы забыли на самом деле реализовать какой-либо протокол или логику уровня приложения любого рода. Это подводит итог:

    k = recv(sfd,buffer,MAXS,0);
    cout<<buffer<<"\n";

Этот код предполагает, что вы завершаете свои сообщения новой строкой:

    sprintf(buffer,"%d\n",(int)strlen(buffer)); //Convert length to  string using sprintf()
    send(sfd,buffer,strlen(buffer),0); //send buffer to client 

Если да, то где код для анализа этого на другом конце? Вы отбрасываете возвращаемую длину, поэтому вы даже не знаете, какие символы проверять на новую строку.

TCP — это протокол потока байтов, который не сохраняет границы сообщений на уровне приложения. Если вы хотите использовать его для отправки строк или других сообщений уровня приложения, вы должны указать и реализовать для этого протокол уровня приложения.

Вы вызываете recv, чтобы получить необработанные данные TCP, а затем выводите их, как если бы это была строка. Если вы собираетесь определить и реализовать протокол для отправки и получения строк по TCP, вам придется это сделать. Вы не можете просто представить, что это происходит по волшебству.

Если вы хотите получать сообщения, заканчивающиеся новой строкой, в строки в стиле C, вы, безусловно, можете это сделать. Но для этого вам нужно написать код.

person David Schwartz    schedule 26.02.2015
comment
Можете ли вы добавить ссылки или ссылки на проектирование логики на уровне приложения, реализацию, передовой опыт и т. д. ? - person sonus21; 27.02.2015