【问题标题】:socket() returning file descriptor 1 inside std::threadsocket() 在 std::thread 中返回文件描述符 1
【发布时间】:2023-04-10 18:15:01
【问题描述】:

当我在std::thread() 内调用socket() 时,它返回一个套接字描述符1。调用std::cout 而不是将用于终端的文本发送到服务器。当我在调用socket() 之前添加cout << ""; 时,会创建stdin/out/err 的描述符并且socket() 返回3。

来自http://www.cplusplus.com/reference/iostream/cout/

就静态初始化顺序而言,cout 保证在不迟于第一次构造 ios_base::Init 类型的对象时正确构造和初始化,其中包含 <iostream> 至少算作一次初始化具有静态持续时间的此类对象。

默认情况下,coutstdout 同步(请参阅ios_base::sync_with_stdio)。

std::cout 应该已经初始化并同步到标准输出,这就解释了为什么std::cout 将消息发送到服务器而不是终端。

我想知道调用 std::thread() 是否会关闭新线程中的 stdin/out/err 描述符,或者这些描述符是否不存在于线程中,因为线程不是由终端或 initd 创建的?

我正在使用 GCC 4.8.2 在 RHEL 6.4 上运行。我在下面包含了我的客户端代码,附加的 cout << ""; 已注释掉以确保完整性。

客户端.cpp:

#include "Client.hpp"

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <sstream>
#include <functional>

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>

using namespace std;

Client::Client( const string& hostname, const string& port ) {
    this->hostname = hostname;
    this->port = port;
}

Client::~Client() { close( fd ); }

void Client::operator()() {
    struct addrinfo hints;
    memset( &hints, 0, sizeof( struct addrinfo ) );

    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;

    struct addrinfo *result;
    int ret = getaddrinfo( hostname.c_str(), port.c_str(), &hints, &result );
    if( ret != 0) {
        cerr << "getaddrinfo failed: " << gai_strerror( ret ) << endl;
        return;
    }

    // cout << ""; // prevents socket() from returning 1 and redefining cout

    struct addrinfo *rp = NULL;
    for( rp = result; rp != NULL; rp = rp->ai_next ) {
        fd = socket( rp->ai_family, rp->ai_socktype, rp->ai_protocol );
        if( fd == -1 ) {
            continue;   /* Error */
        }
    
        if( connect( fd, rp->ai_addr, rp->ai_addrlen ) != -1) {
            break;      /* Success */
        }

        close( fd );        /* Try again */
    }

    if( rp == NULL ) {
        cerr << "Failed to connect to " << hostname << ":" << port << endl;
        return;
    }

    freeaddrinfo( result );

    cout << "Starting echo client" << endl;

    int i = 0;
    do {
        stringstream ss;
        ss << "Thread " << this_thread::get_id() << ": Message #" << ++i;
        string str = ss.str();

        _send( str.c_str() );
        string msg = _recv();

        //cout << "Thread " << this_thread::get_id() << " received message: " << msg << endl;
    } while ( i < 10 );

    cout << "Stopping echo client" << endl;

    close( fd );
}

string Client::_recv( ) const {
    const int BUF_SIZE = 1024;
    char* buf = ( char * ) malloc( sizeof( char) * BUF_SIZE );
    memset( buf, '\0', sizeof( char ) * BUF_SIZE );

    int bytes = recv( fd, buf, BUF_SIZE, 0 );
    if( bytes < 0 ) {
        perror( "recv failed" );
    }

    string msg = string( buf );

    free( buf );

    return msg;
}

void Client::_send( const string buf ) const {
    if( send( fd, buf.c_str(), buf.length(), 0 ) < 0 ) {
        perror( "send failed" );
    }
}
void usage() {
    cerr << "Usage: client <hostname> <port>" << endl;
    cerr << "   hostname - server name listening for incoming connctions" << endl;
    cerr << "   port - internet port to listen for connections from" << endl;
}

int main( int argc, char* argv[] ) {
    if( argc < 3 ) {
        cerr << "Not enough arguments!" << endl;
        usage();
        return EXIT_FAILURE;
    }

    vector<thread> clients;
    for( int i = 0; i < 1; i++ ) {
        clients.push_back( thread( ( Client( argv[1], argv[2] ) ) ) );
    }

    for_each( clients.begin(), clients.end(), mem_fn( &thread::join ) );

    return EXIT_SUCCESS;
}

客户端.hpp:

#ifndef __CLIENT_HPP__
#define __CLIENT_HPP__

#include <string>

class Client {
    private:
        int fd;
        std::string hostname;
        std::string port;
        std::string _recv( ) const;
        void _send( const std::string buf ) const;

    public:
        Client( const std::string& hostname, const std::string& port);
        ~Client();
        void operator()();
};

#endif

【问题讨论】:

  • 请同时显示 .hpp 文件
  • 这是标准的Rule of Three 违规。您的班级拥有在析构函数中销毁的资源 (fd),您应该实施或禁止复制/移动构造/分配。另外,对freeaddrinfo的调用是在错误条件中的return之后进行的,导致连接不成功时内存泄漏。

标签: multithreading sockets c++11


【解决方案1】:

如果您不小心关闭了stdout,通常会发生这种情况,即存在虚假的close(1);。那么 FD 将合法地排在第一位。这很可能在程序的其他地方。

我已经遇到过几次了,通常使用gdb 找到它并在close() 上设置断点。

你的析构函数看起来很可疑:

Client::~Client() { close( fd ); }

您不应该在构造函数中将fd 设置为-1,并小心地将fd 设置为-1,并且在fd==-1 的情况下不关闭它?当前创建一个Client 并销毁它会关闭一个随机fd。

【讨论】:

  • 所有代码都贴在问题中。我正在使用g++ -std=gnu++11 -pthread -ggdb -Wall -c Client.cpp; g++ -std=gnu++11 -pthread -ggdb -Wall -o client Client.o; 编译并使用client localhost 9850 启动程序;`。我没有使用 gdb 来启动程序,但我确实在启用调试信息的情况下编译它。
  • 将 fd 初始化为 -1 似乎已经成功了。我还在析构函数中添加了if( fd != -1 ) { close( fd ); },以防止关闭随机描述符。我将在我调用close() 的任何地方添加fd = -1;,以消除在对象被破坏时关闭随机描述符。
  • 对于它的价值,虚假关闭不是“代码中的其他地方”。它在main 中的for 循环结束时,客户端对象在被复制到新对象以供新线程使用后超出范围。
猜你喜欢
  • 1970-01-01
  • 2023-04-07
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 2021-01-09
  • 1970-01-01
  • 2019-06-11
  • 1970-01-01
相关资源
最近更新 更多