recvfrom не возвращает -1 после сигнала

Я пишу программу ping с использованием сырых сокетов, но recvfrom не возвращает -1 с EINTR, даже если обрабатывается SIGALRM. Этот SIGALRM создается моим сигналом тревоги (1). Я хочу, чтобы recvfrom возвращался, чтобы я мог решить, что пакет действительно был потерян.

#include "libsock"
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

double total=0, max=0, min=10000000;
int totalpackets=0, packetslost=0;
int recieved=0;

void handler2()
{
  printf("host unreachable\n");
}

unsigned short 
csum (unsigned short *buf, int nwords)
{
  unsigned long sum;
  for (sum = 0; nwords > 0; nwords--)
    sum += *buf++;
  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);
  return ~sum;
}

void 
handler()
{
  printf("\n");
  printf("--------------PINGING STATISTICS----------------\n");
  printf("AVG:%f MAX:%f MIN:%f TOTAL PACKETS:%d PACKETS LOST:%d SUCCESS PERCENTAGE:%f\n\n",total/(totalpackets-packetslost),max,min,totalpackets,packetslost,((double)(totalpackets-packetslost)/(double)totalpackets)*100);
  exit(0);
}

int 
main (int argc, char *argv[])
{
  if (argc != 2)
  {
    printf ("need destination for tracert\n");
    exit (0);
  }
  int sfd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
  char buf[4096] = { 0 };

  int one = 1;
  const int *val = &one;
  if (setsockopt (sfd, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0)
    printf ("Cannot set HDRINCL!\n");

  struct sockaddr_in addr;
  struct ip* ip_hdr=(struct ip*)buf;
  addr.sin_port = htons (7);
  addr.sin_family = AF_INET;
  inet_pton (AF_INET, argv[1], &(addr.sin_addr));

  ip_hdr->ip_hl = 5;
  ip_hdr->ip_v = 4;
  ip_hdr->ip_tos = 0;
  ip_hdr->ip_len = 20 + 8 + 64;
  ip_hdr->ip_id =0;
  ip_hdr->ip_off = 64;
  ip_hdr->ip_ttl = 64;
  ip_hdr->ip_p = IPPROTO_ICMP;
  inet_pton (AF_INET, "172.30.104.59", &(ip_hdr->ip_src));
  inet_pton (AF_INET, argv[1], &(ip_hdr->ip_dst));
  ip_hdr->ip_sum = csum ((unsigned short *) buf, 4);

  struct icmphdr *icmphd = (struct icmphdr *) (buf+20);
  icmphd->type = ICMP_ECHO;
  icmphd->code = 0;
  icmphd->checksum = 0;
  icmphd->un.echo.id = 0;
  icmphd->un.echo.sequence =1;

  memset(buf+28,'a',64);
  icmphd->checksum = csum ((unsigned short *) (buf+20), 36);
  signal(SIGINT,handler);
  struct timeval tv1,tv2;  

  printf("Pinging %s with 64 bytes of data.\n\n",argv[1]);

  while(1)
  {
    recieved=0;     
    totalpackets++;
    sendto (sfd, buf,20+ 8+64, 0, SA & addr, sizeof addr);
    char buff[4096] = { 0 };
    struct sockaddr_in addr2;
    socklen_t len = sizeof (struct sockaddr_in);
    signal(SIGALRM,handler2);
    gettimeofday(&tv1,NULL);
    alarm(1);
    int x=recvfrom (sfd, buff, 20+8+64, 0, SA & addr2, &len);
    gettimeofday(&tv2,NULL);
    double   d=(double)(tv2.tv_sec-tv1.tv_sec)*1000+((double)(tv2.tv_usec-tv1.tv_usec))/1000;

    if(x>0) {
      struct icmphdr *icmphd2 = (struct icmphdr *) (buff + 20);
      printf ("Reached destination:%s\tTime elapsed:%f ms\n\n",
          inet_ntoa (addr2.sin_addr),d);
      total+=d;
      if(d>max)
        max=d;
      if(d<min)
        min=d;
    } else {
      printf("Packet lost\n");   
      packetslost++;
    }

    sleep(1);
  }
  return 0;
}

libsock содержит заголовки и SA = (struct sockaddr *)

SIGALRM отличается от других сигналов, я не устанавливал SA_RESTART ..

Спасибо.


person Sr1n4th    schedule 17.03.2013    source источник
comment
Вы не думали просто установить тайм-аут сокета?   -  person nneonneo    schedule 17.03.2013
comment
@nneonneo, спасибо за это, раньше не знал о тайм-ауте носка, моя проблема решена, но мне все еще интересно, почему recvfrom не работает так, как я думаю.   -  person Sr1n4th    schedule 17.03.2013
comment
1) обработчик сигнала должен иметь подпись void handler(int signum) 2) Не используйте printf () в обработчике сигнала, он не реентерабелен.   -  person wildplasser    schedule 17.03.2013
comment
почему вы не могли использовать printf в обработчике сигналов? отсутствие повторного входа означает, что сигнал замаскирован во время вызова.   -  person Edouard Thiel    schedule 17.03.2013
comment
@EdouardThiel: см. man 7 signal   -  person alk    schedule 17.03.2013
comment
Сигнал может поступить, пока printf находится внутри malloc, для (пере) выделения своего буфера. Или просто обновите его указатели внутри этого буфера.   -  person wildplasser    schedule 17.03.2013
comment
ok printf - небезопасная функция в смысле POSIX, но и вызывать ее не запрещено. Любой пример или FAQ по нему, мне интересно узнать больше.   -  person Edouard Thiel    schedule 17.03.2013
comment
@EdouardThiel: сложно заставить его потерпеть неудачу: вы должны активировать сигнал прямо тогда, когда внешний printf находится в середине какой-то важной операции. Затем вы попадаете в ту же ситуацию повторного входа, что и в любой другой многопроцессорной системе, основанной на потоках или прерываниях. Автор printf внутренней работы может попытаться минимизировать окно для коррупции (я сделал в своем), но есть некоторые вещи, от которых, похоже, не стоит защищать.   -  person torek    schedule 17.03.2013
comment
возможный дубликат тайм-аута recvfrom () с alarm ()   -  person alk    schedule 17.03.2013


Ответы (3)


Точное поведение signal зависит от системы. Из man 2 signal:

Семантика BSD эквивалентна вызову sigaction(2) со следующими флагами:

sa.sa_flags = SA_RESTART;

В Linux ситуация следующая:

...

По умолчанию в glibc 2 и новее функция-оболочка signal() не вызывает системный вызов ядра. Вместо этого он вызывает sigaction (2), используя флаги, обеспечивающие семантику BSD.

(курсив мой)

Таким образом, по умолчанию в системах Linux и BSD signal установит SA_RESTART, что автоматически перезапустит ваш системный вызов, чтобы он никогда не сообщал EINTR.

Вам нужно использовать sigaction, чтобы получить точный контроль над флагами:

struct sigaction sact = {
    .sa_handler = handle_sigalrm,
    .sa_flags = 0,
};
sigaction(SIGALRM, &sact, NULL);
person nneonneo    schedule 17.03.2013

Поведение при использовании "простого" signal в значительной степени зависит от ОС. Предполагая соответствие POSIX, вы должны вместо этого использовать sigaction. Подробнее см. В чем разница между sigaction и signal? .

person torek    schedule 17.03.2013

Вы не должны использовать signal, потому что он не переносится. Вместо этого используйте sigaction.

С опцией SA_RESTART блокирующий вызов будет автоматически возобновлен после того, как сигнал будет пойман. Без SA_RESTART вызов блокировки останавливается, возвращает -1 и устанавливает errno в EINTR.

Вот функция mysignal, которую вы можете использовать для замены вашего signal вызова на options, установленный на 0:

int mysignal (int sig, void (*func)(int), int options) {
  int r; 
  struct sigaction act; 

  act.sa_handler = func; 
  act.sa_flags = options; 
  sigemptyset (&act.sa_mask); 

  r = sigaction (sig, &act, NULL); 
  if (r < 0) perror (__func__); 
  return r;
}
person Edouard Thiel    schedule 17.03.2013