2016-04-14 68 views
0

我正在构建一个套接字客户端,我需要为协议本身(缺少答案等)实现连接,读取,写入和超时超时。套接字连接从分离的线程中止

我正在考虑在分离的线程中使用一个简单的计时器,该计时器将在每个事务中启动,然后在事务完成时取消。这种方法将用于使用不同超时的协议控制。

为了测试我做了如下简单的代码:

#include <string> 
#include <sstream> 
#include <map> 
#include <iostream> 
#include <cstring> 
#include <thread> 

#ifdef _WIN32 
#include <io.h> 
#include <winsock2.h> 
#include <ws2tcpip.h> 
#include <Windows.h> 
#else 
#include <unistd.h> 
#include <sys/socket.h> 
#include <netdb.h> 
#include <sys/types.h> 
#endif 

#include <stdio.h> 
#include <stdlib.h> 

bool timerOn = false; 
int currentSocket = 0; 


void Timer(int seconds) 
{ 
    int tick = seconds; 

    while (tick > 0) 
    { 
     std::this_thread::sleep_for(std::chrono::seconds(1)); 
     tick--; 
    } 

    if (timerOn) 
     close(currentSocket); 
} 

void StartTimer(int seconds) 
{ 
    timerOn = true; 
    std::thread t(&Timer, seconds); 
    t.detach(); 
} 

void StopTimer() 
{ 
    timerOn = false; 
} 


void Connect(std::string address, int port) 
{ 
    struct addrinfo hints; 
    struct addrinfo *result = NULL; 
    struct addrinfo *rp = NULL; 
    int sfd, s; 

    std::memset(&hints, 0, sizeof(struct addrinfo)); 

    hints.ai_family = AF_UNSPEC;  /* Allow IPV4 or IPV6 */ 
    hints.ai_socktype = SOCK_STREAM;  
    hints.ai_flags = 0; 
    hints.ai_protocol = 0;    

    std::string portStr; 
    portStr = std::to_string(port); 

    s = getaddrinfo(address.c_str(), portStr.c_str(), &hints, &result); 

    if (s != 0) 
    { 
     std::stringstream ss; 
     ss << "Cannot resolve hostname " << address << gai_strerror(s); 
     throw std::runtime_error(ss.str()); 
    } 

    for (rp = result; rp != NULL; rp = rp->ai_next) 
    { 
     sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 

     if (sfd == -1) 
      continue; 

     StartTimer(10); 
     int sts = connect(sfd, rp->ai_addr, rp->ai_addrlen); 
     StopTimer(); 

     if (sts == 0) 
      break; 

     close(sfd); 
    } 

    freeaddrinfo(result); /* Object no longer needed */ 

    if (rp == NULL) 
    { 
     std::stringstream ss; 
     ss << "Cannot find server address at " << address << " port " << port; 
     throw std::runtime_error(ss.str()); 
    } 

    currentSocket = sfd; 
} 


int main() 
{ 
    try 
    { 
     Connect("192.168.0.187", 9090); 
     std::cout << "Connected to server. Congrats!!!" << std::endl; 
    } 
    catch (std::exception& ex) 
    { 
     std::cout << "Error connecting to server. Aborting." << std::endl; 
     std::cout << ex.what() << std::endl; 
    } 
} 

关闭定时器插座不取消“连接”的操作,迫使其与错误中止。我也试过shutdown(sfd, SHUT_RDWR);没有成功...

我的方法无效吗?为什么它不起作用?

如何强制connect中止与分离线程错误?

+0

并非所有平台都允许通过简单地关闭套接字来中断connect()。 –

+0

@RemyLebeau我知道没有人会这样做。它有时可能发生运气,但几乎不可能保证。(仅仅使用常规的'socket','connect','close' API,它实际上是无法完成的。您需要一个具有附加功能的API,例如原子“连接然后解锁”功能。) –

+0

@DavidSchwartz Windows允许通过关闭套接字来中止连接()。 –

回答

4

关闭定时器上的套接字不取消'连接'操作,强制它中止并出错。

哇!你绝对不能这样做。在关闭套接字时,无法知道该线程实际上在connect(而不是要拨打connect)时被阻止。释放一个线程中的资源,而另一个线程正在或可能正在使用它,这是一种灾难。

想象一下这种情况:

  1. 线程是要叫connect,所以安排超时。

  2. 超时到期并且套接字已关闭。

  3. 某个库中的线程创建了一个新的套接字,由于某种原因它自己使用了套接字描述符。

  4. 即将调用connect的线程最终被调度并调用connect - 连接库的套接字!灾害。

你有两个选择:

  1. 使用非阻塞connect操作。

  2. 使用类似信号的东西来中断调用connect的线程。

但我不知道为什么你很烦。为什么你需要中止连接?如果连接在超时之前没有成功,则需要执行其他操作,请继续操作。

+0

“*为什么你在打扰*” - 因为没有超时,'connect()'可能需要很长时间才会退出并显示错误。取决于到达目的地所需的跳数,回答可能需要一段时间来发现这种或那种方式。与现代网络一样快,它们仍然不是即时的。如果你在暂停后不中止它,它可能会在稍后回来并说“噢,我终于做到了”,但你已经继续前进了。 –

+0

@RemyLebeau那么是什么?为什么这是一个问题?即使你放弃它,它仍然可以成功 - 事实上,它可能已经成功了,你还没有收到最后一包这么说(或者另一方还没有收到它,但不可撤销地会收到它然后发送您收到的回复,完成连接尝试)。所以对于您将放弃已经(或稍后)成功的连接的可能性,您无能为力。 –

+0

即使它在较低级别成功,较高级别的套接字也将被中止/关闭,因此任何后续流量都会因重置响应而失败。 –