Linux C网络通信程序在调试器中工作,但不在外部

时间:2016-10-15 07:55:09

标签: c linux network-programming

我有两个文件,server.c和client.c。服务器侦听客户端请求然后适当地回复(现在只实现带有静态目录的LIST)。当服务器收到LIST命令时,它会计算指定目录中的常规文件数量(现在是一个静态值),然后向客户端发送数量,以便它一直监听,直到收到n个元素。然后,服务器开始将文件名发送给侦听客户端。

这在理论上和调试服务器时有效,但在我通过终端正常运行这两个应用程序时却不行。客户端在输入LIST命令后卡住了,当服务器收到它时,它不会继续发送项目数量和名称。然而,当在CodeBlocks中以调试模式运行服务器时(虽然我猜IDE无关紧要)并逐行完成代码,一切都按预期工作。这可能是某种竞争条件,但我无法自行解决。

client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUF 1024
#define PORT 8543

int main (int argc, char **argv) {
  int create_socket;
  char buffer[BUF];
  struct sockaddr_in address;
  int size;

  if ((create_socket = socket (AF_INET, SOCK_STREAM, 0)) == -1)
  {
     perror("Socket error");
     return EXIT_FAILURE;
  }

  memset(&address,0,sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons (PORT);
  const char *addr = "localhost";
  inet_aton (addr, &address.sin_addr);
  if (connect ( create_socket, (struct sockaddr *) &address, sizeof (address)) == 0)
  {
     printf ("Connection with server (%s) established\n", inet_ntoa (address.sin_addr));
     size=recv(create_socket,buffer,BUF-1, 0);
    if (size>0)
     {
        buffer[size]= '\0';
        printf("%s",buffer);
        bzero(buffer, BUF);

     }
  }
  else
  {
     perror("Connect error - no server available");
     return EXIT_FAILURE;
  }

  do {
     printf ("Send message: ");
     fgets (buffer, BUF, stdin);
     send(create_socket, buffer, strlen (buffer), 0);
     if(strcmp(buffer, "LIST\n")){
        bzero(buffer, BUF);
        size=recv(create_socket,buffer,BUF-1, 0);
        if(size >0){
          buffer[size] = '\0';
          int items = atoi(buffer);
         printf("%d", items);
          bzero(buffer, BUF);
          for(int i = 0; i < items; i++){
            size=recv(create_socket,buffer,BUF-1, 0);
            if(size >0){
              buffer[size] = '\0';
              printf("%s\n", buffer);
            }
            bzero(buffer, BUF);
          }
        }
     }
  }
  while (strcmp (buffer, "QUIT\n") != 0);
  close (create_socket);
  return EXIT_SUCCESS;
}

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <string>
#include <iostream>
#define BUF 1024
#define PORT 8543

using namespace std;

int main (void) {
  int create_socket, new_socket;
  socklen_t addrlen;
  char buffer[BUF];
  int size;
  struct sockaddr_in address, cliaddress;
  struct dirent *direntp;
  DIR *dirp;

  create_socket = socket (AF_INET, SOCK_STREAM, 0);

  memset(&address,0,sizeof(address));
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = INADDR_ANY;
  address.sin_port = htons (PORT);

  int yes = 1;
  if (setsockopt(create_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
    perror("setsockopt");
    exit(1);
  }


  if (bind ( create_socket, (struct sockaddr *) &address, sizeof (address)) != 0) {
     perror("bind error");
     return EXIT_FAILURE;
  }
  listen (create_socket, 5);

  addrlen = sizeof (struct sockaddr_in);

  int file_count = 0;
  struct dirent * entry;

  dirp = opendir("/home"); 
  while ((entry = readdir(dirp)) != NULL) {
    /* Only increment counter if item selected is a regular file */
    if (entry->d_type == DT_REG) {
         file_count++;
    }
  }
  closedir(dirp);

  while (1) {
     printf("Waiting for connections...\n");
     new_socket = accept ( create_socket, (struct sockaddr *) &cliaddress, &addrlen );
     if (new_socket > 0)
    {
        printf ("Client connected from %s:%d...\n", inet_ntoa (cliaddress.sin_addr),ntohs(cliaddress.sin_port));
        strcpy(buffer,"Welcome, please enter your command:\n");
        send(new_socket, buffer, strlen(buffer),0);
        bzero(buffer, BUF);
     }
     do {
        size = recv (new_socket, buffer, BUF-1, 0);
        if( size > 0)
        {
           if(strcmp(buffer, "LIST\n") == 0){
              if ((dirp = opendir("/home")) == NULL) {
                perror ("Failed to open directory");
                return 1;
              }
              bzero(buffer, BUF);
              sprintf(buffer, "%d", file_count);
              /* Send client the amount of items he is to receive */
              send(new_socket, buffer, strlen(buffer),0);
              bzero(buffer, BUF);
              while ((direntp = readdir(dirp)) != NULL)
                  /* Check if item is a file and if yes, send client its name */
                  if (direntp->d_type == DT_REG){
                    strcpy(buffer,direntp->d_name);
                    send(new_socket, buffer, strlen(buffer),0);
                    bzero(buffer, BUF);
                  }
              while ((closedir(dirp) == -1) && (errno == EINTR)) ;
           }
        }
        else if (size == 0)
        {
           printf("Client closed remote socket\n");
           break;
        }
        else
        {
           perror("recv error");
           return EXIT_FAILURE;
        }
     } while (strncmp (buffer, "quit", 4)  != 0);
     close (new_socket);
  }
  close (create_socket);
  return EXIT_SUCCESS;
}

1 个答案:

答案 0 :(得分:2)

正如immibis所指出的那样,您的代码假设每个人send都对应一个人recv。当您慢慢地逐步执行代码时,此假设会(意外地)正确,但是当程序运行时,单个send可以读取多个recv

这是一个非常常见的错误。事实上,TCP可以处理数据流,而不是单个数据包或消息。它就像在文件中读取或写入数据一样(如immibis指出的那样)。 send调用将数据放入发送缓冲区,TCP可以自行决定何时发送数据。到达接收端的时间取决于数据发送的时间和(通常是不可预测的)网络条件。

解决方案是将数据流分成块(消息)你自己。以下是一些方法:

  • 使用固定长度的消息 - 如果所有消息都具有固定长度,则接收方只需要recv正确的字节数。如果在这些字节之后接收缓冲区中还有任何内容,那么该数据已经属于下一条消息。

  • 在每封邮件之前发送邮件的长度。如果你想发送字符串&#34; blah&#34;,请将其编码为&#34; 0004blah&#34;或类似的东西。接收器将始终读取前四个字节(即0004)以计算要读取的剩余字节数。然后它将读取所需的字节数,处理消息,然后等待下一个字节。它是一个强大的解决方案,也很容易实现。

  • 使用分隔符。文本文件中的行除以换行符(\n)。同样,您可以在消息之间添加特殊的分隔符字节(或字节)。例如,您可以定义消息始终以美元符号($)结尾。然后所有接收器必须逐字节地从套接字recv开始,直到它收到一个美元符号。当然,如果你采用这种方法,你必须确保消息的正文不包含分隔符。