网络编程中默认情况下进入connect函数,会一直等待连接结束。超时等待设置关键在于
1、将socket置为非阻塞后
2、设定超时等待时间
3、时间结束后读取socket状态,进行判断
记录下两种设置socket为非阻塞方式,分别是fcntl() 和 ioctl() 两个函数
fcntl()
#include
#include
/*********************************************************************
* 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
/*********************************************************
* 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 */);
select() 多路复用
#include
#include
#include
#include
/*********************************************************
* 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的位是否为真
#include
#include
/*********************************************************
* 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);
本例程为一份检测本地与目标端口连接是否活跃的程序,只封装了建立一个普通TCP带有超时参数的客户端connect函数。
头文件 - Network.h
/*************************************************************************
> File Name: Network.h
> Author: WangMinghang
> Mail: [email protected]
> 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
/*************************************************************************
> File Name: Network_test.c
> Author: WangMinghang
> Mail: [email protected]
> Blog: https://www.wangsansan.com
> Created Time: 2019年04月29日 星期一 10时32分21秒
************************************************************************/
#include
#include
#include
#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: [email protected]
> Blog: https://www.wangsansan.com
> Created Time: 2019年04月29日 星期一 10时32分21秒
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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