linux下使用write\send发送数据报 EAGAIN : Resource temporarily unavailable 错

本文探讨了在Linux环境下使用非阻塞Socket编程时遇到的问题,包括如何正确处理EAGAIN错误,非阻塞connect的实现细节,select超时设置的影响,send/recv函数的行为解析,以及关闭Socket后send/recv的操作注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://badqiu.iteye.com/blog/1149965


linux下使用write\send发送数据报 EAGAIN : Resource temporarily unavailable 错

首先是我把套接字设置为异步的了,然后在使用write发送数据时采取的方式是循环发送大量的数据;由于是异步的,write\send将要发送的数据提交到发送缓冲区后是立即返回的,并不需要对端确认数据已接收。在这种情况下是很有可能出现发送缓冲区被填满,导致write\send无法再向缓冲区提交要发送的数据。因此就产生了Resource temporarily unavailable的错误,EAGAIN 的意思也很明显,就是要你再次尝试。

把发送部分修改如下

  1. int SeanSend(int fd, void *buffer, int length)  
  2. {  
  3.     int bytes_left;   
  4.     int written_bytes;   
  5.     char *ptr;   
  6.     ptr=(char *)buffer;   
  7.     bytes_left=length;   
  8.     while(bytes_left>0)   
  9.     {   
  10.         /* 开始写*/   
  11.         written_bytes=write(fd, ptr, bytes_left);   
  12.         if(written_bytes<=0) /* 出错了*/   
  13.         {   
  14.             if(errno==EINTR) /* 中断错误 我们继续写*/   
  15.             {  
  16.                 continue;  
  17.                 printf("[SeanSend]error errno==EINTR continue\n");  
  18.             }  
  19.             else if(errno==EAGAIN) /* EAGAIN : Resource temporarily unavailable*/   
  20.             {  
  21.                 sleep(1);//等待一秒,希望发送缓冲区能得到释放  
  22.                 continue;  
  23.                 printf("[SeanSend]error errno==EAGAIN continue\n");  
  24.             }  
  25.             else /* 其他错误 没有办法,只好退了*/   
  26.             {  
  27.                 printf("[SeanSend]ERROR: errno = %d, strerror = %s \n"  
  28.                                 , errno, strerror(errno));  
  29.                 return(-1);  
  30.             }  
  31.         }  
  32.         bytes_left-=written_bytes;   
  33.         ptr+=written_bytes;/* 从剩下的地方继续写?? */   
  34.     }   
  35.     return length;   

非阻塞socket编程问题小结

http://blog.sina.com.cn/s/blog_4462f8560100tvu4.html


项目需要写一个主动连接且定时发送数据的客户端程序,并保证传输数据的可靠性和稳定性。

注意的问题有:

1.connect返回值判定

之前的程序

if(connect(tcp_client_sock,(structsockaddr*)&server, server_length) <0)

//向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接
     {
       printf("Can Not Connect To %s!\n",SERVE_IP);
       close(tcp_client_sock);

    return-1;
       //exit(1);
     }
     else
     {
     TCP_CONNET_FLAG = 1;//建立连接

...

}

但是在网上查询发现:当我们以非阻塞的方式来进行连接的时候,返回的结果如果是-1,这并不代表这次连接发生了错误,如果它的返回结果是EINPROGRESS,那么就代表连接还在进行中。后面可以通过poll或者select来判断socket是否可写,如果可以写,说明连接完成了。

更改如下:

//先定位为非阻塞模式,立即返回状态;如有错误存为SO_ERROR值     

if((flags =fcntl(tcp_client_sock,F_GETFL, 0 )) < 0)
        {
       perror("fcntl");
       return -1;
        }
        flags |= O_NONBLOCK;
        if(fcntl(tcp_client_sock, F_SETFL, flags) < 0)//设置socket为非阻塞模式
       {
         perror("fcntl");
         return -1;
       }

 

 if(connect(tcp_client_sock,(structsockaddr*)&server, server_length) <0) //向服务器发起连接
         

        if(errno != EINPROGRESS)//非等待状态
           {
             perror("connect error");

          close(tcp_client_sock);//下一步重连

         //return -1;
           }
           else//EINPROGRESS:正常处理连接
           {

           perror("connect");//查询ERROR值
              printf("check delay connected\n");
              goto done;//进一步检查是否握手完成
           }
       }

else
       {
    done:

        ...//下一步的select判定connect是否完成及tcp_client_sock可写性

    }

这里,perror("connect")语句查询ERROR值为“Operation now inprogress”,表明非阻塞connect立即返回的状态为正在建立三次握手;如果不想出现这种情况,可以将以上关于非阻塞设置socket的语句放到connect之后,就会在阻塞方式下等待connect完成,但仍需要作进一步检查。对于非阻塞方式,下一步就可以通过select自定义超时时间(通常比阻塞方式下connect超时时间短),并进一步检查是否连接错误和规定时间内套接口可读写性。

2.select超时设置问题

如果设置connect为非阻塞函数后,进行select时只关注writefds,忽略readfds,exceptfds,可能出现一个问题:本来不想由于connect阻塞等太久,结果用select后反而傻等了。

如果在connect后开始select,只关注writefds,设置的超时是10秒,在connect发出[SYN]后:假定目标IP的主机不存在或者是目标端口给防火墙过滤了,那么你等再久也不会有任何回复,这时候如果是阻塞connect可能要15秒才返回,那么你10秒就返回了,这种情况就赚了5秒。

然而假定connect的目标IP主机是存在的也没防火墙,只是端口是没打开的,在connect发出[SYN]后的1秒系统已经收到目标主机回复的[RST,ACK],也就是说系统此时已经知道这个端口是连接不上的了,但是应用程序只关注writefds,后面的9秒钟select就会傻傻的等待下去……原本以为用select来减少不必要的等待时间,如果不设置参数exceptfds,这时候反而浪费时间。 

3.send/recv 返回值

由于send、recv函数用于已连接的数据报或流式套接口s进行数据的接收。所以在非阻塞socket的客户端程序中recv、send函数成功返回并不代表对端一定收到了发送的消息。tcp协议本身是可靠的,并不等于应用程序用tcp发送数据就一定是可靠的。不管是否阻塞,send发送的大小,并不代表对端recv到多少的数据.

关于recv返回值,百度的解释:

     1).若无错误发生,recv()返回读入的字节数。

     2).如果连接已中止,返回0。

     3).否则的话,返回SOCKET_ERROR错误。

  如果套接口为SOCK_STREAM类型,并且远端“优雅”地中止了连接,那么recv()一个数据也不读取,立即返回。如果立即被强制中止,那么recv()将以WSAECONNRESET错误失败返回。

   接收数据时perror时常遇到"Resource temporarilyunavailable"的提示,errno代码为11(EAGAIN)。这表明你在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,不用管它,下次循环接着recv就可以。对非阻塞socket而言,EAGAIN不是一种错误。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。其实这算不上错误,只是一种异常而已。

  另外,如果出现EINTR即errno为4,错误描述Interrupted systemcall,即由于信号中断导致操作失败,也应该继续。

      val= recv(client_sock,info,length,MSG_NOSIGNAL);
      if(val< 0)//判断是否网络无数据或接收缓冲区是否准备好
      
            if(val==EAGAIN||EWOULDBLOCK||EINTR)
            {
                 printf("Recvdata Timeout.Waiting...\n");
                 return1;
             }
             else//网络异常断开或阻塞;需重新连接
             {
                 perror("recv");
                 return-1;
             }
      }

      elseif(val == 0)//server端正常关闭,需重新连接

         

        TCP_CONNET_FLAG=0;//关闭TCP连接flag
         printf("socketclose nomally\n");
         return0;
       }

      else//返回接收字节大于零,正常接收数据
       

           ...//对接收缓冲区info的每个字节作读写操作处理

          return 1;

       }

 

关于send函数在阻塞模式和非阻塞模式下的区别:

      在阻塞模式下,send函数的过程是将应用程序请求发送的数据拷贝到发送缓存中发送并得到确认后再返回.但由于发送缓存的存在,表现为:如果发送缓存大小比请求发送的大小要大,那么send函数立即返回,同时向网络中发送数据;否则,send向网络发送缓存中不能容纳的那部分数据,并等待对端确认后再返回(接收端只要将数据收到接收缓存中,就会确认,并不一定要等待应用程序调用recv);
      在非阻塞模式下,send函数的过程仅仅是将数据拷贝到协议栈的缓存区而已,如果缓存区可用空间不够,则尽能力的拷贝,返回成功拷贝的大小;如缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN.

 

当客户通过Socket提供的send函数发送大的数据包时,就可能返回一个EGGAIN的错误。该错误产生的原因是由于send函数中的size变量大小超过了tcp_sendspace的值。tcp_sendspace定义了应用在调用send之前能够在kernel中缓存的数据量。当应用程序在socket中设置了O_NDELAY或者O_NONBLOCK属性后,如果发送缓存被占满,send就会返回EAGAIN的错误。

为了消除该错误,有三种方法可以选择:
  1).调大tcp_sendspace,使之大于send中的size参数
  ---no -p -otcp_sendspace=65536
  2).在调用send前,在setsockopt函数中为SNDBUF设置更大的值

  intopt=SO_REUSEADDR;
     setsockopt(tcp_client_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
          opt = 256*1024;//512k
          int optlen = sizeof(int);
          setsockopt(tcp_client_sock,SOL_SOCKET,SO_SNDBUF,&opt,sizeof(int));
          getsockopt(tcp_client_sock,SOL_SOCKET,SO_SNDBUF,&opt,&optlen); 
  3).使用write替代send,因为write没有设置O_NDELAY或者O_NONBLOCK

 在优快云中看到有这样一种情况:

假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误,同时,不理会这次请求发送的数据.

所以,需要根据send()函数返回值及errno值作进一步处理。遇到该情况,函数要求尽量将数据写完再返回,或通过更改发送缓冲区大小。当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试send().这种方式并不很完美,在理论上可能会长时间的阻塞在socket的send()中,但暂没有更好的办法.

        SendFlag&=TCP_CONNET_FLAG;//测试tcp_client_sock描述符可写且TCP连接存在
           if(SendFlag==1)//尝试发送
           {
              res=send_client_info(tcp_client_sock,buf,45);            
               if(res<0)
               {
                 if(errno == EINTR)//当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,返回后判断网络状态再重试.
                   return -1;
                 if(errno == EAGAIN)//发送缓冲区剩余0字节,延时等待发送;
                 {
                      usleep(10000);
                      res=send_client_info(tcp_client_sock,buf,45);
                 }
               }
          }
          else
          {
          ...//发送缓冲区数据暂存,等待连接正常再发
          

linux编程环境中,如果TCP连接断开后继续发数据的时候,不仅send()的返回值会有反映,而且还会像系统发送一个异常消息,如果不作处理,系统会出BrokePipe,程序会退出。为此,send()函数的最后一个参数可以设MSG_NOSIGNAL,禁止send()函数向系统发送异常消息。

 

3.close(socket)后send/recv数据的问题

如果在发送数据的过程中send()没有完成,还有数据没发送,而调用了closesocket(),以前一般采取的措施是shutdown(s,SD_BOTH),但是数据将会丢失。

  如果设计程序功能要求待未发送完的数据发送出去后再关闭socket,进行如下操作:

  struct linger {

  u_short l_onoff;

  u_short l_linger;

  };

  linger m_sLinger;

  m_sLinger.l_onoff = 1;//在调用closesocket()时还有数据未发送完,允许等待

  //若m_sLinger.l_onoff=0;则调用closesocket()后强制关闭

  m_sLinger.l_linger = 5;//设置等待时间为5秒

  setsockopt( s, SOL_SOCKET, SO_LINGER,( const char* )&m_sLinger, sizeof( linger ) );

若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为“优雅的”关闭。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。 

WSAEWOULDBLOCK:该套接口设置为非阻塞方式且SO_LINGER设置为非零超时间隔。


<think>我们正在处理一个关于网络编程中accept函数失败的问题,具体误是'资源暂时不可用'(Resource temporarily unavailable)。这个误通常对应于误码EAGAIN或EWOULDBLOCK(在Linux中,这两个误码通常相同,表示非阻塞模式下资源暂时不可用)。 根据系统级指令,我们需要使用正确的格式书写数学表达式和独立公式,但在这个问题中,可能涉及数学表达的地方不多。我们将按照要求使用$...$和$$...$$格式,同时用中文回答,并生成相关问题。 ### 问题分析 当在非阻塞套接字上调用accept函数时,如果没有传入连接等待接受,accept会立即返回一个误,并设置errno为EAGAIN或EWOULDBLOCK(即“资源暂时不可用”)[^1]。这通常发生在以下情况: 1. 套接字被设置为非阻塞模式(O_NONBLOCK),并且当前没有连接请求等待接受。 2. 系统资源(如文件描述符)暂时不足,导致无法接受新连接。 ### 解决方案 1. **检查套接字是否处于非阻塞模式**:如果程序将监听套接字设置为非阻塞,那么在没有连接到来时调用accept就会返回EAGAIN。此时,应该等待套接字可读(即使用select、poll或epoll等I/O多路复用机制)后再调用accept。 2. **使用I/O多路复用机制**:在非阻塞模式下,通常需要结合I/O多路复用来等待连接到来。例如: ```c // 假设listen_sock是监听套接字 fd_set readfds; FD_ZERO(&readfds); FD_SET(listen_sock, &readfds); struct timeval timeout; timeout.tv_sec = 5; timeout.tv_usec = 0; int ready = select(listen_sock+1, &readfds, NULL, NULL, &timeout); if (ready > 0 && FD_ISSET(listen_sock, &readfds)) { // 现在可以调用accept了 int client_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &client_len); if (client_sock < 0) { // 处理误 } } ``` 3. **检查系统资源**:如果误是由于系统资源(如文件描述符数量达到上限)导致的,需要检查并增加资源限制。可以使用以下命令查看当前进程的文件描述符限制: ```bash ulimit -n ``` 或者在程序中通过setrlimit函数调整。 4. **处理其他误**:如果上述情况都不是,需要检查是否有其他误发生,例如套接字是否已经关闭等。 ### 代码示例 以下是一个使用非阻塞套接字和select处理accept的简单示例: ```c #include <sys/socket.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <stdio.h> int main() { int listen_sock = socket(AF_INET, SOCK_STREAM, 0); // ... 绑定和监听 ... // 将监听套接字设置为非阻塞 int flags = fcntl(listen_sock, F_GETFL, 0); fcntl(listen_sock, F_SETFL, flags | O_NONBLOCK); while (1) { fd_set readfds; FD_ZERO(&readfds); FD_SET(listen_sock, &readfds); struct timeval timeout = {.tv_sec = 1, .tv_usec = 0}; int ready = select(listen_sock+1, &readfds, NULL, NULL, &timeout); if (ready < 0) { perror("select"); break; } else if (ready == 0) { // 超时,继续等待 continue; } if (FD_ISSET(listen_sock, &readfds)) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &client_len); if (client_sock < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // 这应该不会发生,因为select已经告诉我们有连接 continue; } else { perror("accept"); break; } } // 处理新连接 // ... } } close(listen_sock); return 0; } ``` ### 引用说明 - 在非阻塞套接字上调用accept,如果没有连接则会返回EAGAIN误[^1]。 - 使用select等I/O多路复用机制可以有效地管理非阻塞套接字的事件[^2]。 §§相关问题§§ 1. 如何设置套接字为非阻塞模式? 2. 除了select,还有哪些I/O多路复用机制可以用于处理非阻塞套接字? 3. 如何增加进程的文件描述符数量限制? 4. accept函数返回EAGAIN误的其他可能原因是什么? [^1]: 参考UNIX网络编程:套接字联网API(卷1) [^2]: 参考Linux编程接口</think>### 网络编程中accept函数报"Resource temporarily unavailable"的分析与解决 #### 问题原因分析 当accept函数返回`EAGAIN`或`EWOULDBLOCK`误(对应误信息"Resource temporarily unavailable")时,通常由以下原因导致: 1. **非阻塞套接字模式**(最常见原因) 当监听套接字被设置为非阻塞模式(`O_NONBLOCK`)时,如果没有待处理的连接请求,accept会立即返回此误而非阻塞等待。数学描述:设套接字状态为$S$,当$S \in \{ \text{非阻塞}, \text{无待处理连接} \}$时,触发此误。 2. **连接队列耗尽** 当已完成连接队列(completed connection queue)为空时,系统无法立即提供新连接。队列长度由`backlog`参数(如`listen(fd, backlog)`)和系统限制(`net.core.somaxconn`)共同决定。 3. **资源限制** 系统级限制可能包括: - 进程文件描述符上限(`ulimit -n`) - 系统全局文件描述符上限(`/proc/sys/fs/file-max`) - 内存资源不足(OOM killer干预) #### 解决方案步骤 1. **检查套接字阻塞模式** 使用`fcntl`检查标志位: ```c int flags = fcntl(listen_fd, F_GETFL, 0); if (flags & O_NONBLOCK) { // 套接字处于非阻塞模式 } ``` **解决方案**: - 若需阻塞模式:`fcntl(listen_fd, F_SETFL, flags & ~O_NONBLOCK)` - 若需保持非阻塞:结合I/O多路复用(select/poll/epoll) 2. **优化连接队列** (1) 增大`listen()`的backlog值(建议≥128) (2) 提升系统限制(需root权限): ```bash sysctl -w net.core.somaxconn=1024 # 临时生效 echo "net.core.somaxconn=1024" >> /etc/sysctl.conf # 永久生效 ``` 3. **检查资源限制** (1) 查看进程限制: ```c #include <sys/resource.h> struct rlimit lim; getrlimit(RLIMIT_NOFILE, &lim); // 获取文件描述符限制 ``` (2) 提升限制(代码中): ```c lim.rlim_cur = 10240; // 建议值 setrlimit(RLIMIT_NOFILE, &lim); ``` 4. **正确的非阻塞模式处理** 使用epoll监控监听套接字: ```c struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; // 边缘触发模式 epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev); while (1) { int nready = epoll_wait(epfd, events, MAX_EVENTS, -1); for (int i = 0; i < nready; i++) { if (events[i].data.fd == listen_fd) { // 确认有连接到达后再调用accept int client_fd = accept(listen_fd, ...); } } } ``` #### 关键注意事项 - 在非阻塞模式下调用accept时,必须确保: $$\exists c \in \text{CompletedQueue} \mid \text{accept}(c) \neq \emptyset$$ - Linux中`EAGAIN`和`EWOULDBLOCK`通常相同,但POSIX规定不同系统可能有差异 - 当使用`accept4()`时,可原子性设置非阻塞标志: `accept4(listen_fd, ..., SOCK_NONBLOCK);` #### 误处理最佳实践 ```c int client_fd = accept(listen_fd, ...); if (client_fd < 0) { switch (errno) { case EAGAIN: // 或EWOULDBLOCK // 等待I/O就绪后重试 break; case EMFILE: // 处理文件描述符耗尽 close_idle_connections(); // 自定义回收逻辑 break; default: perror("Fatal accept error"); exit(EXIT_FAILURE); } } ``` > **底层原理**:accept函数从已完成连接队列中取出连接。当该队列为空时,在阻塞模式下会休眠等待,在非阻塞模式下立即返回`EAGAIN`[^1]。连接队列长度由$min(\text{backlog}, \text{somaxconn})$决定,其中$\text{backlog}$是`listen()`参数,$\text{somaxconn}$是系统内核参数[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值