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狀態
#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: [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 <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: [email protected]
> 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