首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 操作系统 > UNIXLINUX >

一个关于poll中accpet的有关问题

2013-03-17 
一个关于poll中accpet的问题在网上看到这样的一个说法:当一个已完成的连接准备好被accept的时候,select会

一个关于poll中accpet的问题
在网上看到这样的一个说法:
当一个已完成的连接准备好被accept的时候,select会把监听socket标记为可读。因此,如果用select等待外来的连接时,应该不需要把监听socket设置为非阻塞模式,因为如果select告诉我们连接已经就绪,accept就不应该被阻塞。不过这样做的时候有一个BUG:当客户端在跟服务器建立连接之后发送了一个RST包,这个时候accept就会阻塞,直到有下一个已完成的连接准备好被accept为止。
struct linger的l_onoff标志设为1,l_linger设为0。这个时候,如果关闭TCP连接时,会先在socket上发送一个RST包。这个时候会出现下面的问题:
A:select向服务器返回监听socket可读,但是服务器要在一段时间之后才能调用accept;
B:在服务器从select返回和调用accept之前,收到从客户发送过来的RST;
C:这个已经完成的连接被从队列中删除,我们假设没有其它已完成的连接存在;
D:服务器调用accept,但是由于没有其它已完成的连接存在,因而服务器被阻塞了;
注意,服务器会被一直阻塞在accept调用上,直到另外一个客户建立一个连接为止;但是如果一直没有其它客户建立连接,那么服务器将仍然一直被阻塞在accept调用上,不处理任何其他已就绪的socket;
解决这个问题的办法是:
A:如果使用select来获知何时有链接已就绪可以accept时,总是把监听socket设置为非阻塞模式,并且
B:在后面的accept调用中忽略以下错误:EWOULDBLOCK(源自Berkeley的实现在客户放弃连接时出现的错误)、ECONNABORTED(Posix.1g的实现在客户放弃连接时出现的错误)、EPROTO(SVR4的实现在客户放弃连接时出现的错误)和EINTR(如果信号被捕获).

现在代码如下:
服务端


#include <stdio.h>
#include <strings.h>
#include <linux/limits.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <errno.h>
#include <asm/poll.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h> 
#include <fcntl.h>

typedef struct 
{
char info[1024];
    char name[20];
int  id;
}STU;

#define MAXLINE 1024
#define SERV_PORT 9988

#define CTRLVA 1

ssize_t Readline(int fd, void *buf, size_t num);
ssize_t writen(int fd, const void *vptr, size_t n);

/*设置为非阻塞*/
static int setnonblocking(int sock)
{
    int opts;
opts = fcntl(sock, F_GETFL);
if (opts < 0)
{
return -1;
}

    opts = opts|O_NONBLOCK;
if (fcntl(sock, F_SETFL, opts) < 0)
{
return -1;
}
return 0;
}

int main(int argc, char *argv[])
{
int      i,maxi,listenfd,connfd,sockfd;
int      nready;
ssize_t  n;
char     line[MAXLINE];
socklen_t clilen;
    struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr,seraddr;
STU     stu;

listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&seraddr, sizeof(seraddr));

seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
seraddr.sin_port = htons(SERV_PORT);

bind(listenfd, (struct sockaddr*)&seraddr, sizeof(seraddr));

listen(listenfd, 5);

    /*if (setnonblocking(listenfd))
{
printf("err\n");
exit(-1);
}*/

    client[0].fd = listenfd;
client[0].events = POLLRDNORM;
    for (i=1; i<OPEN_MAX; i++)
    {
client[i].fd = -1;
    }

maxi = 0;

for (; ; )
{
printf("333\n");
nready = poll(client, maxi+1, -1);
        if (client[0].revents & POLLRDNORM)
        {


clilen = sizeof(cliaddr);
printf("111\n");
sleep(3);
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
printf("connfd = %d\n", connfd);
/*if (connfd == -1) 
{  
if (errno != EAGAIN && errno != ECONNABORTED   
&& errno != EPROTO && errno != EINTR)  
{
perror("accept");  
}
}*/
printf("222\n");  
for (i=1; i<OPEN_MAX; i++)
{
if (client[i].fd < 0)
{
client[i].fd = connfd;
}
break;
}

if (i == OPEN_MAX)
{
printf("i == OPEN_MAX\n");
return -1;
}

client[i].events = POLLRDNORM;
if (i > maxi)
maxi = i;

if (--nready <= 0)
continue;
        }

for (i=1; i<=maxi; i++)
{
if ((sockfd = client[i].fd) < 0)
continue;
 
if (client[i].revents & (POLLRDNORM|POLLERR))
{
memset(&stu, 0, sizeof(STU));
if ((n = read(sockfd, &stu, sizeof(STU))) < 0)
{
if (errno == ECONNRESET)
{
close(sockfd);
client[i].fd = -1;
printf("Readline:[%d:%s]\n", errno, strerror(errno));
}
else
{
printf("readlien err\n");
return -2;
}
}
else if (n == 0)
{
close(sockfd);
client[i].fd = -1;
}
else
{
if (write(sockfd, &stu, n) < 0)
{
printf("%d : %s\n", errno, strerror(errno));
}
}

if (--nready <= 0)
break;
}
}
}

    return 0;
}


/*自定制的read函数*/
ssize_t Readline(int fd, void *buf, size_t num)
{
ssize_t res;
size_t n;
char *ptr;

n = num;
ptr = (char *)buf;
while (n > 0) 
{
 if ((res = read (fd, ptr, n)) == -1)
 {
  if (errno == EINTR) /*中断*/
   res = 0;
  else
   return (-1);
 }
 else if (res == 0)
 {
break;
 }
             
 if (ptr[res-2] == CTRLVA)
 {
 printf("xxxx   res = %d, ptr = %c\n", res, ptr[res-2]);
 ptr += res;
 n -= res;
 break;
 }

 ptr += res;
 n -= res;
 
}

return (num - n);
}
   
/*自定制的write函数*/
ssize_t writen(int fd, const void *vptr, size_t n)
{
    size_t     nleft;


ssize_t    nwritten;
const char *ptr;

    ptr = (char *)vptr;
nleft = n;

while (nleft > 0)
{
if ((nwritten = write(fd, ptr, nleft)) <= 0)
{
if (nwritten < 0 && errno == EINTR) 
{
nwritten = 0;
}
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
    
return n;
}



客服端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>

typedef struct 
{
char info[1024];
    char name[20];
int  id;
}STU;

#define SERVPORT 9988/* 服务器监听端口号 */
#define max(a, b) (((a) > (b)) ? (a):(b))

#define CTRLVA 1

int main(int argc, char *argv[])
{
int       server;
    int       maxfd = 0;
    ssize_t   n;
    STU       stu;      
    struct sockaddr_in sin;
    struct hostent *host;
    int       clientmsgtype;
char      ctrlva[2];

    fd_set sock_set;
    FD_ZERO(&sock_set);

    server = socket(AF_INET, SOCK_STREAM, 0);

    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERVPORT);
    sin.sin_addr.s_addr = inet_addr("127.0.0.1");
 
    if (connect (server, (struct sockaddr*)&sin, sizeof(sin))<0)
    {
        printf("connect err\n");
        exit(1);
    }
 close(server);  
 exit(-1);
memset(&stu, 0, sizeof(stu));

memset(ctrlva, 0, sizeof(ctrlva));
    sprintf(ctrlva, "%c", CTRLVA);

    strcpy(stu.name, "zhangyanli");
stu.id = 10;
  
write (server, (void *)&stu, sizeof(STU));

memset(&stu, 0, sizeof(stu)); 

  if ((n = read(server, &stu, sizeof(STU))) > 0)
  {
       printf("stu.name = %s\n", stu.name);
   printf("stu.id = %d\n", stu.id);
  }
else if (n == -1)
{
printf("read err\n");
printf("Readline:[%d:%s]\n", errno, strerror(errno));
}

close(server);

    return 0;
}



问题:
我在客服端connect后,立即close(server),在服务端进入accept()之前sleep(3),按道理accept()会阻塞啊,可为什么还是返回了4,也就是printf("connfd = %d\n", connfd);打印出connfd = 4啊?并且用
tcpdump观察还是建立了连接啊···
还有,我想模拟
“不过这样做的时候有一个BUG:当客户端在跟服务器建立连接之后发送了一个RST包,这个时候accept就会阻塞,直到有下一个已完成的连接准备好被accept为止。”
于是,我去掉客服端的
close(server);  
exit(-1);
然后启动一个客服端,在立即又启动一个客户端且立即关闭,按道理说这个时候服务端会阻塞与accpet()调用啊?!可为什么还是正常的进行了下去啊
[解决办法]
select与accept之间收到RST, accept并不一定会阻塞, 只是在某些系统上。
比如我用的FreeBSD上就会accept就会返回-1 errno ECONNABORTED (这似乎是POSIX的标准)
而在Linux上只有accept是会成功,只有在read时才会出错.

AIX 就认为select后accept阻塞是个BUG,进行过Fix。

http://www-01.ibm.com/support/docview.wss?uid=isg1IY54858

所以select后accept阻塞不一定能看到。
所以楼主帖子里的说法可以这样理解:
将监听套接口设置为非阻塞,并忽略 EWOULDBLOCK, ECONNABORTED, EPROTO,可以防止在某些系统上select后阻塞在accept上。

热点排行