Chinaunix

标题: LINUX下TCP程序问题,为何服务端会延迟一会儿收到客户端的数据 [打印本页]

作者: dablyo    时间: 2007-02-07 09:47
标题: LINUX下TCP程序问题,为何服务端会延迟一会儿收到客户端的数据
简单点说,就是客户端发送a,服务端没收到任何东西,客户端发送b,客户端收到a,客户端发送c,服务端收到b
下面是详细的情况

linux下的c程序作为服务端,客户端是java程序。
服务端使用非阻塞socket,每隔一秒定时接收一次,客户端每隔5秒发送心跳信息,此外客户端不定时发送指令数据,要求服务端回应此指令。

现在的问题是服务端经常会延迟(远大于1秒)收到客户端的指令,若客户端关闭发送心跳数据,则服务端经常会在客户端第二次发送指令的时候才收到第一次的数据。似乎是后一次发送的数据将前一次的数据由协议层‘挤’到socket层的。

后来我又把socket改为阻塞的,采用线程方式,效果类似,不知道原因在哪里?

代码见下面

服务端socket初始化部分
        if (srvsock > 0)
        {
                shutdown(srvsock, SHUT_RDWR);
                close(srvsock);
        }

        srvsock = socket(AF_INET, SOCK_STREAM, 0);
        if (srvsock < 0)
                return (BOOLEAN)1;              /*log it */
        i = fcntl(srvsock, F_SETFL, O_NONBLOCK);
        if (i < 0)
        {
                shutdown(srvsock, SHUT_RDWR);
                close(srvsock);
                return (BOOLEAN)1;      /*log it */
        }
        k = 1;
        v = 1;

        i = setsockopt(srvsock, SOL_SOCKET, SO_REUSEADDR, &k, sizeof(int));
        if (i < 0)
        {
                shutdown(srvsock, SHUT_RDWR);
                close(srvsock);
                return (BOOLEAN)1;      /*log it */
        }

        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(MTRACE_TCP_PORT);
        i = bind(srvsock, (struct sockaddr *)&local, sizeof(local));
        if (i < 0)
        {
                shutdown(srvsock, SHUT_RDWR);
                close(srvsock);
                return (BOOLEAN)1;      /*log it */
        }
        i = listen(srvsock, MAX_CLIENT_NUM);
        if (i < 0)
        {
                shutdown(srvsock, SHUT_RDWR);
                close(srvsock);
                return (BOOLEAN)1;      /*log it */
}

接收部分
                        while ((j = recv(sockid, msgbuf, sizeof(msgbuf), 0/*MSG_NOSIGNAL*/)) > 0)
                        {
/ *process... */
                        }
                        if (j == 0)             /*peer closed */
                        {
/* close */
                        }
                        else if (j < 0)
                        {
                                if (errno != EAGAIN)
                                {
/* close */
                                }
                        }



了socket section 7,上面说
SO_RCVLOWAT and SO_SNDLOWAT
     Specify  the minimum number of bytes in the buffer until the socket layer will pass the data to the protocol (SO_SNDLOWAT) or the user on receiving (SO_RCVLOWAT).  These two values are  not  changeable  in  Linux  and  their  argument  size  is  always  fixed to 1 byte.  getsockopt is able to read them; setsockopt will always  return ENOPROTOOPT.

抓包器抓包的结果可以确定客户端发的数据已经发送到了服务端的机器里,貌似协议层没有通知应用层来收这些数据。只到下一次数据的到来。根据上面的描述应该不是因为SO_RCVLOWAT的问题。


在accept时,设置客户端对应socket的非阻塞属性,如下:

       fd = accept(srvsock, (struct sockaddr *)&addr, (socklen_t *)&j);
       if (fd > 0)
       {
           fcntl(fd, F_SETFL, O_NONBLOCK);
           k = setsockopt(fd,SOL_SOCKET,SO_SNDBUF,&maxsndbuf,4);
           if ( k != 0)
......
        }
作者: dablyo    时间: 2007-02-07 09:49
后来我又些了一个linux的服务端测试程序,客户端采用delphi,发送字符串,服务端将之打出来,情况和上面的一样,代码如下

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int srvsock;
int cltsock[3];
int main(int argc,char* argv[])
{
        int i,k,j,len;
        struct sockaddr_in local;
        fd_set rdfs,wrfs,errfs;
        struct timeval tv;
        struct sockaddr addr;
        char msgbuf[512];


        for(i = 0; i < 3; i++)
                cltsock[i] = 0;
        srvsock = socket(AF_INET, SOCK_STREAM, 0);
        if (srvsock < 0)
        {
                printf("\nsocket error";
                return 1;
        }
        i = fcntl(srvsock, F_SETFL, O_NONBLOCK);
        if (i < 0)
        {
                printf("\nfcntl error";
                close(srvsock);
                return 1;
        }
        k = 1;
        i = setsockopt(srvsock, SOL_SOCKET, SO_REUSEADDR, &k, sizeof(int));
        if (i < 0)
        {
                printf("\nsetsockopt error";
                close(srvsock);
                return 1;
        }
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(5500);
        i = bind(srvsock, (struct sockaddr *)&local, sizeof(local));
        if (i < 0)
        {
                printf("\nbind error";
                close(srvsock);
                return 1;
        }
        i = listen(srvsock, 3);
        if (i < 0)
        {
                printf("\nlisten error";
                close(srvsock);
                return 1;
        }
        for(;
        {
                FD_ZERO(&rdfs);
                FD_ZERO(&errfs);
                FD_SET(srvsock,&rdfs);
                FD_SET(srvsock,&errfs);
                for(i = 0; i < 3; i++)
                        if (cltsock[i] > 0)
                        {
                                FD_SET(cltsock[i],&rdfs);
                                FD_SET(cltsock[i],&errfs);
                        }
                tv.tv_sec = 1;
                tv.tv_usec=0;
                k = srvsock;
                for(i = 0; i < 3; i++)
                        if (cltsock[i] > k) k = cltsock[i];
                i = select(k + 1,&rdfs,NULL,&errfs,&tv);
                if ( i==0) continue;
                if ( i > 0)
                {
                        if (FD_ISSET(srvsock,&rdfs))
                        {
                                j = sizeof(addr);
                                k = accept(srvsock,&addr,&j);
                                if (k > 0)
                                {
                                        if (cltsock[0] > 0) close(cltsock[0]);
                                        cltsock[0] = k;
                                        j = fcntl(k, F_SETFL, O_NONBLOCK);
                                        if (j < 0)
                                        {
                                                printf("\ncltsock fcntl error";
                                                close(k);
                                        }
                                }
                        }
                        if (FD_ISSET(srvsock,&errfs))
                        {
                                printf("\nsrvsock err";
                                close(srvsock);
                                return 1;
                        }
                        if (FD_ISSET(cltsock[0],&rdfs))
                        {
                                memset(msgbuf,0x0,512);
                                len = recv(cltsock[0],msgbuf,512,MSG_NOSIGNAL);
                                if (len == 0)
                                {
                                        close(cltsock[0]);
                                        printf("\nthe peer close";
                                }
                                if (len < 0)
                                {
                                        if ( errno != EAGAIN)
                                        {
                                                close(cltsock[0]);
                                                printf("\nthe peer close due to error";
                                        }
                                }
                                printf("\ndata income,len:%d,content:%s",len,msgbuf);
                        }
                }
        }
        return 0;
}
作者: dablyo    时间: 2007-02-07 09:51
估计应该是哪个参数没有设置好造成的
只是我不知道而已
作者: cookis    时间: 2007-02-07 10:03
最好采用 head + body 的形式..
TCP有缓存的..不一定每个数据包都立即被应用程序接收到.
作者: dablyo    时间: 2007-02-07 10:13
刚才我跟了一下2楼的程序
                      memset(msgbuf,0x0,512);
                                len = recv(cltsock[0],msgbuf,512,MSG_NOSIGNAL);
                                if (len == 0)
                                {
                                        close(cltsock[0]);
                                        printf("\nthe peer close";
                                }
                                if (len < 0)
                                {
                                        if ( errno != EAGAIN)
                                        {
                                                close(cltsock[0]);
                                                printf("\nthe peer close due to error";
                                        }
                                }
                                printf("\ndata income,len:%d,content:%s",len,msgbuf);
客户端发送后,服务端实际是收到了的,msgbuf中有正确的内容,只是
                                printf("\ndata income,len:%d,content:%s",len,msgbuf);
一行打出的是上一次收到的东西,比较奇怪。
作者: cookis    时间: 2007-02-07 10:43
对了.还有..recv(cltsock[0],msgbuf,512,MSG_NOSIGNAL); 这个MSG_NOSIGNAL标志位..应该是用在send
作者: langue    时间: 2007-02-07 11:09
--

有 nagle 算法

  1. if there is new data to send
  2. if the window size and available data is >= MSS
  3.   send complete MSS size segment now
  4. else
  5.   if there is unconfirmed data still in the pipe
  6.    enqueue data in the buffer until an acknowledge is received
  7.   else
  8.    send data immediately
复制代码


--
作者: coolzhh    时间: 2007-02-07 11:55
设置NODelay看看
作者: cookis    时间: 2007-02-07 13:14
现在的TCP协议栈都做了优化..一般的小包都加了PSH位..所以一般都不会有延时的..我抓我自己的包..都有PSH位.除了那些连续的大数据包.
楼主的情况..是不是客户端的问题..
作者: joyue    时间: 2007-02-07 13:40
原帖由 cookis 于 2007-2-7 13:14 发表
现在的TCP协议栈都做了优化..一般的小包都加了PSH位..所以一般都不会有延时的..我抓我自己的包..都有PSH位.除了那些连续的大数据包.
楼主的情况..是不是客户端的问题..



真解,上午抓了一下包,几乎大部分tcp的psh都置位了
作者: dablyo    时间: 2007-02-12 16:50
是服务端代码拆报的问题,出在判断报分隔符的位置上,已解决。

2楼的例子不知道是怎么回事,不过肯定和一楼的原因不一样。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2