TFTP 服务器 - 线程版本问题

TFTP Server - Issue With Threaded Version

我创建了一个简单的 tftp 服务器,它只处理读取请求 (RRQ)。在我开始制作服务器的多线程版本之前,一切都运行良好。在应用程序中,我只是在主线程中接收请求,然后将请求转发到执行数据包分析的新线程。因此,我需要将套接字、接收到的数据包和包含客户端信息的 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, '[=10=]', 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, '[=11=]', 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;
}

提前致谢:)

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

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

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

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

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

     struct clientThread clientT;

是在堆栈中动态创建的,当然结构是 "discarded" 当代码块超出范围时(在你的情况下在每个循环中)这意味着你正在传递一个指向 "soon to be garbage" 到刚刚创建的线程。

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

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

如您所见,即使是像 TFTP 这样的简单协议,实现服务器也不是一件容易的事。