【问题标题】:UDP socket in cpp loops for evercpp循环中的UDP套接字永远
【发布时间】:2015-10-27 02:52:44
【问题描述】:

我有一段代码在 c++ 中实现了多个线程,并且运行良好。其中一个线程是从 UDP 客户端接收消息的 UDP 服务器。所以太好了。

现在我想在不同的线程上实现 TCP 服务器,以便 UDP 客户端和 TCP 客户端都能够将消息发送到其正确的服务器(它们在不同的端口上运行)。这样做之后,UDP服务器会发疯......(我真的不知道如何解释坚果)。请尝试关注我:

最小代码:

// How to compile using mysql.h
// g++ -o aserver aserver.cpp $(mysql_config --libs) -lpthread
//
//// to operate with I/O functions
#include <iostream>
#include <fstream>
// to operate with strings
#include <string>
// to operate with string streams
#include <sstream>
// to opereta with time
#include <time.h>
// to operate with directories
#include <dirent.h>
// to operate with sleep function
#include <unistd.h>
// to operate with threads
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
// to operate with sockets
#include <sys/socket.h>
#include <sys/types.h>
// Defines the structure of the socket
#include <netinet/in.h>
// Uses memset to clear the structure
#include <string.h>
#include <cerrno>

using namespace std;

// **************************************************************
// * GLOBAL VARIABLES                                           *
// **************************************************************
int logto_id;
int udp_port;
int tcp_port;
int sock;

const int success = 0;
const int general_error = -1;
const string general_error_str = "Error";

void logto(string text, int debug_id) {

    int append_status;

    switch (debug_id) {
    case 1:
        cout << text + "\n";
        break;
    case 2:
        break;
    case 3:
        break;
    default:
        cout << "";
    }

}

int create_udp_socket() {

    // UDP Socket Variables
    unsigned int serverlen;
    sockaddr_in udpServer;
    int bind_status = 0;

    string function_name="create_udp_socket: ";

    /* Create the UDP socket */
    sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) {
        cout << function_name + "Could not create UDP socket...\n";
        return general_error;
    }

    /* Construct the server sockaddr_in structure */
    memset(&udpServer, 0, sizeof(udpServer));       /* Clear struct */
    udpServer.sin_family = AF_INET;                 /* Internet/IP */
    udpServer.sin_addr.s_addr = htonl(INADDR_ANY);  /* Any IP address */
    udpServer.sin_port = htons(udp_port);           /* server port */

    /* Bind the socket */
    serverlen = sizeof(udpServer);
    bind_status= bind(sock, (struct sockaddr *) &udpServer, serverlen);
    if (bind_status < 0) {
        cout << function_name + "Could not bind UDP socket...\n";
        return general_error;
    } else {
        cout << function_name + "UDP Socket created and binded...\n";
        return success;
    }

}

int create_tcp_socket() {

    // TCP Socket Variables
    unsigned int serverlen;
    sockaddr_in tcpServer;
    int bind_status = 0;
    int listen_status = 0;

    string function_name="create_tcp_socket: ";

    /* Create the TCP socket */
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0) {
        cout << function_name + "Could not create TCP socket...\n";
        return general_error;
    }

    /* Construct the server sockaddr_in structure */
    memset(&tcpServer, 0, sizeof(tcpServer));       /* Clear struct */
    tcpServer.sin_family = AF_INET;                 /* Internet/IP */
    tcpServer.sin_addr.s_addr = htonl(INADDR_ANY);  /* Any IP address */
    tcpServer.sin_port = htons(tcp_port);           /* server port */

    /* Bind the socket */
    serverlen = sizeof(tcpServer);
    bind_status = bind(sock, (struct sockaddr *) &tcpServer, serverlen);
    if (bind_status < 0) {
        cout << function_name + "Could not bind TCP socket...\n";
        return general_error;
    } else {
        cout << function_name + "TCP Socket created and binded...\n";

        /* Listen */
        listen_status = listen(sock,10);
        if (listen_status < 0) {
            cout << function_name + "Could not listen on the TCP socket...\n";
            return general_error;
        } else {
            cout << function_name + "TCP Socket listening...\n";
            return success;
        }

    }

}

void *thread_udp_server(void *arg) {
    // **************************************************************
    // * LOCAL VARIABLES                                            *
    // * we define this internal variables that before were Global  *
    // **************************************************************

    /* here we store the SQL INSERT query */
    string node_query;
    /* here we find the data to build the query 
     * this variable is always passed by reference to all the functions
     */
    string node_line;
    /* UDP Socket related variables */
    char udp_buffer[255];
    int received = 0;
    unsigned int echolen, clientlen;
    sockaddr_in udpClient;

    // Name of thread
    string thread_name = (char*)arg;

    // We start the whole thing ...
    if (create_udp_socket()==success) {

        /* Endless loop */
        //for(;;) {
        while(1) {
            logto(udp_buffer,logto_id);
            /* Receive a message from the client */
            clientlen = sizeof(udpClient);
            received = recvfrom(sock, udp_buffer, 255, 0, (struct sockaddr *) &udpClient, &clientlen);

            if (received < 0) {

                logto(thread_name + " Failed to receive message",logto_id);
                std::cout << "Something went wrong! errno " << errno << ": ";
                std::cout << strerror(errno) << std::endl;

            } else {

                logto("\n---------\n" + thread_name,logto_id);
                /* We now copy the content of the buffer into 'node_line' */
                node_line=udp_buffer;

                logto(thread_name + node_line,logto_id);

            }

        }

    } else {

        logto(thread_name + " Could not bring up UDP socket...",logto_id);
        std::cout << "Something went wrong! errno " << errno << ": ";
        std::cout << strerror(errno) << std::endl;
        return NULL;

    }

}

void *thread_tcp_server(void *arg) {
    // **************************************************************
    // * LOCAL VARIABLES                                            *
    // * we define this internal variables that before were Global  *
    // **************************************************************

    /* here we store the SQL INSERT query */
    string node_query;
    /* here we find the data to build the query 
     * this variable is always passed by reference to all the functions
     */
    string node_line;
    /* TCP Socket related variables */
    char tcp_buffer[255];
    int recTcp = 0;
    unsigned int echolen, clientlen;
    sockaddr_in tcpClient;

    // Name of thread
    string thread_name = (char*)arg;

    // We start the whole thing ...
    if (create_tcp_socket()==success) {

        /* Endless loop */
        for(;;) {
            logto(tcp_buffer,logto_id);
            /* Receive a message from the client */
            clientlen = sizeof(tcpClient);
            recTcp = accept(sock, (struct sockaddr *) &tcpClient, &clientlen);
            if (recTcp < 0) {

                logto(thread_name + " Failed to receive message",logto_id);
                std::cout << "Something went wrong! errno " << errno << ": ";
                std::cout << strerror(errno) << std::endl;

            } else {

                logto("\n---------\n" + thread_name,logto_id);
                /* We now copy the content of the buffer into 'node_line' */
                node_line=tcp_buffer;

                logto(thread_name + node_line,logto_id);

            }

        }

    } else {

        logto(thread_name + " Could not bring up TCP socket...",logto_id);
        std::cout << "Something went wrong! errno " << errno << ": ";
        std::cout << strerror(errno) << std::endl;

        return NULL;

    }

}

// -----------------
// - main function -
// -----------------
int main () {

    // **************************************************************
    // * VARIABLES                                          *
    // **************************************************************

    // Labels of the threads
    string label_udp = "UDP_thread";
    string label_tcp = "TCP_thread";

    // We define the threads...
    pthread_t udp_server_id=20;
    pthread_t tcp_server_id=50;

    udp_port = 10101;
    tcp_port = 10102;
    logto_id = 1;

    // **************************************************************
    // * START                                                      *
    // **************************************************************

    if ( pthread_create( &udp_server_id, NULL, thread_udp_server, (void*) label_udp.c_str()) ) {
        logto("Error creating thread_udp_server...",logto_id);
        return general_error;
    }

    if ( pthread_create( &tcp_server_id, NULL, thread_tcp_server, (void*) label_tcp.c_str()) ) {
        logto("Error creating thread_tcp_server...",logto_id);
        return general_error;
    }

    if ( pthread_join ( udp_server_id, NULL ) ) {
        logto("UDP_thread couldn't join the main thread...",logto_id);
        return general_error;
    }

    if ( pthread_join ( tcp_server_id, NULL ) ) {
        logto("TCP_thread couldn't join the main thread...",logto_id);
        return general_error;
    }

}

启动程序后,errno 如下,取决于打开的套接字:

TCP 正常!:

./aserver
create_tcp_socket: TCP Socket created and binded...
create_tcp_socket: TCP Socket listening...

create_udp_socket: Could not bind UDP socket...
UDP_thread Could not bring up UDP socket...
Something went wrong! errno 22: Invalid argument

UDP 好的!:

./aserver
create_udp_socket: UDP Socket created and binded...
create_tcp_socket: TCP Socket created and binded...
create_tcp_socket: Could not listen on the TCP socket...
TCP_thread Could not bring up TCP socket...
Something went wrong! errno 95: Operation not supported

还有第三种情况,即 UDP 被启动(TCP 套接字保持关闭)并且出于某种原因,我让这些消息在整个窗口中滚动...

./aserver
create_tcp_socket: Could not bind TCP socket...
TCP_thread Could not bring up TCP socket...
Something went wrong! errno create_udp_socket: UDP Socket created and binded...

22: UDP_thread Failed to receive message
Something went wrong! errno 107: Transport endpoint is not connectedInvalid argument

UDP_thread Failed to receive message
Something went wrong! errno 107: Transport endpoint is not connected

UDP_thread Failed to receive message
Something went wrong! errno 107: Transport endpoint is not connected

UDP_thread Failed to receive message
Something went wrong! errno 107: Transport endpoint is not connected

但是,如果我注释掉其中一个线程(TCP 或 UDP),剩下的一个可以正常工作...

底线:我无法让两个线程(UDP 和 TCP)同时存在...

谁能给我一个提示。我真的很困惑为什么两个线程同时破坏我的应用程序...... :-(

提前致谢,

卢卡斯

【问题讨论】:

  • 请提供一个Minimal, Complete, and Verifiable example 来演示实际存在的问题。当您创建两个套接字时,您可能会丢弃一些东西,但我们在您显示的代码中看不到这一点。而且,当套接字函数失败时,您应该记录它们报告的错误代码。这将告诉你为什么他们失败了。您可以从errno(或Windows 上的WSAGetLastError())获取错误代码。
  • 我已经有好几年没有编写可以使用 TCP 或 UDP 的服务器和客户端应用程序了。我不是 100% 确定,因为它已经很长时间了,但我认为这与他们通过 winsock 库使用的端口寻址到实际硬件有关。我不确定您是否可以同时运行两者,除非您明确为每种类型指定不同的端口(您声明它们是),您的服务器和客户端也必须在 TCP 或 UDP 之间匹配。所以我认为这可能是这里没有看到的实现问题。
  • 您需要担心的另一件事是您编写的应用程序可能正常工作,但如果您在同一台机器上或通过局域网运行它,则需要通过防火墙进行相当多的设置让他们互相交流。您可能必须允许特定端口通过防火墙,您可能必须配置路由器以进行端口转发和端口触发等。记住这些!
  • @LucasAimaretto:请不要要求人们从外部站点下载代码。如果代码太大而无法直接提出您的问题,则它不够小。在您处理此问题时,让我们从简单的事情开始 - 当recvfrom() 失败时,errno 实际报告的错误代码是什么?
  • 您的错误报告存在主要问题。 errno 可以随着每个系统调用而改变。 Ergo 当系统调用返回 -1 时,您必须做的第一件事是报告errno。这应该在您的 logto() 方法中。每次使用errno 打印时,值和消息都是可疑的。使用正确的错误处理代码再试一次。

标签: c++ multithreading udp tcpclient udpclient


【解决方案1】:

看起来您正在为两个线程使用相同的全局套接字。

int sock;

如果create_udp_socket 函数首先运行,它创建的套接字将被create_tcp_socket 覆盖,反之亦然。

可能的解决方案,要么使用两个全局套接字:

int tcp_sock;
int udp_sock;

或(更好)使create_xxx_socket 函数将套接字直接返回给调用者,避免使用全局变量。

这是后者的一个示例(为清楚起见,省略了错误处理)。

int create_tcp_socket()
{
    int sock;

    /* Create the TCP socket */
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    /* Bind and listen... */

    return sock;
}

TCP 线程会像这样调用create_tcp_socket

void *thread_tcp_server(void *arg)
{
    /* ... */

    int sock = create_tcp_socket();
    if(sock < 0)
    {
        logto(thread_name + " Could not bring up TCP socket...", logto_id);
        return NULL;
    }

    /* Socket created, start accept'ing connections */
}

由于多种原因,全局变量不好。

特别是在多线程代码中,将数据(在这种情况下为sock)保持私有意味着对所有权的怀疑更少。

代码可能会假设谁拥有全局变量,但随着程序规模的扩大,这在实践中变得无法管理。

将此与从其中一种创建方法返回sock 进行对比;很容易看出,最初,sock 归创建方法所有。当创建方法返回时,套接字的所有权被传递给调用者。访问套接字的函数或线程永远不会超过一个,因此对套接字的并发访问永远不会成为问题。

知道谁拥有数据还可以在不再需要资源时更轻松地释放或取消分配资源。在这种情况下,如果一个服务器线程要退出,它 - 作为套接字的所有者 - 将负责在退出时关闭它。而且它可以安全地这样做,因为没有其他人可以使用套接字。

【讨论】:

  • 嗨@Carsten,感谢您的提示。这太明显了,我没有看到。感谢您澄清它。现在,我了解了这两个变量(int tcp_sock 和 udp_sock)的用法。我认为,我也知道你的想法是让函数返回套接字 - 从概念上讲 - 但真的不知道如何实现它......你有一个例子或至少一个链接可以阅读一些东西吗?顺便说一句,我尝试同时使用全局变量,这解决了我的问题!谢谢!!
  • 卢卡斯,很高兴听到这个消息。我已经更新了我的答案以显示如何从创建方法中返回一个套接字
  • 嗨@Carsten,只是提到我确实尝试了你的建议(将套接字返回给调用者),它的工作就像一个魅力......谢谢。我同意你的观点:这种方式不仅更优雅,而且更简单、更安全。谢谢你的提示!卢卡斯
猜你喜欢
  • 2011-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-30
相关资源
最近更新 更多