前言
qt、qml项目经常会涉及访问MySQL数据库、网络服务器,并且界面打开时的初始化过程就会涉及到链接Mysql、网络服务器获取数据,如果网络不通,卡个几十秒,会让用户觉得非常的不爽,本文从技术调研的角度讲解解决此类问题的socket编程方案。
调研场景一
目标:是否有成熟的第三方工具,可以通过后台命令行判断指定IP的某个端口是否在线。(其他文章分享有多线程启动第三方工具并获取结果的方法,不会导致界面卡顿)
经过调研,发现有两个工具初步满足需求:
nmap工具
格式:nmap -p [端口] [IP地址],如下所示
nmap -p 3306 192.168.1.123
如上效果图,内容较为详细,但不管端口是否在线,都花费5秒钟才能输出结果,比较影响界面的体验。
nc工具
格式:nc -zv [IP地址] [端口] -w 1 -w1是1秒钟超时
nc -zc 192.168.1.123 3306 -w1
如上效果图,加了超时参数-w1之后,确实是1秒中之内出结果,并且succeeded表示端口在线,但是当使用多线程获取此命令行返回的结果时,发现结果是空的,是在国产系统(统信UOS和麒麟kylin系统上进行的测试),然后在后台命令行进行重定向>1.txt,并且cat 1.txt时,发现输出结果也是空的,网上查资料说可能是按照的是特殊的版本,输出结果无法重定向(没有深究,因为可以调研socket编程来实现)。
socket设置超时
1、QT的QsqlDatabase
首先尝试了QsqlDatabase的超时设置方法setConnectOptions("CONNECT_TIMEOUT=1"),代码执行时提示无效。
2、原生socket的setsockopt
当无法链接上指定IP的端口时,3秒才返回,接收数据被阻塞了。
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <ctime>
#define qint64 std::time_t
qint64 getcurtime(){
// 获取当前时间戳(秒级)
std::time_t timestamp = std::time(nullptr);
return timestamp;
}
bool checkPortOpen(const std::string& ip, int port, int timeout = 1) {
qint64 bgtime=getcurtime();
// 创建一个socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
std::cerr << "Socket creation failed!" << std::endl;
return false;
}
// 设置超时参数
struct timeval tv;
tv.tv_sec = timeout; // 超时1秒
tv.tv_usec = 0;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
// 设置目标地址
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = inet_addr(ip.c_str());
// 尝试连接指定的IP和端口
int result = connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
if (result == -1) {
qint64 endtime=getcurtime();
std::cout << " checksqlonline use time:" << endtime-bgtime << std::endl;
close(sock); // 连接失败,关闭socket
return false;
}
qint64 endtime=getcurtime();
std::cout << " checksqlonline use time:" << endtime-bgtime << std::endl;
close(sock); // 连接成功,关闭socket
return true;
}
int main(int argc, char **argv) {
if (argc==1){
printf("请传入参数:IP");
return -1;
}
std::string ip = argv[1]; // 替换为你要检测的IP
int port = 3306; // MySQL的默认端口3306
if (checkPortOpen(ip, port)) {
std::cout << "Port " << port << " on IP " << ip << " is open!" << std::endl;
} else {
std::cout << "Port " << port << " on IP " << ip << " is closed!" << std::endl;
}
return 0;
}
3、select异步socket
使用了异步模式,1秒以内返回,达到了优化的效果。
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <cstring>
#include <ctime>
#define qint64 std::time_t
qint64 getcurtime(){
// 获取当前时间戳(秒级)
std::time_t timestamp = std::time(nullptr);
return timestamp;
}
bool checkPortOpen(const std::string& ip, int port, int timeout_sec = 1) {
qint64 bgtime=getcurtime();
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
std::cerr << "Error creating socket." << std::endl;
return false;
}
// Set socket to non-blocking mode
fcntl(sockfd, F_SETFL, O_NONBLOCK);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &server_addr.sin_addr);
// Start connecting to the server (this will be non-blocking)
int connect_result = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
// Check if the connection was successful or pending
if (connect_result < 0) {
if (errno != EINPROGRESS) {
std::cerr << "Connection error: " << strerror(errno) << std::endl;
close(sockfd);
return false;
}
}
// Use select() to set a timeout for the connection
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(sockfd, &writefds);
struct timeval timeout;
timeout.tv_sec = timeout_sec;
timeout.tv_usec = 0;
int select_result = select(sockfd + 1, nullptr, &writefds, nullptr, &timeout);
if (select_result > 0) {
// Check if the socket is writable (connection succeeded)
int so_error;
socklen_t len = sizeof(so_error);
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) {
// Port is open
qint64 endtime=getcurtime();
std::cout << " checksqlonline use time:" << endtime-bgtime << std::endl;
close(sockfd);
return true;
}
}
qint64 endtime=getcurtime();
std::cout << " checksqlonline use time:" << endtime-bgtime << std::endl;
// If select result is <= 0, either timed out or failed to connect
close(sockfd);
return false;
}
int main(int argc, char **argv) {
if (argc==1){
printf("请传入参数:IP");
return -1;
}
std::string ip = argv[1]; // 替换为你要检测的IP
int port = 3306; // MySQL的默认端口3306
if (checkPortOpen(ip, port)) {
std::cout << "Port " << port << " on IP " << ip << " is open!" << std::endl;
} else {
std::cout << "Port " << port << " on IP " << ip << " is closed!" << std::endl;
}
return 0;
}