1

我最近正在研究一个基于usrsctp的项目。

在创建新的 SCTP 套接字时,可以指定一个回调函数,当有新数据可用时调用该回调函数,如下面的代码所示。

创建一个新的 SCTP 套接字:

struct socket *s = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP,
                                  sctp_data_received_cb, NULL, 0, sctp);

回调函数:

static int
sctp_data_received_cb(struct socket *sock, union sctp_sockstore addr, void *data,
                  size_t len, struct sctp_rcvinfo recv_info, int flags, void *user_data)
{
  struct sctp_transport *sctp = (struct sctp_transport *)user_data;
  if (sctp == NULL || len == 0)
    return -1;

  fprintf(stdout, "Data of length %u received on stream %u with SSN %u, TSN %u, PPID %u\n",
          (uint32_t)len,
          recv_info.rcv_sid,
          recv_info.rcv_ssn,
          recv_info.rcv_tsn,
          ntohl(recv_info.rcv_ppid));

  if (flags & MSG_NOTIFICATION)
    handle_notification_message(sctp, (union sctp_notification *)data, len);
  else
    handle_rtcdc_message(sctp, data, len, ntohl(recv_info.rcv_ppid), recv_info.rcv_sid);

  free(data);
  return 0;
}

这个回调函数调用得当,但它的参数值只是一派胡言。上面代码的输出就像

Data of length 675381504 received on stream 31504 with SSN 34835, TSN 32651, PPID 8470824

应该是这样的

Data of length 18 received on stream 0 with SSN 0, TSN 4117987333, PPID 50

我阅读了usrsctp的源代码,发现调用回调的地方:

if (control->spec_flags & M_NOTIFICATION) {
    flags |= MSG_NOTIFICATION;
}
inp->recv_callback(so, addr, buffer, control->length, rcv, flags, inp->ulp_info);
SCTP_TCB_LOCK(stcb);

将其更改为下面的代码并重新编译库

if (control->spec_flags & M_NOTIFICATION) {
    flags |= MSG_NOTIFICATION;
}
fprintf(stdout, "[LIB] Data of length %u received on stream %u with SSN %u, TSN %u, PPID %u\n",
        control->length,
        rcv.rcv_sid,
        rcv.rcv_ssn,
        rcv.rcv_tsn,
        ntohl(rcv.rcv_ppid));
inp->recv_callback(so, addr, buffer, control->length, rcv, flags, inp->ulp_info);
SCTP_TCB_LOCK(stcb);

我可以得到预期的输出:

[LIB] Data of length 18 received on stream 0 with SSN 0, TSN 4117987333, PPID 50

为什么回调函数中的参数值变成了废话?

我在这里找到了一个类似的问题,但无法理解它的答案。我很确定这是同一个问题。

[更新1]

usrsctp.h中usrsctp_socket的原型:

struct socket *
usrsctp_socket(int domain, int type, int protocol,
           int (*receive_cb)(struct socket *sock, union sctp_sockstore addr, void *data,
                             size_t datalen, struct sctp_rcvinfo, int flags, void *ulp_info),
           int (*send_cb)(struct socket *sock, uint32_t sb_free),
           uint32_t sb_threshold,
           void *ulp_info);

[更新2]

我很确定不需要像旧的类似线程中建议的额外技巧,因为我在官方示例中没有看到奇怪的铸件,而且它们运行良好。

例如在 echo_server.c 中:

static int
receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
{
  char namebuf[INET6_ADDRSTRLEN];
  const char *name;
  uint16_t port;

  if (data) {
    if (flags & MSG_NOTIFICATION) {
      printf("Notification of length %d received.\n", (int)datalen);
    } else {
      switch (addr.sa.sa_family) {
#ifdef INET
      case AF_INET:
        name = inet_ntop(AF_INET, &addr.sin.sin_addr, namebuf, INET_ADDRSTRLEN);
        port = ntohs(addr.sin.sin_port);
        break;
#endif
#ifdef INET6
      case AF_INET6:
        name = inet_ntop(AF_INET6, &addr.sin6.sin6_addr, namebuf, INET6_ADDRSTRLEN),
        port = ntohs(addr.sin6.sin6_port);
        break;
#endif
      case AF_CONN:
#ifdef _WIN32
        _snprintf(namebuf, INET6_ADDRSTRLEN, "%p", addr.sconn.sconn_addr);
#else
        snprintf(namebuf, INET6_ADDRSTRLEN, "%p", addr.sconn.sconn_addr);
#endif
        name = namebuf;
        port = ntohs(addr.sconn.sconn_port);
        break;
      default:
        name = NULL;
        port = 0;
        break;
      }
      printf("Msg of length %d received from %s:%u on stream %d with SSN %u and TSN %u, PPID %d, context %u.\n",
             (int)datalen,
             name,
             port,
             rcv.rcv_sid,
             rcv.rcv_ssn,
             rcv.rcv_tsn,
             ntohl(rcv.rcv_ppid),
             rcv.rcv_context);
      if (flags & MSG_EOR) {
        struct sctp_sndinfo snd_info;

        snd_info.snd_sid = rcv.rcv_sid;
        snd_info.snd_flags = 0;
        if (rcv.rcv_flags & SCTP_UNORDERED) {
          snd_info.snd_flags |= SCTP_UNORDERED;
        }
        snd_info.snd_ppid = rcv.rcv_ppid;
        snd_info.snd_context = 0;
        snd_info.snd_assoc_id = rcv.rcv_assoc_id;
        if (usrsctp_sendv(sock, data, datalen, NULL, 0, &snd_info, sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0) < 0) {
          perror("sctp_sendv");
        }
      }
    }
    free(data);
  }
  return (1);
}
4

1 回答 1

2

好的,我自己弄清楚了为什么。这很愚蠢,但我会在这里发布解决方案,以防有人需要它。

union sctp_sockstore(回调函数第二个参数的类型)的定义如下图所示。

union sctp_sockstore {
#if defined(INET)
    struct sockaddr_in sin;
#endif
#if defined(INET6)
    struct sockaddr_in6 sin6;
#endif
    struct sockaddr_conn sconn;
    struct sockaddr sa;
};

INET 和 INET6 在 usrsctp 库中定义,但不在我的代码中,因为我手工制作了 Makefile 并省略了它们。由于联合的大小不同,参数被转移(如 16 位),因此变得无稽之谈。

在编译自己的代码时定义 INET 和 INET6(尤其是 INET6)可以解决问题。

于 2015-02-12T07:56:40.533 回答