Linux网络编程errno的EAGAIN和EINTR

在Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。EAGAIN和 EWOULDBLOCK等效!

从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。例如,以O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。又例如,当一个系统调用(比如fork)因为没有足够的资源(比如虚拟内存)而执行失败,返回EAGAIN提示其再调用一次(也许下次就能成功)。

Linux - 非阻塞socket编程处理EAGAIN错误

在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable,errno代码为11(EAGAIN),这是什么意思?

这表明你在非阻塞模式(比如epoll的ET模式下设置recv,对应的fd文件描述符设置为非阻塞)下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,不用管它,下次循环接着recv就可以。对非阻塞socket而言,EAGAIN不是一种错误。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。

EINTR错误:

慢系统调用(slow system call):此术语适用于那些可能永远阻塞的系统调用。永远阻塞的系统调用是指调用有可能永远无法返回,多数网络支持函数都属于这一类。如:若没有客户连接到服务器上,那么服务器的accept调用就没有返回的保证。

  • EINTR错误的产生:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。例如:在socket服务器端,设置了信号捕获机制,有子进程,当在父进程阻塞于慢系统调用时由父进程捕获到了一个有效信号时,内核会致使accept返回一个EINTR错误(被中断的系统调用)。

当碰到EINTR错误的时候,可以采取有一些可以重启的系统调用要进行重启,而对于有一些系统调用是不能够重启的。例如:accept、read、write、select、和open之类的函数来说,是可以进行重启的。不过对于套接字编程中的connect函数我们是不能重启的,若connect函数返回一个EINTR错误的时候,我们不能再次调用它,否则将立即返回一个错误。针对connect不能重启的处理方法是,必须调用select来等待连接完成。

对于socket接口(指connect/send/recv/accept..等等后面不重复,不包括不能设置非阻塞的如select),在阻塞模式下有可能因为发生信号,返回EINTR错误,由用户做重试或终止。

但是,在非阻塞模式下,是否出现这种错误呢?
对此,重温了系统调用、信号、socket相关知识,得出结论是:不会出现。

首先,

  1. 信号的处理是在用户态下进行的,也就是必须等待一个系统调用执行完了才会执行进程的信号函数,所以就有了信号队列保存未执行的信号;
  2. 用户态下被信号中断时,内核会记录中断地址,信号处理完后,如果进程没有退出则重回这个地址继续执行;

socket接口是一个系统调用,也就是即使发生了信号也不会中断,必须等socket接口返回了,进程才能处理信号。
也就是,EINTR错误是socket接口主动抛出来的,不是内核抛的。socket接口也可以选择不返回,自己内部重试之类的..

那阻塞的时候socket接口是怎么处理发生信号的?

举例socket接口,例如recv接口会做2件事情,

  1. 检查buffer是否有数据,有则复制清除返回;
  2. 没有数据,则进入睡眠模式,当超时、数据到达、发生错误则唤醒进程处理;

socket接口的实现都差不了太多,抽象说,

  1. 资源是否立即可用,有则返回
  2. 没有,就等...

对于

1.这个时候不管有没信号,也不返回EINTR,只管执行自己的就可以了
2.采用睡眠来等待,发生信号的时候进程会被唤醒,socket接口唤醒后检查有无未处理的信号(signal_pending)会返回EINTR错误。

所以
socket接口并不是被信号中断,只是调用了睡眠,发生信号睡眠会被唤醒通知进程,然后socket接口选择主动退出,这样做可以避免一直阻塞在那里,有退出的机会。非阻塞时不会调用睡眠。
 

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
适合初学者,如 /******* 服务器程序 (server.c) ************/ #include <stdlib.h>; #include <stdio.h>; #include <errno.h>; #include <string.h>; #include <netdb.h>; #include <sys/types.h>; #include <netinet/in.h>; #include <sys/socket.h>; int main(int argc, char *argv[]) { int sockfd,new_fd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int sin_size,portnumber; char hello[]="Hello! Are You Fine?\n"; if(argc!=2) { fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]); exit(1); } if((portnumber=atoi(argv[1]))<0) { fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]); exit(1); } /* 服务器端开始建立socket 描述符 */ if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) { fprintf(stderr,"Socket error:%s\n\a",strerror(errno)); exit(1); } /* 服务器端填充 sockaddr 结构 */ bzero(&server_addr,sizeof(struct sockaddr_in)); server_addr.sin_family=AF_INET; server_addr.sin_addr.s_addr=htonl(INADDR_ANY); server_addr.sin_port=htons(portnumber); /* 捆绑sockfd 描述符 */ if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))== -1) { fprintf(stderr,"Bind error:%s\n\a",strerror(errno)); exit(1); } /* 监听sockfd 描述符 */ if(listen(sockfd,5)==-1) { fprintf(stderr,"Listen error:%s\n\a",strerror(errno)); exit(1); } while(1) { /* 服务器阻塞,直到客户程序建立连接 */ sin_size=sizeof(struct sockaddr_in); if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size ))==-1) { fprintf(stderr,"Accept error:%s\n\a",strerror(errno)); exit(1); } fprintf(stderr,"Server get connection from %s\n", inet_ntoa(client_addr.sin_addr)); if(write(new_fd,hello,strlen(hello))==-1) { fprintf(stderr,"Write Error:%s\n",strerror(errno)); exit(1); } /* 这个通讯已经结束 */ close(new_fd); /* 循环下一个 */ } close(sockfd); exit(0); } /******* 客户端程序 client.c ************/ #include <stdlib.h>; #include <stdio.h>; #include <errno.h>; #include <string.h>; #include <netdb.h>; #include <sys/types.h>; #include <netinet/in.h>; #include <sys/socket.h>; int main(int argc, char *argv[]) { int sockfd; char buffer[1024]; struct sockaddr_in server_addr; struct hostent *host; int portnumber,nbytes; if(argc!=3) { fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]); exit(1); } if((host=gethostbyname(argv[1]))==NULL) { fprintf(stderr,"Gethostname error\n"); exit(1); } if((portnumber=atoi(argv[2]))<0) { fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]); exit(1); } /* 客户程序开始建立 sockfd 描述符 */ if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) { fprintf(stderr,"Socket Error:%s\a\n",strerror(errno)); exit(1); } /* 客户程序填充服务端的资料 */ bzero(&server_addr,sizeof(server_addr)); server_addr.sin_family=AF_INET; server_addr.sin_port=htons(portnumber); server_addr.sin_addr=*((struct in_addr *)host->;h_addr); /* 客户程序发起连接请求 */ if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr) )==-1) { fprintf(stderr,"Connect Error:%s\a\n",strerror(errno)); exit(1); } /* 连接成功了 */ if((nbytes=read(sockfd,buffer,1024))==-1) { fprintf(stderr,"Read Error:%s\n",strerror(errno)); exit(1); } buffer[nbytes]='\0'; printf("I have received:%s\n",buffer); /* 结束通讯 */ close(sockfd); exit(0); }
线程概念 什么是线程 LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下) 进程:独立地址空间,拥有PCB 线程:也有PCB,但没有独立的地址空间(共享) 区别:在于是否共享地址空间。 独居(进程);合租(线程)。 Linux下: 线程:最小的执行单位 进程:最小分配资源单位,可看成是只有一个线程的进程。 Linux内核线程实现原理 类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切。 1. 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone 2. 从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的 3. 进程可以蜕变成线程 4. 线程可看做寄存器和栈的集合 5. 在linux下,线程最是小的执行单位;进程是最小的分配资源单位 察看LWP号:ps –Lf pid 查看指定线程的lwp号。 三级映射:进程PCB --> 页目录(可看成数组,首地址位于PCB中) --> 页表 --> 物理页面 --> 内存单元 参考:《Linux内核源代码情景分析》 ----毛德操 对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。 但!线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。 实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。 如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。 因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。 线程共享资源 1.文件描述符表 2.每种信号的处理方式 3.当前工作目录 4.用户ID和组ID 5.内存地址空间 (.text/.data/.bss/heap/共享库) 线程非共享资源 1.线程id 2.处理器现场和栈指针(内核栈) 3.独立的栈空间(用户空间栈) 4.errno变量 5.信号屏蔽字 6.调度优先级 线程优、缺点 优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便 缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好 优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。 线程控制原语 pthread_self函数 获取线程ID。其作用对应进程中 getpid() 函数。 pthread_t pthread_self(void); 返回值:成功:0; 失败:无! 线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现 线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同) 注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。 pthread_create函数 创建一个新线程。 其作用,对应进程中fork() 函数。 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失败均直接返回错误号。 参数: pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t; 参数1:传出参数,保存系统为我们分配好的线程ID 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。 参数4:线程主函数执行期间所使用的参数。 在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。star
Linux错误码65280对应的错误号(errno)是126,表示执行一个命令时发生了错误。以下是常见的Linux错误号及其对应的错误: 1. EPERM (1):操作不允许 2. ENOENT (2):文件或目录不存在 3. ESRCH (3):没有这样的进程 4. EINTR (4):系统调用被中断 5. EIO (5):输入/输出错误 6. ENXIO (6):没有这样的设备或地址 7. E2BIG (7):参数列表太长 8. ENOEXEC (8):执行格式错误 9. EBADF (9):文件描述符无效 10. ECHILD (10):没有这样的子进程 11. EAGAIN (11):资源暂时不可用 12. ENOMEM (12):内存不足 13. EACCES (13):权限不足 14. EFAULT (14):错误的地址 15. ENOTBLK (15):块设备必须使用块IO 16. EBUSY (16):设备或资源忙 17. EEXIST (17):文件已存在 18. EXDEV (18):跨文件系统链接 19. ENODEV (19):操作不支持设备 20. ENOTDIR (20):不是目录 21. EISDIR (21):是目录 22. EINVAL (22):无效的参数 23. ENFILE (23):文件打开太多 24. EMFILE (24):文件描述符打开太多 25. ENOTTY (25):不是终端设备 26. ETXTBSY (26):文本文件忙 27. EFBIG (27):文件太大 28. ENOSPC (28):没有空间 29. ESPIPE (29):无效的seek 30. EROFS (30):只读文件系统 31. EMLINK (31):链接太多 32. EPIPE (32):捕获信号管道 33. EDOM (33):数学参数超出定义域 34. ERANGE (34):数学结果不可表示 35. EDEADLK (35):资源死锁避免 36. ENAMETOOLONG (36):文件名太长 37. ENOLCK (37):没有可用的记录锁 38. ENOSYS (38):函数不支持 39. ENOTEMPTY (39):目录不为空 40. ELOOP (40):太多的符号链接 41. ENOMSG (42):没有消息的标识符 42. EIDRM (43):标识符已删除 43. ECHRNG (44):通道范围不正确 44. EL2NSYNC (45):Level 2不同步 45. EL3HLT (46):Level 3被挂起 46. EL3RST (47):Level 3重置 47. ELNRNG (48):Link number超出范围 48. EUNATCH (49):Protocol driver不可用 49. ENOCSI (50):没有CSI结构可用 50. EL2HLT (51):Level 2被挂起 51. EBADE (52):无效的交换 52. EBADR (53):无效的请求描述符 53. EXFULL (54):交换空间已满 54. ENOANO (55):没有对应的自动识别对象 55. EBADRQC (56):无效的请求描述符或参数 56. EBADSLT (57):Slot不存在 57. EBFONT (59):无效字体文件格式 58. ENOSTR (60):设备不是流 59. ENODATA (61):没有数据可用 60. ETIME (62):计时器已过期 61. ENOSR (63):没有记录可用 62. ENONET (64):网络不可用 63. ENOPKG (65):包没有安装 64. EREMOTE (66):对象是远程的 65. ENOLINK (67):链路不存在 66. EADV (68):广告错误 67. ESRMNT (69):Srmount错误 68. ECOMM (70):通信错误 69. EPROTO (71):协议错误 70. EMULTIHOP (72):多跳跃I / O 71. EDOTDOT (73):RFS特定错误 72. EBADMSG (74):错误的消息 73. EOVERFLOW (75):值太大以便于定义 74. ENOTUNIQ (76):名称不唯一 75. EBADFD (77):文件描述符在错误状态 76. EREMCHG (78):远程地址已更改 77. ELIBACC (79):无法访问共享库 78. ELIBBAD (80):共享库损坏 79. ELIBSCN (81):没有共享库文本段 80. ELIBMAX (82):共享库表已满 81. ELIBEXEC (83):无法执行共享库 82. EILSEQ (84):无效的或不完整的多字节序列 83. ERESTART (85):无需重新启动系统调用 84. ESTRPIPE (86):管道流不支持 85. EUSERS (87):太多用户 86. ENOTSOCK (88):套接字操作的目标不是套接字 87. EDESTADDRREQ (89):需要目标地址 88. EMSGSIZE (90):消息太长 89. EPROTOTYPE (91):错误的协议类型 90. ENOPROTOOPT (92):协议不可用 91. EPROTONOSUPPORT (93):协议不支持 92. ESOCKTNOSUPPORT (94):套接字类型不支持 93. EOPNOTSUPP (95):操作不支持 94. EPFNOSUPPORT (96):协议族不支持 95. EAFNOSUPPORT (97):地址族不支持 96. EADDRINUSE (98):地址已在使用中 97. EADDRNOTAVAIL (99):不能分配所需的地址 98. ENETDOWN (100):网络已关闭 99. ENETUNREACH (101):网络不可达 100. ENETRESET (102):网络连接已重置 101. ECONNABORTED (103):连接被中止 102. ECONNRESET (104):连接被重置 103. ENOBUFS (105):没有缓冲区可用 104. EISCONN (106):套接字已连接 105. ENOTCONN (107):套接字未连接 106. ESHUTDOWN (108):不能执行发送,套接字已关闭 107. ETOOMANYREFS (109):太多参考:无法分配请求的资源 108. ETIMEDOUT (110):连接超时 109. ECONNREFUSED (111):连接被拒绝 110. EHOSTDOWN (112):主机已关闭 111. EHOSTUNREACH (113):主机不可达 112. EALREADY (114):操作已经在进行中 113. EINPROGRESS (115):操作正在进行中 114. ESTALE (116):Stale NFS文件句柄 115. EUCLEAN (117):结构取消 116. ENOTNAM (118):不是XENIX命名文件 117. ENAVAIL (119):No XENIX semaphores available 118. EISNAM (120):Is a named type file 119. EREMOTEIO (121):远程I / O错误 120. EDQUOT (122):Quota exceeded 121. ENOMEDIUM (123):没有中介信息 122. EMEDIUMTYPE (124):中介错误 123. ECANCELED (125):操作被取消 124. ENOKEY (126):没有KEY值 125. EKEYEXPIRED (127):KEY已过期 126. EKEYREVOKED (128):KEY已被撤销 127. EKEYREJECTED (129):KEY被拒绝 128. EOWNERDEAD (130):所有者已死 129. ENOTRECOVERABLE (131):无法恢复

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值