关于winsock2中的connect函数

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于winsock2中的connect函数相关的知识,希望对你有一定的参考价值。

如果其输入参数中的IP不存在,connect函数就会一直等待。怎样可以超时跳出等待,继续执行下面的代码,并提示连接失败?

Socket中如何设置连接超时
AntGhazi/2001.12.14 主页:antghazi.yeah.net

把CSDN与中文yahoo翻了底朝天,也没找到如何设置socket的连接超时的满意方法,问此问题的兄弟已有一大堆,这里偶就讲一下win下如何设置socket的connect超时。
设置connect的超时很简单,CSDN上也有人提到过使用select,但却没有一个令人满意与完整的答案。偶所讲的也正是select函数,此函数集成在winsock1.1中,简单点讲,"作用使那些想避免在套接字调用过程中被锁定的应用程序,采取一种有序的方式,同时对多个套接字进行管理" (《Windows网络编程技术》原话)。使用方法与解释请见《Windows网络编程技术》。
在使用此函数前,需先将socket设置为非锁定模式,这样,在connect时,才会立马跳过,同时,通常也会产生一个WSAEWOULDBLOCK错误,这个错误没关系。再执行select则是真正的超时。

WSADATA wsd;
SOCKET cClient;
int ret;
struct sockaddr_in server;
hostent *host=NULL;

if(WSAStartup(MAKEWORD(2,0),&wsd))return 0;
cClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(cClient==INVALID_SOCKET)return 0;
//set Recv and Send time out
int TimeOut=6000; //设置发送超时6秒
if(::setsockopt(cClient,SOL_SOCKET,SO_SNDTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
return 0;

TimeOut=6000;//设置接收超时6秒
if(::setsockopt(cClient,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
return 0;

//设置非阻塞方式连接
unsigned long ul = 1;
ret = ioctlsocket(cClient, FIONBIO, (unsigned long*)&ul);
if(ret==SOCKET_ERROR)return 0;

//连接
server.sin_family = AF_INET;
server.sin_port = htons(25);
server.sin_addr .s_addr = inet_addr((LPCSTR)pSmtp);
if(server.sin_addr.s_addr == INADDR_NONE)return 0;

connect(cClient,(const struct sockaddr *)&server,sizeof(server));

//select 模型,即设置超时
struct timeval timeout ;
fd_set r;

FD_ZERO(&r);
FD_SET(cClient, &r);
timeout.tv_sec = 15; //连接超时15秒
timeout.tv_usec =0;
ret = select(0, 0, &r, 0, &timeout);
if ( ret <= 0 )

::closesocket(cClient);
return 0;

//一般非锁定模式套接比较难控制,可以根据实际情况考虑 再设回阻塞模式
unsigned long ul1= 0 ;
ret = ioctlsocket(cClient, FIONBIO, (unsigned long*)&ul1);
if(ret==SOCKET_ERROR)
::closesocket (cClient);
return 0;


--------------------------------------------------------------------------------------------------------------

LINUX下的方法:

在阻塞套接字的一般情况下,connect ()直到客户端对SYN消息的ACK消息到达之前才会返回。使connect()调用具有超时机制的一个方法是让套接字成为非阻塞的套接字体,然后用select()来等待它完成。
[code:1:7901c37cf2]
s = socket(AF_INET, SOCK_STREAM, 0);
//下面获取套接字的标志
if ((flags = fcntl(s, F_GETFL, 0)) < 0)
//错误处理


//下面设置套接字为非阻塞
if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0)
//错误处理


if ((retcode = connect(s, (struct sockaddr*)&peer, sizeof(peer)) &&
errno != EINPROGRESS)
//因为套接字设为NONBLOCK,通常情况下,连接在connect()返回
//之前是不会建立的,因此它会返回EINPROGRESS错误,如果返回
//任何其他错误,则要进行错误处理


if (0 == retcode) //如果connect()返回0则连接已建立
//下面恢复套接字阻塞状态
if (fcntl(s, F_SETFL, flags) < 0)
//错误处理


//下面是连接成功后要执行的代码

exit(0)


FD_ZERO(&rdevents);
FD_SET(s, &rdevents); //把先前的套接字加到读集合里面
wrevents = rdevents; //写集合
exevents = rdevents; //异常集合

tv.tv_sec = 5; //设置时间为5秒
tv_tv_usec = 0;

retcode = select(s+1, &rdevents, &wrevents, &exevents, &tv);
if (retcode < 0) //select返回错误???
//错误处理

else if (0 == retcode) //select 超时???
//超时处理

esle
//套接字已经准备好
if (!FD_ISSET(s, &rdevents) && !FD_ISSET(s, &wrevents))
//connect()失败,进行错处理


if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
//getsockopt()失败,进行错处理


if (err != 0)
//connect()失败,进行错处理


//到这里说明connect()正确返回
//下面恢复套接字阻塞状态
if (fcntl(s, F_SETFL, flags) < 0)
//错误处理


//下面是连接成功后要执行的代码

exit(0)

参考资料:http://blog.bcchinese.net/v45072501/archive/2006/06/05/73642.aspx

参考技术A 多线程

Winsock 连接失败并显示 WSAEFAULT |仅在 Windows 11 上出现错误

【中文标题】Winsock 连接失败并显示 WSAEFAULT |仅在 Windows 11 上出现错误【英文标题】:Winsock connect fails with WSAEFAULT | Error on Windows 11 only 【发布时间】:2021-12-19 17:14:07 【问题描述】:

我的代码在从 XP 到 10 的任何 Windows 上都能完美运行。 现在我在Win11中第一次测试了我的代码, 并且 connect() 函数失败并出现错误 10014 WSAEFAULT:

地址错误。 系统在尝试使用调用的指针参数时检测到无效的指针地址。如果应用程序传递了无效的指针值,或者缓冲区的长度太小,则会发生此错误。例如,如果参数的长度(即 sockaddr 结构)小于 sizeof(sockaddr)。

但是,当我使用调试器检查时,sockaddr_in 结构似乎传递正确:

connect(hSocket, (sockaddr*)(&InSockAddr), sizeof(InSockAddr))

我正在使用 Visual C++ 2015 编译器。

以下是相关代码的片段:

#include <WinSock2.h>

class CConnection

   public:
    static bool         bWinsockInitialized;
    SOCKET              hSocket = INVALID_SOCKET;
    sockaddr_in         sockAddr;
         
    bool                Create();
    bool                InitializeWinsock();
    bool                Connect(sockaddr_in &InSockAddr);
;   

CConnection sckt_Main;
sockaddr_in g_sockAddr;


void main()

    if (!sckt_Main.Create())
    
        // Error: Unable to create connection socket
        return;
    

    g_sockAddr.sin_family = AF_INET;

    // Get IP address from string. 
    // If address is a DNS, HostInfo->h_addr will contain resolved IP address.
    // Save host information into HostInfo struct:
    hostent* HostInfo = gethostbyname("127.0.0.1");

    //Error checking:
    if (!HostInfo) 
        return;
    

    assert((sizeof(g_sockAddr.sin_addr)) >= HostInfo->h_length);

    //Copy the resolved IP address into our sockAddr structure: 
    memcpy(&g_sockAddr.sin_addr, HostInfo->h_addr, HostInfo->h_length);

    //Saves connection port
    g_sockAddr.sin_port = htons(atoi("2405"));
    
    sckt_Main.Connect(g_sockAddr);



bool CConnection::Create()

    if (!InitializeWinsock()) 
        return false;
    
        
    hSocket = socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
        
    if (this->hSocket == INVALID_SOCKET) 
        return false;
        
    return true;



bool CConnection::InitializeWinsock()

    WSADATA wsaData;
    if (!WSAStartup(MAKEWORD(2, 2), &wsaData)) 
       bWinsockInitialized = true;
     
    else bWinsockInitialized = false;
        
    return bWinsockInitialized;



bool CConnection::Connect(sockaddr_in &InSockAddr)


    // If no error occurs, connect returns zero. 
    // Otherwise, it returns SOCKET_ERROR, and a specific error code can be retrieved by calling WSAGetLastError.
    if (connect(hSocket, (sockaddr*)(&InSockAddr), sizeof(InSockAddr)) == 0)
    
       // Connect SUCCESS
       return true;
    
    else 
       
       // !!! connect error !!!
       int err = WSAGetLastError();
       return false;
    

【问题讨论】:

你有一个缓冲区溢出。试试assert((sizeof (g_sockAddr.sin_addr)) &gt;= HostInfo-&gt;h_length) atoi 的调用也毫无意义。要么它是一个常量,在这种情况下使用数字文字而不是字符串,要么它是一个只有在运行时才知道值的字符串,在这种情况下使用像strtol这样的合格转换函数。 @Flavio:这可能不会导致您今天的错误,但是当您从gethostbyname() 获取 IPv6 地址时,您的代码将失败,并将顺序从 IPv4-first 更改为 IPv6-first 是正是操作系统更新所期望的那种变化。 清除 last-error 值以确保 WSAEFAULT 来自 connect 而不是来自以前的某个函数可能也是值得的。 socket(AF_UNSPEC, ...) 无效。您必须指定 AF_INET (IPv4) 或 AF_INET6 (IPv6)。如果你想要一个同时支持 IPv4 和 IPv6 的套接字,你需要创建一个dual-stack socket,即关闭IPV6_V6ONLY 选项的AF_INET6 套接字。 【参考方案1】:

如 cmets 中所述,您的代码不支持 IPv6。 Windows 11 默认启用 IPv6。

您应该将您的代码更新为与 IPv4 与 IPv6 无关。

见Microsoft Docs。

注意微软很久以前就停止提供checkv4.exe,但如果我根据你的代码运行它,我会得到:

sockaddr_in : use sockaddr_storage instead, or use sockaddr_in6 in addition for IPv6 support

AF_INET : use AF_INET6 in addition for IPv6 support

gethostbyname : use getaddrinfo instead

hostent : use addrinfo instead

【讨论】:

根据您的建议,我将已弃用的 gethostbyname() 替换为 getaddrinfo()。随后我编辑connect() 以从addrinfo 获取值,这样:connect(hSocket, AddrInfo-&gt;ai_addr, AddrInfo-&gt;ai_addrlen)。现在代码可以正常工作,从 WinXP 到 Win11。谢谢。 按照 Microsoft 文档,在 getaddrinfo() 之后,我还编辑了 socket() 函数以从 addrinfo 获取地址族,而不是使用 AF_UNSPEC。这也是解决错误的关键部分。

以上是关于关于winsock2中的connect函数的主要内容,如果未能解决你的问题,请参考以下文章

关于Qt中的信号槽连接connect函数

关于QT中的signal 和 slot 中带参数问题

调用未定义的函数 mysql_connect

PHP中的MYSQL常用函数(php下操作数据库必备)

如何修复此错误:调用未定义的函数 odbc_connect() [重复]

如何从文件标签中的hardisk获取那些选定文件并将其传递到另一个php函数中