【问题标题】:TFTP Server - Issue With Threaded VersionTFTP 服务器 - 线程版本的问题
【发布时间】:2015-06-21 04:31:18
【问题描述】:

我创建了一个仅处理读取请求 (RRQ) 的简单 tftp 服务器。在我开始制作服务器的多线程版本之前,一切都运行良好。在应用程序中,我只是在主线程中接收请求,然后将请求转发到执行数据包分析的新线程。因此,我需要将套接字、接收到的数据包和包含客户端信息的 sockaddr_in 结构体转发给线程。话虽如此,我创建了一个结构来保存所有这些并将它们转发到 pthread。

我怀疑问题出在struct clientThread 初始化部分和转发部分;因为我确定connection_handler 内部处理的正确性。

注意:可以使用linux自带的tftp客户端进行测试。

这是我目前编写的代码(线程版)。请使用-pthread 标志编译它...

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>

#define TIMEOUT 5000
#define RETRIES 3

void *connection_handler(void *);

struct clientThread 
{
    int clientSock;
    char buffer[1024];  
    struct sockaddr_in client;
};

int main()
{
    char buffer[1024];
    int udpSocket, client_socket, nBytes;
    struct sockaddr_in serverAddr, client;
    socklen_t addr_size;

    udpSocket = socket(AF_INET, SOCK_DGRAM, 0);

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(69);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); 

    bind(udpSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr));

    while(1)
    {
        memset(buffer, 0, 1024);
        nBytes = recvfrom(udpSocket,buffer,1024,0,(struct sockaddr *)&client, &addr_size);

        // Creating a thread and passing the packet received, the socket and the sockaddr_in struct...
        pthread_t client_thread;
        struct clientThread clientT;
        strcpy(clientT.buffer,buffer);
        clientT.clientSock = udpSocket;
        clientT.client = client;
        pthread_create(&client_thread, NULL, connection_handler, &clientT);
    }

    return 0;
}


void* connection_handler (void *clientThreaded)
{
        char buffer[1024], filename[200], mode[20], *bufindex, opcode;

        struct clientThread *cthread = clientThreaded;
        int udpSocket = cthread->clientSock;
        strcpy(buffer, cthread->buffer);
        struct sockaddr_in client = cthread->client;

        bufindex = buffer;
        bufindex++;

        // Extracting the opcode from the packet...
        opcode = *bufindex++;

        // Extracting the filename from the packet...
        strncpy(filename, bufindex, sizeof(filename)-1);

        bufindex += strlen(filename) + 1;

        // Extracting the mode from the packet...
        strncpy(mode, bufindex, sizeof(mode)-1);

        // If we received an RRQ...
        if (opcode == 1)
        {
                puts("Received RRQ Request");
                struct timeval tv;
                tv.tv_sec = 5;
                char path[70] = "tmp/";
                char filebuf [1024];
                int count = 0, i;  // Number of data portions sent
                unsigned char packetbuf[1024];
                char recvbuf[1024];
                socklen_t recv_size;

                socklen_t optionslength = sizeof(tv);
                setsockopt(udpSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, optionslength);

                FILE *fp;
                char fullpath[200];
                strcpy(fullpath, path);
                strncat(fullpath, filename, sizeof(fullpath) -1);
                fp = fopen(fullpath, "r");
                if (fp == NULL)
                    perror("");

                memset(filebuf, 0, sizeof(filebuf));


                while (1)               
                {
                        int acked = 0;
                        int ssize = fread(filebuf, 1 , 512, fp);
                        count++;
                        sprintf((char *) packetbuf, "%c%c%c%c", 0x00, 0x03, 0x00, 0x00);
                        memcpy((char *) packetbuf + 4, filebuf, ssize);
                        packetbuf[2] = (count & 0xFF00) >> 8;
                        packetbuf[3] = (count & 0x00FF);

                        int len = 4 + ssize;

                        memset(recvbuf, 0, 1024);
                        printf("\nSending Packet #%i", count);
                        sendto(udpSocket, packetbuf, len, 0, (struct sockaddr *) &client, sizeof(client));

                        for (i=0; i<RETRIES; i++)
                        {
                            int result = recvfrom(udpSocket, recvbuf, 1024, 0, (struct sockaddr *) &client, &recv_size);

                                if ((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
                                {
                                    sendto(udpSocket, packetbuf, len, 0, (struct sockaddr *) &client, sizeof(client));
                                    printf("\nRetransmitting Packet #%i", count);
                                }

                                else if (result == -1)
                                {
                                   // Handle Error
                                }

                                else
                                {
                                    acked++;
                                        printf("\nReceived ACK For Data Packet #%i", count);
                                        break;
                                }
                    }


                        if (acked!=1)
                        {
                            puts("\nGave Up Transmission After 3 Retries");
                                break;
                        }

                        if (ssize != 512)
                            break;
                }
    }

    return 0;
}

这是我的非线程版本的代码...

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#define TIMEOUT 5000
#define RETRIES 3

int main()
{
    int udpSocket, nBytes;
    char buffer[1024], filename[200], mode[20], *bufindex, opcode;
    struct sockaddr_in serverAddr, client;
    struct sockaddr_storage serverStorage;
    socklen_t addr_size;

    udpSocket = socket(AF_INET, SOCK_DGRAM, 0);

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(69);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); 

    bind(udpSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr));

    while(1)
    {
        memset(buffer, 0, 1024);
        nBytes = recvfrom(udpSocket,buffer,1024,0,(struct sockaddr *)&client, &addr_size);
        printf("%s",buffer);
        bufindex = buffer;
        bufindex++;

        // Extracting the opcode from the packet...     
        opcode = *bufindex++;

        // Extracting the filename from the packet...
        strncpy(filename, bufindex, sizeof(filename)-1);

        bufindex += strlen(filename) + 1;

        // Extracting the mode from the packet...       
        strncpy(mode, bufindex, sizeof(mode)-1);

        // If we received an RRQ...
        if (opcode == 1)
        {
                puts("Received RRQ Request");
                struct timeval tv;
                tv.tv_sec = 5;
                char path[70] = "tmp/";
                char filebuf [1024];
                int count = 0, i;  // Number of data portions sent
                unsigned char packetbuf[1024];
                char recvbuf[1024];
                socklen_t recv_size;

                socklen_t optionslength = sizeof(tv);
                setsockopt(udpSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, optionslength);

                FILE *fp;
                char fullpath[200];
                strcpy(fullpath, path);
                strncat(fullpath, filename, sizeof(fullpath) -1);
                fp = fopen(fullpath, "r");
                if (fp == NULL)
                        perror("");

                 memset(filebuf, 0, sizeof(filebuf));


                while (1)
                {
                        int acked = 0;
                        int ssize = fread(filebuf, 1 , 512, fp);
                        count++;
                        sprintf((char *) packetbuf, "%c%c%c%c", 0x00, 0x03, 0x00, 0x00);
                        memcpy((char *) packetbuf + 4, filebuf, ssize);
                        packetbuf[2] = (count & 0xFF00) >> 8;
                        packetbuf[3] = (count & 0x00FF);

                        int len = 4 + ssize;

                        memset(recvbuf, 0, 1024);
                        printf("\nSending Packet #%i", count);
                        sendto(udpSocket, packetbuf, len, 0, (struct sockaddr *) &client, sizeof(client));

                        for (i=0; i<RETRIES; i++)
                        {
                                int result = recvfrom(udpSocket, recvbuf, 1024, 0, (struct sockaddr *) &client, &recv_size);

                                if ((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
                                {
                                    sendto(udpSocket, packetbuf, len, 0, (struct sockaddr *) &client, sizeof(client));
                        printf("\nRetransmitting Packet #%i", count);
                                }

                                else if (result == -1)
                                {
                                        // Handle Error
                                }

                                else
                                {
                                        acked++;
                        printf("\nReceived ACK For Data Packet #%i", count);
                                        break;
                                }
                        }

                         if (acked!=1)
                        {
                            puts("\nGave Up Transmission After 3 Retries");
                            break;
                        }

                        if (ssize != 512)
                                break;
                }
        }
    }   
    return 0;
}

提前致谢:)

【问题讨论】:

  • 套接字上没有任何形式的锁定?你到底怎么能指望这行得通。如果你是多线程的,套接字就是共享资源

标签: c multithreading sockets pthreads tftp


【解决方案1】:

您在端口 69 上循环侦听,但实际数据传输将从另一个随机选择的端口执行(请阅读 RFC 1350)。 然后你的主循环必须为每次新的传输创建一个新线程,这个新线程应该接收一个结构,其中包含要服务的文件的路径、目标 IP/端口、随机选择的本地端口等。

在将结构指针传递给线程时,您必须考虑的是支持该结构的内存。 你的情况

     struct clientThread clientT;

是在堆栈中动态创建的,当然,当代码块超出范围时(在您的情况下,在每个循环中),该结构将被“丢弃”,这意味着您正在传递一个指向“即将成为垃圾”的指针到刚刚创建的线程。

我建议在将结构传递给刚刚创建的线程时使用 malloc/free。

最后,您的主线程(调度程序)应该维护一个结构,考虑到所有创建的线程及其状态。这对于检测死线程或在传输正在进行时需要关闭主程序是必要的。

正如您所见,即使对于像 TFTP 这样的简单协议,实现服务器也并非易事。

【讨论】:

    【解决方案2】:

    您的连接处理程序在这里是错误的,因为您传递给每个线程的套接字上没有任何锁定。

    大多数基于 udp 的服务器实际上并没有分叉多个线程。 TCP 服务器可以做到这一点,因为每次接受都会获得一个新套接字,该套接字可以委托给一个不会被任何其他线程使用的新线程。

    但是对于 udp,您基本上对所有线程都使用相同的套接字,这是不行的。如果您要在套接字上提供保护,您可以使其工作,但您将失去通过使其成为多线程而试图获得的好处。

    【讨论】:

    • 对不起:我对这个答案投了反对票; 1)我认为“大多数基于 udp 的服务器实际上并没有分叉多个线程。”在实现即 TFTP 服务器或延迟的 DHCP 服务器等时是完全错误的。 2) 锁定套接字的问题在这里不是真正的问题,因为认为 OP 将为所有线程使用相同的套接字是错误的;请阅读 RFC-1350。
    • @Pat,我认为 1 是见仁见智的问题。我们实现的 udp 服务器是单线程的。史蒂文斯甚至提到了这一点。 2.你在代码中看到他其实是在使用同一个socket。归根结底,该代码存在很多问题。
    • @Philip; 1) 这不是意见问题;你不能实现一个没有多线程的 TFTP 服务器,你不能实现一个没有多线程的延迟 DHCP 服务器。 2) 当您应该说 OP 实际上错误地解释了 TFTP 标准时,您指出了一个套接字共享问题。正确实现 RFC 1350 后,套接字共享问题自然会避免
    • 我们同意在所有方面不同意 :-) 这并不是因为你举了一个 2 的例子,大多数 udp 服务器突然就多线程了。我的多线程问题是正确的,而共享套接字的使用是错误的。我只是从与你不同的角度看待它。但你的回答比我的好,我没有问题要承认。
    猜你喜欢
    • 2015-06-20
    • 2018-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多