2016-02-29 2 views
-2

У меня простая прокси-серверная программа, написанная на C для курса. У меня возникают некоторые проблемы с непоследовательными данными. Когда я запрашиваю сайт, я сохраняю полученные данные в файле на стороне сервера, а затем отправляю его клиенту и сохраняю его на стороне клиента. Результаты как на клиенте, так и на сервере будут разных размеров, и кажется, что некоторые из HTML дублируются. Как правило, файл, сохраненный на сервере, будет меньше, чем тот, который сохранен на клиенте, хотя оба файла по-прежнему больше фактической веб-страницы (т. Е. Если бы я щелкнул правой кнопкой мыши по странице и «сохранить как», итоговая страница меньше, чем те, которые возвращаются моим кодом). Я пробовал разные вещи, чтобы решить эту проблему, и ничего не работает. Результаты даже кажутся разными между попытками на том же сайте. Например, я могу запросить один и тот же сайт дважды, но размеры файлов отличаются от обеих попыток. В очень редких случаях, особенно на небольших веб-сайтах, как клиентская, так и серверная программа возвращают правильную веб-страницу, и оба файла имеют соответствующий размер.C - Непоследовательные данные из сокета

Примечание: Я знаю, что код по-прежнему довольно грязный. Я больше беспокоюсь об этом, прежде чем двигаться дальше. Я буду решать проблемы (например, проверить, не был ли сокет не открыт) после исправления этой проблемы, поэтому, пожалуйста, обращайтесь только к проблеме, которую я изложил.

Server.c

#include <sys/types.h> 
#include <sys/socket.h> 
#include <netdb.h> 
#include <stdio.h> 
#include <string.h> 
#include <arpa/inet.h> 
#include <regex.h> 
#include <time.h> 

/*Code used to resolve URL into IP address adapted from following URL: 
http://www.binarytides.com/hostname-to-ip-address-c-sockets-linux/ 
*/ 

//This code has been adapted from the server code provided in class 

int main(int argc, char** argv) 
{ 
    char str[655360]; 
    char recvline[655360]; 
    char parsedRecv[655360]; 
    char domain[1025]; 
    char directory[1025]; 
    char absoluteURL[1025]; 
    char temp[1025]; 


    int listen_fd, conn_fd, n, tempCount; 
    struct sockaddr_in servaddr; 
    int bytesRead; 
    int stringCounter; 

    int port; 

    FILE *fp; 

    //Variables used for second socket and resolving of host 
    char ip[100]; 
    int sockfd, secondSocketCount; 
    int len = sizeof(struct sockaddr); 
    struct sockaddr_in secondServaddr; 
    struct addrinfo *servinfo, *p; 
    struct sockaddr_in *h; 
    int rv; 
    char simpleMatch[10]; 
    int flag = 0; 
    //End 

    //Used for HTTP GET request 
    char request[2049]; 

    listen_fd = socket(AF_INET, SOCK_STREAM, 0); 

    bzero(&servaddr, sizeof(servaddr)); 

    servaddr.sin_family = AF_INET; 
    servaddr.sin_addr.s_addr = htons(INADDR_ANY); 
    if(argc < 2) 
    { 
     printf("Error! Enter a port number to run this server on.\n\tEx: ./server 22000\n\r\0"); 
     return 0; 
    } 
    else 
    { 
     port = atoi(argv[1]); 
    } 

    servaddr.sin_port = htons(port); 

    printf("\n"); 
    printf("Awaiting connections...\n"); 

    bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)); 
    listen(listen_fd, 10); 

    //Once the server is listening, enter an infinite loop to keep listening 
    while(1) 
    { 
     conn_fd = accept(listen_fd, (struct sockaddr*) NULL, NULL); 
     bytesRead = read(conn_fd, recvline, sizeof(recvline)); 
     if(bytesRead > 0) //data read 
     { 
      recvline[bytesRead] = '\0'; 

      bzero(absoluteURL, 1025); 
      strcpy(absoluteURL, recvline); 

      //Extract host and page from recvline 
      //For loop used to check if URL begins with HTTP or HTTPS 
      for(stringCounter = 0; stringCounter < 5; stringCounter++) 
      { 
       simpleMatch[stringCounter] = tolower(recvline[stringCounter]); 
      } 
      simpleMatch[strlen(simpleMatch)] = '\0'; 
      if(strcmp("http:", simpleMatch) == 0) 
      { 
       for(stringCounter = 7, tempCount = 0; stringCounter < strlen(recvline); stringCounter++, tempCount++) 
       { 
        temp[tempCount] = recvline[stringCounter]; 
       } 
       temp[strlen(temp)] = '\0'; 
       strcpy(recvline, temp); 
      } 
      else if(strcmp("https", simpleMatch) == 0) 
      { 
       for(stringCounter = 8, tempCount = 0; stringCounter < strlen(recvline); stringCounter++, tempCount++) 
       { 
        temp[tempCount] = recvline[stringCounter]; 
       } 
       temp[strlen(temp)] = '\0'; 
       strcpy(recvline, temp); 
      } 

      //printf("\n\nAfter stripping HTTP, we are left with: %s\n\n", recvline); 

      //Now that HTTP:// or HTTPS:// has been stripped, can parse for domain 
      for(stringCounter = 0, tempCount = 0; stringCounter < strlen(recvline); stringCounter++) 
      { 
       //moving domain into the domain string 
       if(flag == 0) 
       { 
        if(recvline[stringCounter] != '/') 
        { 
         domain[stringCounter] = recvline[stringCounter]; 
        } 
        else 
        { 
         domain[stringCounter + 1] = '\0'; 
         //directory[tempCount] = recvline[stringCounter]; 
         flag = 1; 
         tempCount++; 
        } 
       } 
       else 
       { 
        directory[tempCount] = recvline[stringCounter]; 
        tempCount++; 
       } 
      } 

      //printf("\n\nDirectory is: %s\n\n", directory); 

      //reset flag and append '\0' to directory and domain 
      flag = 0; 
      if(tempCount < 1025) 
      { 
       directory[tempCount] = '\0'; 
      } 
      //directory[strlen(directory)] = '\0'; 
      //domain[strlen(domain)] = '\0'; 

      //Done extracting 

      //Resolve hostname to IP 

      if((rv = getaddrinfo(domain, NULL, NULL, &servinfo)) != 0) 
      { 
       printf("Error: an IP address cannot be resolved for %s\n", domain); 
       return 0; 
       //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); 
      } 
      else 
      { 
       for(p = servinfo; p != NULL; p = p->ai_next) 
       { 
        h = (struct sockaddr_in *) p->ai_addr; 
        strcpy(ip, inet_ntoa(h->sin_addr)); 
       } 

       freeaddrinfo (servinfo); 

       printf("%s resolved to: %s\n", domain, ip); 
      } 
      //End Resolve 

      //Now that the IP is resolved, open a socket and connect to the IP 

      //Open socket 
      sockfd = socket(AF_INET, SOCK_STREAM, 0); 
      bzero(&secondServaddr, sizeof(secondServaddr)); 

      secondServaddr.sin_family = AF_INET; 
      secondServaddr.sin_port = htons(80); 

      inet_pton(AF_INET, ip, &(secondServaddr.sin_addr)); //IP taken from earlier resolution 
      connect(sockfd, (struct sockaddr*) &secondServaddr, sizeof(secondServaddr)); 

      //socket is open, can create and send request, finally 
      bzero(request, 2049); 
      //sprintf(request, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", directory, domain); 
      //sprintf(request, "GET %s HTTP/1.1\r\n\r\n", absoluteURL); 
      //sprintf(request, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", absoluteURL, domain); 
      sprintf(request, "GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n", directory, domain); 

      write(sockfd, request, strlen(request)); 

      printf("\tAttempting to retrieve data: this may be slow.\n"); 

      bzero(recvline, 655360); 
      bzero(parsedRecv, 655360); 

      //Old method used to retrieve data 
      //This was changed when I began to run into issues 
      /*while(1) 
      { 
       secondSocketCount = read(sockfd, parsedRecv, sizeof(parsedRecv)); 
       if(secondSocketCount == -1) 
       { 
        printf("Error receiving data: server terminating.\n"); 
        return 0; 
       } 
       else if(secondSocketCount == 0) 
       { 
        //no more data 
        break; 
       } 
       strcat(recvline, parsedRecv); 
      }*/ 

      //This while loop is used to read in data (the response from the server) 
      bzero(str, 655360); 
      while(secondSocketCount = read(sockfd, recvline, sizeof(recvline)) > 0) 
      { 
       strcat(str, recvline); 
      } 
      //bzero(parsedRecv, 655360); 
      //recvline[strlen(recvline)] = '\0'; 

      printf("\tData retrieved from main server.\n"); 

      //This for loop finds the end of the HTTP header and copies everything after into parsedRecv 
      for(stringCounter = 0, tempCount = 0; stringCounter < strlen(str); stringCounter++) 
       { 
        //lazy if statement to find two \r\n in a row to mark the end of the header 
        if(str[stringCounter] == '\r' && str[stringCounter + 1] == '\n' && str[stringCounter + 2] == '\r' && str[stringCounter + 3] == '\n' && flag == 0) 
        { 
         flag = 1; 
         stringCounter += 3; 
        } 
        if(flag == 1) 
        { 
         parsedRecv[tempCount] = str[stringCounter]; 
         tempCount++; 
        } 
       } 
      flag = 0; 
      parsedRecv[strlen(parsedRecv)] = '\0'; 
      fp = fopen("ReturnedPageServer.html", "w"); 
      if(fp != NULL) 
      { 
       fprintf(fp, "%s", parsedRecv); 
       //fprintf(fp, "%s", recvline); 
      } 
      fclose(fp); 
      printf("\tData saved to ReturnedPageServer.html\n"); 

     } 

     //strcpy(str, "This is a test of the Hello World Broadcast System.\n"); 
     bzero(str, 655360); 
     strcpy(str, parsedRecv); 
     write(conn_fd, str, strlen(str)); 
     close(conn_fd); 
     printf("\tData sent to client.\n"); 
     printf("Awaiting further connections...\n"); 

     //strcpy(directory, ""); 
     //strcpy(domain, ""); 
     //strcpy(recvline, ""); 
     bzero(directory, 1025); 
     bzero(domain, 1025); 
     bzero(temp, 1025); 
     bzero(recvline, 655360); 
    } 
    return 0; 
} 

Client.c

#include <sys/types.h> 
#include <sys/socket.h> 
#include <netdb.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

//Code adapted from client code provided in class 

int main(int argc, char** argv) 
{ 
    int sockfd, n, port; 
    int len = sizeof(struct sockaddr); 
    char sendline[10000]; 
    char recvline[655360]; 
    struct sockaddr_in servaddr; 

    FILE *fp; 

    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    bzero(&servaddr, sizeof(servaddr)); 

    servaddr.sin_family = AF_INET; 
    if(argc < 2) 
    { 
     printf("Error! Enter the port number for the server.\n\tEx: ./client 22000\n\r\0"); 
     return 0; 
    } 
    else 
    { 
     port = atoi(argv[1]); 
    } 
    servaddr.sin_port = htons(port); 

    inet_pton(AF_INET, "129.120.151.94", &(servaddr.sin_addr)); //CSE01 IP 
    connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)); 

    printf("url: "); 
    scanf("%s", sendline); 

    //strcpy(sendline, "The server should display this text.\n\0"); 
    //printf("\nLength of string: %d\n", strlen(sendline)); 
    //printf("\t%s\n", sendline); 
    write(sockfd, sendline, strlen(sendline)); 

    fp = fopen("ReturnedPageClient.html", "w"); 
    bzero(recvline, 655360); 
    while(n = read(sockfd, recvline, sizeof(recvline)) > 0) 
    { 
     //printf("%s", recvline); 
     if(fp != NULL) 
     { 
      fprintf(fp, "%s", recvline); 
     } 
     else 
     { 
      printf("\tError saving file: client terminating.\n"); 
      fclose(fp); 
      return 0; 
     } 

    } 
    fclose(fp); 
    printf("\tResponse received from proxy server.\n\tFile saved as \"ReturnedPageClient.html\"\n"); 
    close(sockfd); 
    return 0; 
} 
+3

'simpleMatch [strlen (simpleMatch)] = '\ 0';' не будет отменять строку! – chqrlie

+2

Вы используете 'strlen' для вещей, которые не являются строками в стиле C. Если они являются строками в стиле C, они уже завершены нулем. Если они не являются строками в стиле C, вы не можете передать их в 'strlen'. –

+0

Синтаксическая ошибка: 'while (n = read (sockfd, recvline, sizeof (recvline))> 0)' – chqrlie

ответ

0

Для секвенирования связи между клиентом и сервером с сокетами, вам нужно какое-то протокол, так что каждый участник в связи может определить когда сообщение завершено. Сообщение, переданное через сокет TCP, может быть получено в кусках другого размера, чем то, что было написано передающей стороной.

Вы не принимаете это во внимание в своих программах, вы предполагаете, что успешный запрос read с возвратом данных, которые были записаны в запросе write на отправляющей стороне. Это неверно: вы должны продолжать чтение из сокета и хранить куски, считываемые в буфер запросов, до тех пор, пока не будет заполнен полный запрос или сокет не будет закрыт.

Простым протоколом было бы написать одну строку за раз. Запросы и ответы на секвенирование с помощью \n символов. Это более или менее то, что делают протоколы HTTP, SMTP и POP.

Кодовые много проблем:

  • имеет синтаксические ошибки, которые мешают компиляции.

  • Вы определяете очень большие буферы как автоматические переменные, превышающие 2 МБ, вы можете получить переполнение стека в некоторых системах.

  • Данные, которые были прочитаны в буфере, должны быть завершены нулем вручную, так как вы не передаете терминаторы '\0'. Вы, кажется, знаете об этой проблеме, но ваш метод parsedRecv[strlen(parsedRecv)] = '\0'; ничего полезного не делает. strlen(parsedRecv) по определению является смещением байта '\0'. Если строка не имеет нулевого конца, strlen будет сканировать за пределами конца буфера и вызывать неопределенное поведение. Вы должны просто установить '\0' байт вручную с recvline[n] = '\0'; после успешного read.

  • Вы пытаетесь и струнные матчи фрагментов с strcmp: это не является наиболее эффективным, так как вам нужно сначала скопировать фрагмент в отдельный буфер и нулевой прекратить его так strcmp можно сравнить, что в полной строке. Вместо этого используйте memcmp с указанной длиной фрагмента.

2

Одна проблема - не уверен, что ее «проблема» - это то, что вы ожидаете, что TCP будет ориентирован на сообщения. То есть вы отправляете сообщение, скажем, 500 байт и ожидаете получить один блок из 500 байтов. Это не то, как работает TCP. TCP ориентирован на поток, 500-байтная отправка может быть получена, как 250 2 байта, читается на сервере, или 1 500 байт или 100, 50, 100, 2,2,2,2,2,2 240. Вы должны зацикливаться на сервере пока вы не получите все «сообщение». Это приведет к непоследовательному поведению, в частности, все будет работать локально, но не над «реальной» сетью.

Это, в свою очередь, ставит вопрос о том, как вы знаете, что получили целое «сообщение». У вас должен быть протокол более высокого уровня, который позволяет создавать кадрирование сообщений (например, отправлять фиксированную длину, а затем тело)

+0

Разве у нас нет хорошего дубликата для этого? Сегодня это третий вопрос такого же характера. – SergeyA

Смежные вопросы