第14章——高级IO函数
1.套接字超时
套接字IO函数设置超时的方法有三种:
(1)调用alarm。
(2)select
(3)使用SO_RECTIMEO和 SO_SNDTIMEO 选项
上面三种方法适用于输入输出操作(read , write, sendto , recvfrom ..)
(1)(2)适用于所有文件描述符,(3)适用于套接字
connect 内置超时时间很长(典型为75秒),select可用于connect的前提是对应套接字处于 非阻塞模式。(3)不适于connect。
2.一些设置超时示例
2.1 使用alarm
涉及信号就避免多线程,信号本身适中全局资源,所以要注意对其他信号使用的影响
int connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec) { Sigfunc *sigfunc; int n; sigfunc = Signal(SIGALRM, connect_alarm); if (alarm(nsec) != 0) err_msg("connect_timeo: alarm was already set"); if ( (n = connect(sockfd, saptr, salen)) < 0) { close(sockfd); if (errno == EINTR) errno = ETIMEDOUT; } alarm(0); /* turn off the alarm */ Signal(SIGALRM, sigfunc); /* restore previous signal handler */ return(n); } static void connect_alarm(int signo) { return; /* just interrupt the connect() */ }
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; Signal(SIGALRM, sig_alrm); while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); alarm(5); if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) { if (errno == EINTR) fprintf(stderr, "socket timeout\n"); else err_sys("recvfrom error"); } else { alarm(0); recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } } } static void sig_alrm(int signo) { return; /* just interrupt the recvfrom() */ }
2.2 使用select
int readable_timeo(int fd, int sec) { fd_set rset; struct timeval tv; FD_ZERO(&rset); FD_SET(fd, &rset); tv.tv_sec = sec; tv.tv_usec = 0; return(select(fd+1, &rset, NULL, NULL, &tv)); /* 4> 0 if descriptor is readable */ } /* end readable_timeo */
2.3 使用SO_RCVTIMEO
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); if (n < 0) { if (errno == EWOULDBLOCK) { fprintf(stderr, "socket timeout\n"); continue; } else err_sys("recvfrom error"); } recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } }
3. recv 和 send 函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len, int flags);
特别的是flags参数:
MSG_DONTROUTE:
和SO_DONTROUTE的套接字选项类似,SO_DONTROUTE是修改套接字到达长期有效,MSG_DONTROUTE是单次有效。
根据IPv4的正常机制,IP分组需要根据目的地址和路由表找到适当本地接口,如果无法确定本地接口,就会报错。
可以使用本选项,以绕过上述机制,强制从某个接口发出。路由守护进程就是这么做的。
MSG_DONTWAIT
临时打开非阻塞标志,进行IO,关闭非阻塞标志
MSG_OOB
让本次调用,用于接受或发送带外数据。
TCP连接上有一个字节可以作为带外数据发送。带外数据是加急数据。
MSG_PEEK
允许查看可读数据,且recv或recvfrom返回后,该数据仍然在缓冲区,不被丢弃。
MSG_WAITALL
通知内核,在尚未读到全部指定数量的数据前不返回。这样就可以简化readnzz
#define readn(fd, ptr, n) recv(fd, ptr, n, MSG_WAITALL)
即使这样,也有可能读到少的数据:(a)信号中断,(b)连接终止(c)套接字错误
4. writev 和 readv
ssize_t readv(int fd, const struct iovec *iov, int iovcnt); ssize_t writev(int fd, const struct iovec *iov, int iovcnt); struct iovec { void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ };
这两个函数的作用是:
集中写:将应用程序多个缓冲区的数据,集中提供给单个写操作。
分散读:将读操作的数据,分散到多个应用程序缓冲。
由于writev是原子操作,对于UDP而言,一次writev多个缓冲区数据,只发送一个UDP报文。
对于TCP而言,通过writev将多个缓冲区放到一个分组发出,避免触发 Nagle 算法。
5. 排队数据量
(1)使用非阻塞IO,可以避免没有数据时的阻塞。
(2)既想查看数据,又不想取出数据,可以用MSG_PEEK,又不希望数据没到达的阻塞,可以加MSG_DONTWAIT。对于TCP,MSG_PEEK读到的数据量和之后读到的数据量是可能不同,因为可能有新的数据来了,对于UDP,两次读到的数据量相同。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?