You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UNIX网络编程中SCTP接收重复数据包问题求助

SCTP客户端重复接收首个数据包问题排查

问题背景

在UNIX网络编程的SCTP应用场景中,实现了客户端与回显服务器:

  • 携带echo参数启动客户端时,会调用sctpstr_cli_echoall函数,向服务器所有可用流各发送一条消息
  • 服务器将收到的数据按原流回显给客户端
  • 运行后客户端输出显示首个数据包被重复接收

客户端代码

#include "unp.h"

int main(int argc, char **argv)
{
    int sock_fd;
    struct sockaddr_in servaddr;
    struct sctp_event_subscribe evnts;
    int echo_to_all = 0;

    if (argc < 2)
        err_quit("Missing host argument - use '%s host [echo]'\n",
                 argv[0]);
    if (argc > 2)
    {
        printf("Echoing messages to all streams\n");
        echo_to_all = 1;
    }
    sock_fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

    bzero(&evnts, sizeof(evnts));
    evnts.sctp_data_io_event = 1;
    Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS,
               &evnts, sizeof(evnts));
    if (echo_to_all == 0)
        sctpstr_cli(stdin, sock_fd, (SA *)&servaddr, sizeof(servaddr));
    else
        sctpstr_cli_echoall(stdin, sock_fd, (SA *)&servaddr, sizeof(servaddr));
    Close(sock_fd);
    return (0);
}

客户端多流发送接收函数:sctpstr_cli_echoall

void sctpstr_cli_echoall(FILE *fp, int sock_fd, struct sockaddr *to, socklen_t tolen)
{
    struct sockaddr_in peeraddr;
    struct sctp_sndrcvinfo sri;
    char sendline[SCTP_MAXLINE], recvline[SCTP_MAXLINE];
    socklen_t len;
    int rd_sz, i, strsz;
    int msg_flags;

    bzero(sendline, sizeof(sendline));
    bzero(&sri, sizeof(sri));
    while (fgets(sendline, SCTP_MAXLINE - 9, fp) != NULL)
    {
        strsz = strlen(sendline);
        if (sendline[strsz - 1] == '\n')
        {
            sendline[strsz - 1] = '\0';
            strsz--;
        }
        for (i = 0; i < SERV_MAX_SCTP_STRM; i++)
        {
            snprintf(sendline + strsz, sizeof(sendline) - strsz,
                     ".msg.%d", i);
            Sctp_sendmsg(sock_fd, sendline, sizeof(sendline),
                         to, tolen,
                         0, 0,
                         i,
                         0, 0);
        }
        for (i = 0; i < SERV_MAX_SCTP_STRM; i++)
        {
            len = sizeof(peeraddr);
            rd_sz = Sctp_recvmsg(sock_fd, recvline, sizeof(recvline),
                                 (SA *)&peeraddr, &len,
                                 &sri, &msg_flags);
            printf("From str:%d seq:%d (assoc:0x%x):",
                   sri.sinfo_stream, sri.sinfo_ssn,
                   (unsigned int)sri.sinfo_assoc_id);
            printf("%.*s\n", rd_sz, recvline);
        }
    }
}

服务器相关代码(注:用户提供的服务器主逻辑存在粘贴重复,以下为有效代码片段)

// 获取服务器可用流数量的函数
int sctp_get_no_strms(int sock_fd, sctp_assoc_t sinfo_assoc_id)
{
    int retsz;
    struct sctp_status status;
    retsz = sizeof(status);
    bzero(&status, sizeof(status));

    status.sstat_assoc_id = sinfo_assoc_id;
    Getsockopt(sock_fd, IPPROTO_SCTP, SCTP_STATUS,
               &status, &retsz);
    return (status.sstat_outstrms);
}

// 正确的服务器回显逻辑参考(用户提供的主逻辑重复了客户端函数,需替换为此类代码)
/*
void sctp_echo(int sock_fd, struct sockaddr *cliaddr, socklen_t clilen)
{
    struct sctp_sndrcvinfo sri;
    char buf[SCTP_MAXLINE];
    int rd_sz, msg_flags;
    socklen_t len;

    for ( ; ; ) {
        len = clilen;
        rd_sz = Sctp_recvmsg(sock_fd, buf, sizeof(buf), cliaddr, &len, &sri, &msg_flags);
        if (rd_sz == 0)
            break;
        Sctp_sendmsg(sock_fd, buf, rd_sz, cliaddr, len,
                     sri.sinfo_ppid, sri.sinfo_flags,
                     sri.sinfo_stream, 0, 0);
    }
}
*/

问题排查与修复方案

核心原因分析

  1. 未过滤SCTP通知消息:客户端开启了sctp_data_io_event事件订阅,首次建立SCTP关联时,系统会发送关联建立通知(如SCTP_COMM_UP),当前代码未区分通知消息与用户数据,将通知误当成回显数据处理,造成“重复接收”假象。
  2. 硬编码流数量不匹配:客户端使用SERV_MAX_SCTP_STRM硬编码流数量,若与服务器实际支持的流数量不一致,会导致接收循环次数与发送消息数不匹配。
  3. 服务器代码逻辑错误:用户提供的服务器主逻辑重复了客户端函数,实际运行时若服务器存在重复发送逻辑,也会导致客户端重复接收。

修复步骤

  1. 过滤SCTP通知消息:在客户端接收循环中增加标志位判断,跳过非数据消息:
rd_sz = Sctp_recvmsg(sock_fd, recvline, sizeof(recvline),
                     (SA *)&peeraddr, &len,
                     &sri, &msg_flags);
// 跳过SCTP通知消息
if (msg_flags & MSG_NOTIFICATION) {
    continue;
}
  1. 动态获取服务器流数量:替换硬编码的流数量,使用sctp_get_no_strms获取实际可用流数:
// 在发送前获取服务器实际流数量
int num_streams = sctp_get_no_strms(sock_fd, 0); // 0表示当前关联
// 发送循环
for (i = 0; i < num_streams; i++) {
    // 原发送逻辑
}
// 接收循环
for (i = 0; i < num_streams; i++) {
    // 原接收逻辑(加上通知过滤)
}
  1. 修正服务器代码:确保服务器使用正确的回显逻辑,仅在收到用户数据时按原流回显,无重复发送操作。

内容的提问来源于stack exchange,提问作者raijin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 07:24:53