Linux C套接字选项SO

您所在的位置:网站首页 recv超时返回值 Linux C套接字选项SO

Linux C套接字选项SO

2023-10-02 23:58| 来源: 网络整理| 查看: 265

SO_RCVTIMEO, SO_SNDTIMEO介绍

套接字选项SO_RCVTIMEO: 用来设置socket接收数据的超时时间; 套接字选项SO_SNDTIMEO: 用来设置socket发送数据的超时时间;

比如,一般情况下,调用accept/connect/send/recv, 进程会阻塞,但是如果对端异常,进行可能无法正常退出等待。如何让这些调用自动定时退出? 可以使用诸如alarm定时器、I/O复用设置定时器,还可以使用socket编程里函数级别的socket套接字选项SO_RCVTIMEO和SO_SNDTIMEO,仅针对与数据接收和发送相关,而无需设置专门的信号捕获函数。

能够作用的系统调用包括:send、sendmsg、recv、recvmsg、accept、connect。

系统调用 有效选项 系统调用超时后的行为 send SO_SNDTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK sendmsg SO_SNDTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK recv SO_RCVTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK recvmsg SO_RCVTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK accept SO_RCVTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK connect SO_SNDTIMEO 返回-1,设置errno为EINPROGRESS 注意: EAGAIN通常和EWOULDBLOCK是同一个值; SO_RCVTIMEO, SO_SNDTIMEO不要求系统调用对应fd是非阻塞(nonblocking)的,但是使用了该套接字选项的sock fd,会成为nonblocking(即使之前是blocking)的。参见man手册ERRORS EAGAIN/EWOULDBLOCK的描述;

man send关于EAGAIN / EWOULDBLOCK描述:

EAGAIN or EWOULDBLOCK The socket is marked nonblocking and the requested operation would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.

示例1:设置connect超时时间

根据系统调用accept的返回值,以及errno判断超时时间是否已到,从而决定是否开始处理超时定时任务。

客户端程序:超时连接服务器

/** * 客户端程序 * 连接服务器,超时报错、返回 * build: * $ gcc timeout_connect.c */ #include #include #include #include #include #include #include #include #include #include #include /* 超时连接 */ int timeout_connect (const char *ip, int port, int time) { int ret = 0; struct sockaddr_in servaddr; printf("client start...\n"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, ip, &servaddr.sin_addr); servaddr.sin_port = htons(port); int sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd >= 0); /* 通过选项SO_RCVTIMEO和SO_SNDTIMEO设置的超时时间的类型时timeval, 和select系统调用的超时参数类型相同 */ struct timeval timeout; timeout.tv_sec = time; timeout.tv_usec = 0; socklen_t len = sizeof(timeout); ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len); if (ret == -1) { perror("setsockopt error"); return -1; } if ((ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) < 0) { /* 超时对于errno 为EINPROGRESS. 下面条件如果成立,就可以处理定时任务了 */ if (errno == EINPROGRESS) { perror("connecting timeout, process timeout logic"); return -1; } perror("error occur when connecting to server\n"); } return sockfd; } int main(int argc, char *argv[]) { if (argc = 0) { close(sockfd); } exit(1); } } int main() { struct sockaddr_in servaddr, cliaddr; int listenfd; signal(SIGINT, sig_func); printf("server start...\n"); if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } sockfd = listenfd; int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { perror("setsocketopt error"); exit(1); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; // servaddr.sin_addr.s_addr = INADDR_ANY; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(8001); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind error"); exit(1); } if (listen(listenfd, 5) < 0) { perror("listen error"); exit(1); } char buf[1024]; socklen_t clilen = sizeof(cliaddr); int connfd; if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) { perror("accept error"); exit(1); } printf("input a line string: \n"); int nbytes; while (fgets(buf, sizeof(buf), stdin)) { nbytes = send(connfd, buf, strlen(buf), 0); if (nbytes < 0) { perror("send error"); break; } else if (nbytes == 0) { } printf("send: %s\n", buf); } close(connfd); close(listenfd); return 0; } 客户端

设置10秒超时,接收服务器数据。

客户端10秒以内,接收到服务器数据,则直接打印;超过10秒,就报错退出。

/** * 客户端程序 * 示例:超时接收服务器数据,超时时间例程中设置为10秒 * 编译: $ gcc timeout_recv_client.c -o client * 运行方式: * 如本地运行(对应服务器实际监听的IP地址和端口号) $ ./client 127.0.0.1 8001 */ #include /* See NOTES */ #include #include #include #include #include #include #include int timeout_recv(int fd, char *buf, int len, int nsec) { struct timeval timeout; timeout.tv_sec = nsec; timeout.tv_usec = 0; printf("timeout_recv called, timeout %d seconds\n", nsec); if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { perror("setsockopt error"); exit(1); } int n = recv(fd, buf, len, 0); return n; } int main(int argc, char *argv[]) { if (argc != 3) { printf("usage: %s \n", argv[0]); } char *ip = argv[1]; uint16_t port = atoi(argv[2]); printf("client start..\n"); printf("connect to %s:%d\n", ip, port); int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, ip, &servaddr.sin_addr); servaddr.sin_port = htons(port); int connfd; if ((connfd = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0) { perror("connect error"); exit(1); } printf("success to connect server %s:%d\n", ip, port); printf("wait for server's response\n"); char buf[100]; while (1) { int nread; nread = timeout_recv(sockfd, buf, sizeof(buf), 10); if (nread < 0) { perror("timeout_recv error"); exit(1); } else if (nread == 0) { shutdown(sockfd, SHUT_RDWR); break; } write(STDOUT_FILENO, buf, nread); } return 0; }

客户端运行结果: 可以看到,超过10秒后,客户端自动退出程序,而不再阻塞在recv。

$ ./client 127.0.0.1 8001 client start.. connect to 127.0.0.1:8001 success to connect server 127.0.0.1:8001 wait for server's response timeout_recv called, timeout 10 seconds hello # 服务器端用户输入数据 timeout_recv called, timeout 10 seconds nihao # 服务器端用户输入数据 timeout_recv called, timeout 10 seconds timeout_recv error: Resource temporarily unavailable # 服务器端超时未输入数据,客户端程序运行结束 参考

[1]游双. Linux高性能服务器编程[M]. 机械工业出版社, 2013.

本文来自博客园,作者:明明1109,转载请注明原文链接:https://www.cnblogs.com/fortunely/p/15055618.html



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3