Linux C语言 connect 的超时设置(含源码和例程)
0x01 关键步骤和相关函数
网络编程中默认情况下进入connect函数,会一直等待连接结束。超时等待设置关键在于
1、将socket置为非阻塞后
2、设定超时等待时间
3、时间结束后读取socket状态,进行判断
1、设置socket为非阻塞
记录下两种设置socket为非阻塞方式,分别是fcntl() 和 ioctl() 两个函数
fcntl()
#include <fcntl.h>
#include <unistd.h>
/*********************************************************************
* Function : fcntl
* Description : 根据文件描述符操作文件特性
* Parameter :
* @fd 文件描述符
* @cmd 操作命令:
* F_DUPFD : 复制文件描述词。
* FD_CLOEXEC : 设置close-on-exec标志
* F_GETFD : 读取文件描述词标志
* F_SETFD : 设置文件描述词标志
* F_GETFL : 读取文件状态标志
* F_SETFL : 设置文件状态标志
* ...
* @arg 供命令使用的参数
*
* Return : int
*
* Usage :
* int fcntl(int fd, int cmd);
* int fcntl(int fd, int cmd, long arg);
* int fcntl(int fd, int cmd, struct flock *lock);
*
* 参考 man 或:
* https://blog.csdn.net/weixin_34362875/article/details/86340074
*
*********************************************************************/
int fcntl(int fd, int cmd, ... /* arg */ );
ioctl()
#include <sys/ioctl.h>
/*********************************************************
* Function : ioctl
* Description : 设备驱动程序中对设备的I/O通道进行管理的函数
* Parameter :
* @fd 文件描述符
* @request 操作命令:
* FIONBIN : 设置/ 清除非阻塞I/O 标志
* FIOASYNC : 设置/ 清除信号驱动异步I/O 标志
* FIONREAD : 获取接收缓存区中的字节数
* FIOSETOWN : 设置文件的进程ID 或进程组ID
* FIOGETOWN : 获取文件的进程ID 或进程组ID
* ...
* @arg 供命令使用的参数
*
* Return : int
*
* 参考 man 或:
* https://www.cnblogs.com/tdyizhen1314/p/4896689.html
* 上文详细介绍了该函数的用途
* 以及request对应参数所需要提供的arg类型
*********************************************************/
int ioctl(int fd, unsigned long request, .../* arg */);
2、超时等待
select() 多路复用
#include <sys/select.h>
include <sys/time.h>
include <sys/types.h>
include <unistd.h>
/
Function : select
Description : 同步I/O多路复用
Parameter :
@nfds 文件描述符+1
@readfds 可读文件描述符词组
@writefds 可写文件描述符词组
@exceptfds 例外文件描述符词组
@timeout 等待时间
Return : int
参考 man 或:
https://blog.csdn.net/lucykingljj/article/details/43198889
/
int select(int nfds, fd_set readfds, fd_set writefds,
fd_set exceptfds, struct timeval timeout);
/*
相关函数
/
void FD_ZERO(fd_set set); // 清除描述符词组set的全部位
void FD_CLR(int fd, fd_set set); // 清除描述符词组set中相关fd的位
void FD_SET(int fd, fd_set set); // 设置描述符词组set中相关fd的位
int FD_ISSET(int fd, fd_set *set); // 测试描述符词组set中相关fd的位是否为真
#### 3、获取socket状态
```C
#include <sys/types.h>
#include <sys/socket.h>
/*********************************************************
* Function : getsockopt
* Description : 返回指定socket的状态
* Parameter :
* @sockfd socket的文件描述符
* @level 操作的网络层, 一般设置为SOL_SOCKET
* @optname 操作的选项:
* SO_DEBUG 打开或关闭排错模式
* SO_REUSEADDR 允许在bind ()过程中本地地址可重复使用
* SO_TYPE 返回socket 形态.
* SO_ERROR 返回socket 已发生的错误原因
* SO_DONTROUTE 送出的数据包不要利用路由设备来传输.
* SO_BROADCAST 使用广播方式传送
* SO_SNDBUF 设置送出的暂存区大小
* SO_RCVBUF 设置接收的暂存区大小
* SO_KEEPALIVE 定期确定连线是否已终止.
* SO_OOBINLINE 当接收到OOB 数据时会马上送至标准输入设备
* SO_LINGER 确保数据安全且可靠的传送出去.
* @optval 保存结果的内存地址
* @optlen optval空间的大小
*
* Return : 成功则返回0, 若有错误则返回-1, 错误原因存于errno
* errno:
* EBADF 参数s 并非合法的socket 处理代码
* ENOTSOCK 参数s为一文件描述词, 非socket
* ENOPROTOOPT 参数optname指定的选项不正确
* EFAULT 参数optval指针指向无法存取的内存空间
*
* 参考 man 或:
* http://www.cnblogs.com/dpf-learn/p/6124170.html
*
*********************************************************/
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
0x02 例程代码
本例程为一份检测本地与目标端口连接是否活跃的程序,只封装了建立一个普通TCP带有超时参数的客户端connect函数。
头文件 - Network.h
/************************************************************************* File Name: Network.h Author: WangMinghang Mail: hackxiaowang@qq.com Blog: https://www.wangsansan.com Created Time: 2019年04月29日 星期一 10时32分21秒 ************************************************************************/
ifndef USER_NETWORK_H
define USER_NETWORK_H
/****
Function : create_connect
Description : 创建一个TCP连接的客户端socket, 并设置超时时间
Parameter :
@host 主机域名或IP地址
@port 目标端口
@s 连接超时时间, 单位: 秒
Return :
连接成功 - 返回socket描述符
连接失败 - 返回值小于0
-1 : 创建socket失败
-2 : 连接超时
****/
int create_connect(const char *host, int port, int s);
endif
> main 主程序 - Network_test.c
```C
/*************************************************************************
> File Name: Network_test.c
> Author: WangMinghang
> Mail: hackxiaowang@qq.com
> Blog: https://www.wangsansan.com
> Created Time: 2019年04月29日 星期一 10时32分21秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "Network.h"
#define TIMEOUT 3
#define SERVER_PORT 80
#define SERVER_HOST "www.wangsansan.com"
void delay_ms(int timeout);
int main(int argc, char **argv)
{
int sock_fd = -1;
while(1)
{
sock_fd = create_connect(SERVER_HOST, SERVER_PORT, TIMEOUT);
if(sock_fd <= 0){
printf("Connect failed - %d \n", sock_fd);
}else{
close(sock_fd);
printf("Connect success - %d \n", sock_fd);
}
/* 在网络通畅时, 如果不加延时会消耗资源 */
delay_ms(1000);
}
}
/* 毫秒定时器 */
void delay_ms(int timeout)
{
struct timeval timer;
timer.tv_sec = 0; // 0秒
timer.tv_usec = 1000*timeout; // 1000us = 1ms
select(0, NULL, NULL, NULL, &timer);
}
源代码 - Network.c
/*************************************************************************
> File Name: Network.c
> Author: WangMinghang
> Mail: hackxiaowang@qq.com
> Blog: https://www.wangsansan.com
> Created Time: 2019年04月29日 星期一 10时32分21秒
************************************************************************/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "Network.h"
int socket_create();
int socket_timeout(int sockfd, int s);
int socket_connect(int sockfd, const char* server, int port);
int create_connect(const char *host, int port, int s)
{
int re = -1;
int sock_fd = -1;
unsigned long ul;
// 创建 socket
sock_fd = socket_create();
if(sock_fd <= 0){
return -1;
}
// 设置非阻塞
ul = 1;
ioctl(sock_fd, FIONBIO, &ul);
// 连接 socket
re = socket_connect(sock_fd, host, port);
if(re == 1){
// 设置为阻塞
ul = 0;
ioctl(sock_fd, FIONBIO, &ul);
return sock_fd;
}
// 设置超时时间
re = socket_timeout(sock_fd, s);
if(re <= 0){
close(sock_fd);
return -2;
}
// 设置为阻塞
ul = 0;
ioctl(sock_fd, FIONBIO, &ul);
return sock_fd;
}
int socket_timeout(int sockfd, int s)
{
int re = 0;
fd_set set;
struct timeval tm;
int len;
int error = -1;
tm.tv_sec = s;
tm.tv_usec = 0;
FD_ZERO(&set);
FD_SET(sockfd, &set);
re = select(sockfd + 1, NULL, &set, NULL, &tm);
if(re > 0){
len = sizeof(int);
// 获取socket状态
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
if(error == 0){
re = 1;
}else{
re = -3;
}
}else{
re = -2;
}
return re;
}
int socket_connect(int sockfd, const char *server, int port)
{
int re = -1;
struct hostent *host;
struct sockaddr_in cliaddr;
// 域名解析
host = gethostbyname(server);
if(host == NULL){
printf("gethostbyname(%s) error:%s\n", server, strerror(errno));
re = -1;
return re;
}
// 填充socket的IP与端口
bzero(&cliaddr, sizeof(struct sockaddr));
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(port);
cliaddr.sin_addr = *((struct in_addr *)host->h_addr);
// 客户端连接
re = connect(sockfd, (struct sockaddr *)&cliaddr, sizeof(struct sockaddr));
if(re >= 0){
return 1;
}
return re;
}
int socket_create()
{
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
perror("create socket error");
return -1;
}
return sockfd;
}
关键位置都有注释说明,非阻塞设置方式使用的是ioctl()函数,在连接完成之后又将socket设置为阻塞,有兴趣的可以在后续代码中添加send和recv的疯转,对socket进行操作。
参考:
https://blog.csdn.net/wangyifei0822/article/details/2171314