Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sockets/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
UDP非阻塞客户机/服务器对中生成的随机EWOOLD块与SIGIO生成的RecvFrom_C_Sockets_Networking_Udp_Asyncsocket - Fatal编程技术网

UDP非阻塞客户机/服务器对中生成的随机EWOOLD块与SIGIO生成的RecvFrom

UDP非阻塞客户机/服务器对中生成的随机EWOOLD块与SIGIO生成的RecvFrom,c,sockets,networking,udp,asyncsocket,C,Sockets,Networking,Udp,Asyncsocket,我正在使用SIGIO构建一个UDP非阻塞客户端和服务器套接字对,以便在服务器上接收数据。这是我的情况。当客户端向我发送消息时,我会激活一个SIGIO。但是,消息大约是3个1024字节数据包的突发,即客户端一次发送3个数据包。我希望服务器在第一个数据包的SIGIO上触发,在处理程序中,我有一个while循环,它最多遍历3个数据包中的2个,并为当前运行读取它们。然而,我已经多次运行了这3个数据包发送,它们之间的间隔为5秒。因此,我希望第一个数据包触发SIGIO,同时清空缓冲区,在信号处理程序中,我取

我正在使用SIGIO构建一个UDP非阻塞客户端和服务器套接字对,以便在服务器上接收数据。这是我的情况。当客户端向我发送消息时,我会激活一个SIGIO。但是,消息大约是3个1024字节数据包的突发,即客户端一次发送3个数据包。我希望服务器在第一个数据包的SIGIO上触发,在处理程序中,我有一个while循环,它最多遍历3个数据包中的2个,并为当前运行读取它们。然而,我已经多次运行了这3个数据包发送,它们之间的间隔为5秒。因此,我希望第一个数据包触发SIGIO,同时清空缓冲区,在信号处理程序中,我取消第一次运行时剩余3-1个数据包可能导致的一个SIGIO挂起信号,并在5秒内为下一次运行的第一个数据包重新启用SIGIO。如果您只看代码,就会更容易解释:

客户:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <math.h>
#include <time.h>

static const unsigned int TIMEOUT_SECS = 2; // Seconds between retransmits

void error(const char *msg, const char *detail);

int main(int argc, char *argv[]) {

  if ((argc < 6) || (argc > 7))
    error("Parameter(s)", "<Server Address/Name> <Echo File> <Server Port/Service> <Maximum packet length> <Number of trials> [<Seconds between trials>]");

  char *server = argv[1];
  FILE *fp = fopen(argv[2], "rb");
  if (fp == NULL)
    error("Error opening file", "");

  fseek(fp, -1, SEEK_END);
  unsigned long stringlen = (getc(fp) == '\n' ? (ftell(fp) - 1) : ftell(fp));
  rewind(fp);

  char *string = malloc(stringlen * sizeof(char));
  if (string == NULL)
    error("malloc() failed", "");
  for (int i = 0; i < stringlen; i++) {
    *(string + i) = fgetc(fp);
  }
  printf("%lu\n", stringlen);

  fclose(fp);

  in_port_t service = (in_port_t) strtol(argv[3], NULL, 10);
  int maxpack = (int) strtol(argv[4], NULL, 10);
  int trials = (int) strtol(argv[5], NULL, 10); // must coordinate with server
  if (trials < 1)
    error("invalid trials parameter", "entered a nonpositive number");
  int sleeptime = (argc == 7) ? (int) strtol(argv[6], NULL, 10) : 5;

  int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (sock < 0)
    error("socket() failed", "");

  struct timeval timeout;
  timeout.tv_sec = TIMEOUT_SECS;
  timeout.tv_usec = 0;
  if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)) < 0)
    error("setsockopt() failed", "");
  if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(timeout)) < 0)
    error("setsockopt() failed", "");

  struct sockaddr_in servAddr;
  memset(&servAddr, 0, sizeof(servAddr));
  servAddr.sin_family = AF_INET;
  int rc = inet_pton(AF_INET, server, &servAddr.sin_addr.s_addr);
  if (rc == 0)
    error("inet_pton() failed", "invalid address string");
  else if (rc < 0)
    error("inet_pton() failed", "");
  servAddr.sin_port = htons(service);

  int packnum = (int) ceil((double) stringlen / maxpack);
  for (int num = 0; num < trials; num++) {
    struct timespec start, end;
    uint64_t diff;
    ssize_t numBytes;
    for (int i = 0; i < packnum; i++) {
      if (i == 0)
        clock_gettime(CLOCK_MONOTONIC, &start);
      numBytes = sendto(sock, string + i * maxpack, maxpack, 0, (struct sockaddr* ) &servAddr, sizeof(servAddr));
      if (numBytes < 0)
        error("sendto() failed", "");
    }

    struct sockaddr_in fromAddr;
    socklen_t fromAddrlen = sizeof(fromAddr);
    char buffer[strlen(argv[3]) + 1 + packnum + 1];
    memset(&buffer, 0, sizeof(buffer));
    do {
      numBytes = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *) &fromAddr, &fromAddrlen);
      if (numBytes < 0)
        error("recvfrom() failed", "");
      buffer[numBytes] = '\0';
    } while ((int) strtol(buffer, NULL, 10) == num);

    clock_gettime(CLOCK_MONOTONIC, &end);
    diff = 1000000000L * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec;

    printf("Received: %s\n", buffer);
    printf("Time: %f ms\n", (double) (diff / (double) 1000000));
    sleep(sleeptime);
  }

  free(string);
  close(sock);

  exit(0);
}

void error(const char *msg, const char *detail) {
  fputs(msg, stderr);
  if (*detail != '\0') {
    fputs(": ", stderr);
    fputs(detail, stderr);
  }
  fputc('\n', stderr);
  exit(1);
}
服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <limits.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>

#define ITOA_BASE_N (sizeof(unsigned)*CHAR_BIT + 2)

void error(const char *msg, const char *detail);
void PrintSocketAddress(const struct sockaddr *address, FILE *stream);
void SIGIOHandler(int signalType);
char * itoa_base(char *s, int x, int base);
void recvflush(int sockfd);

#define ITOA(x,b) itoa_base((char [ITOA_BASE_N]){0} , (x), (b))

int sock, acknum, maxpack; // GLOBAL for signal handler
volatile sig_atomic_t run, packets; // VOLATILE FOR signal handler modification
struct sigaction handler;

int main(int argc, char *argv[]) {

  if (argc != 5)
    error("Parameter(s)", "<Server Port/Service> <Number of ACKs> <Maximum packet length> <Number of trials>");

  in_port_t service = (in_port_t) strtol(argv[1], NULL, 10);
  acknum = (int) strtol(argv[2], NULL, 10);
  maxpack = (int) strtol(argv[3], NULL, 10);
  int trials = (int) strtol(argv[4], NULL, 10); // must coordinate with client
  run = 0;

  sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (sock < 0)
    error("socket() failed", strerror(errno));

  struct sockaddr_in servAddr;
  memset(&servAddr, 0, sizeof(servAddr));
  servAddr.sin_family = AF_INET;
  servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servAddr.sin_port = htons(service);

  if (bind(sock, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0)
    error("bind() failed", strerror(errno));

  handler.sa_handler = SIGIOHandler;
  if (sigfillset(&handler.sa_mask) < 0)
    error("sigfillset() failed", "mask not set");
  handler.sa_flags = 0;

  if (sigaction(SIGIO, &handler, 0) < 0)
    error("sigaction() failed", "SIGIO behavior unable to be modified");

  if (fcntl(sock, F_SETOWN, getpid()) < 0)
    error("Unable to set process owner to us", "");

  if (fcntl(sock, F_SETFL, O_NONBLOCK | FASYNC) < 0)
    error(
        "Unable to put client sock into non-blocking/async mode", "");

  recvflush(sock);

  for (;;) {
    if (run == trials) {
      close(sock);
      exit(0);
    }
    printf(".");
    fflush(stdout);
    sleep(1);
  }
}

void SIGIOHandler(int signalType) {
  printf("SIGIO\n");
  packets = 0;
  run++;
  struct sockaddr_in clntAddr;
  socklen_t clntLen = sizeof(clntAddr);
  ssize_t numBytes;
  //FILE *fp = fopen("output.txt", "ab");
  //if (fp == NULL)
    //error("Error opening file", "");
  char buffer[maxpack + 1];
  //char *output = malloc(2000 * (maxpack + 1) * sizeof(char));
  //if (output == NULL)
    //error("malloc() failed", "");
  memset(buffer, 0, sizeof(buffer));
  //memset(output, 0, sizeof(output));
  while (packets < 2) { // read up to 2 packets (1024 bytes each) from buffer
    numBytes = recvfrom(sock, buffer, maxpack, 0, (struct sockaddr *) &clntAddr, &clntLen);
    if (numBytes < 0) {
      if (errno != EWOULDBLOCK) // only acceptable error
        error("recvfrom() failed", strerror(errno));
      else
        printf("%d EWOULDBLOCK", numBytes);
    } else {
      if (numBytes > 0) {
        packets++;
        buffer[numBytes] = '\0';
        //memmove(output + maxpack * packets, buffer, strlen(buffer));
      }
      printf("Run-Packet numBytes: %d-%d %d\n", run, packets, numBytes);
      printf("\t%s\n", buffer);
    }
  }
  sigset_t mask;
  sigpending(&mask);
  if (sigismember(&mask, SIGIO))
    printf("pending SIGIO signal\n");
  printf("setting ignore\n");
  struct sigaction change;
  change.sa_handler = SIG_IGN; // set ignore handler to cancel pending SIGIO signals from 1024 - 1 packets
  if (sigfillset(&change.sa_mask) < 0)
    error("sigfillset() failed", "mask not set");
  change.sa_flags = 0;
  if (sigaction(SIGIO, &change, 0) < 0)
    error("sigaction() failed", "SIGIO behavior unable to be modified");
  sigpending(&mask);
  if (sigismember(&mask, SIGIO))
    printf("pending SIGIO signal\n"); // should NOT show up
  recvflush(sock); // flush ignored bytes
  //for (int i = 0; i < strlen(output); i++)
    //putc(output[i], fp);
  //putc('\0', fp);

  //fclose(fp);
  //free(output);

  char *ack = ITOA(run, 10);
  char *append = ITOA(packets, 10);
  strcat(ack, "/"); // fix client side
  strcat(ack, append);
  for (int i = 0; i < acknum; i++) {
    numBytes = sendto(sock, ack, sizeof(ack), 0, (struct sockaddr *) &clntAddr, clntLen);
    if (numBytes < 0)
      error("sendto() failed", strerror(errno));
  }

  if (sigaction(SIGIO, &handler, 0) < 0)
        error("sigaction() failed", "SIGIO behavior unable to be modified"); // reenable SIGIO for subsequent runs

  printf("HERE\n");
}

char * itoa_base(char *s, int x, int base) {
  s += ITOA_BASE_N - 1;
  *s = '\0';
  if (base >= 2 && base <= 36) {
    int x0 = x;
    do {
      *(--s) = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[abs(x % base)];
      x /= base;
    } while (x);
    if (x0 < 0) {
      *(--s) = '-';
    }
  }
  return s;
}

void recvflush(int sockfd) {
  ssize_t num;
  do {
    num = recvfrom(sockfd, NULL, 0, 0, NULL, NULL);
    if (num < 0 && errno != EWOULDBLOCK)
      error("recvfrom() failed", strerror(errno));
  } while (errno != EWOULDBLOCK);
}
现在,我首先使用参数8080 10 1024 2启动服务器,并使用| head-c 10000对其进行管道处理,以查看前10000个字符。然后,我用参数192.168.4.101 test.txt 8080 1024 2启动客户机。test.txt中填充了大约3000个垃圾字符,这些字符应在客户端中拆分为3个1024个通知ceil的数据包。然而,每当我发送信息时,就会发生一件非常奇怪的事情

有时候,一切都很完美,我收到了预期的输出,缓冲打印间隔为5秒。其他时候,它会出错并生成一个eWoldBlock错误,请参见printf%d eWoldBlock,numBytes;永远在服务器代码中。每次它生成这个错误时,它仍然读取被忽略的数据包,因为我只读取了3个数据包中的2个,并使用recvflush刷新其余的数据包,即使我正在刷新接收缓冲区。据推测,recvflush会清除整个缓冲区以准备后续运行,但由于某些原因,被忽略的数据包会留在那里。救命啊


谢谢

您假设在SIGIO触发信号处理程序时,所有3个数据包都已在套接字缓冲区中。但是SIGIO在第一个数据包上被触发,这意味着套接字缓冲区中可能只有一个或两个数据包


假设只有一个数据包,由于没有更多的数据包,第二个recv将使用EWoldblock失败。类似地,recvflush中的recv将失败,因为丢失的数据包还没有出现。如果数据包不存在,也不会触发SIGIO,因此任何sigpending调用都无法检测到有未完成的数据。

Hmm。。。你认为会有什么问题?我认为通过SigIO触发器到达缓冲区的3个包是一个错误假设,因为中间有一个流量整形器,对通过的数据包进行计数,并且每当客户端发送3个包突发时,它就在SIGIO触发器之前记录3个数据包。我不确定。你知道更好的解决方案吗?我想更好的解决方案是避免像瘟疫这样的信号处理程序。它们很难做到正确,例如,在信号处理程序中有许多事情不安全,而且当您违反处理程序安全规则时,它们并不总是显而易见的,它们是不可移植的,与多线程不匹配,由于您使用的是非阻塞I/O,所以它们是不必要的。@Jonathanley:我同意JeremyFriesner的观点,信号是不好的。最好使用select、poll、epoll、kqueue或类似的方法,而且开销可能更小。不过,主要的问题是,您对套接字缓冲区中有多少数据包做出了毫无根据的假设。不要假设缓冲区中有三个数据包,而是应该确保它们确实存在,即在套接字再次可读后重试eWoldBlock,并计算跳过的数据包数。此外,由于UDP不可靠,您还必须处理数据包丢失、重新排序或重复。@JeremyFriesner@Steff那么多线程方法如何?可能会产生一个子线程,处理缓冲区中的字节,并在等待下一次运行时杀死自己?或者使用select、poll、epoll、kqueue或其他方式的多路复用方法更好?澄清一下,服务器最多有两个可能的客户端通过UDP进行通信,但主要是一个。根据我的经验,单线程通过选择和非阻塞I/O处理UDP套接字和任何其他必需的套接字都可以正常工作。如果预期UDP通信量特别大,那么产生一个专门用于处理UDP通信的独立线程来降低UDP套接字缓冲区溢出和丢弃UDP数据包的风险可能值得付出额外的复杂性。如果您确实使用单独的线程,那么应该保留线程,而不是结束线程并生成新线程,因为生成/收获线程的成本相当高。