标签 linux 下的文章

“”

经常使用UDP发送数据,但一般都是小包,没有太关注过发送大包的问题。突然要发个大包,c++ udp send big packet。
就tcpdump wireshark抓包看了一下。
结果在tcpdump中,出现了这个东西。
UDP, bad length 5455 > 1472
看来UDP包长度超过MTU了。应该是分片了,但分片就分片,只要网络可靠,应该都没什么问题。

另外就是怕UDP的接收或者发送缓冲区不够了。

udp setsockopt sendbuf example

还有多网卡的问题。直接指定网卡,这个要用到struct ifreq.这是linux下的用法。
windows好像没有这个SO_BINDTODEVICE选项,不过可以使用静态路由的方式。可以参考这个链接:
多网卡指定网卡进行UDP通信(添加静态路由解决双网卡问题 )全记录
最后代码如下:

注意 struct ifreq 头文件如下: struct ifreq header file

#include <sys/ioctl.h>
#include <net/if.h>

int cudp::udp_send(string ip, int port, string data)
{
    printf("udp_send ip=%s,port=%d\n",ip.c_str(),port);
    int m_nsockfd;
    if ((m_nsockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        printf("udp_send error.\n");
        return -1;
    }
    struct ifreq ifr;
    memset(&ifr, 0x00, sizeof(ifr));
    strncpy(ifr.ifr_name, "eth0", 4);
    setsockopt(m_nsockfd, SOL_SOCKET, SO_BINDTODEVICE, (char *)&ifr, sizeof(ifr));

    int addrlen = sizeof(sockaddr_in);
    struct sockaddr_in addr;
    bzero(&addr, sizeof(struct sockaddr_in));    
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    int sendbuff;
    socklen_t optlen;
    int res = 0;

    // Get buffer size
    optlen = sizeof(sendbuff);
    res = getsockopt(m_nsockfd, SOL_SOCKET, SO_SNDBUF, &sendbuff, &optlen);

    if(res == -1)
        printf("Error getsockopt one");
    else
        printf("send buffer size = %d\n", sendbuff);

    sendbuff = 50*1024;

    printf("sets the send buffer to %d\n", sendbuff);
    res = setsockopt(m_nsockfd, SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff));

    if(res == -1)
        printf("Error setsockopt");
        
    int ret = sendto(m_nsockfd, data.data(), data.length(), 0, (struct sockaddr *)&addr, addrlen);
    printf("sendto ret = %d\n", ret);
    return ret;
}

linux下可以直接拿来使用,用第一个示例就好了。

注意 struct ifreq 头文件如下: struct ifreq header file

#include <sys/ioctl.h>
#include <net/if.h>

这是代码的方法,肯定也有指定路由的办法。有兴趣的话可以了解一下。大致思路就是使用

route add ...
  1. example(TCP, UDP, RAW):

    int sock;
    struct ifreq ifr;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    memset(&ifr, 0x00, sizeof(ifr));
    strncpy(ifr.ifr_name, "eth0", IFNAMSIZE);
    setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (char *)&ifr, sizeof(ifr));
    
  2. example(PACKET):

    int sock;
    struct sockaddr_ll sl;
    struct ifreq ifr;
    sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6));
    memset(&sl, 0x00, sizeof(sl));
    memset(&ifr, 0x00, sizeof(ifr));
    sl.sll_family = AF_PACKET;
    sl.sll_protocol = htons(ETH_P_IPV6);
    strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));
    ioctl(fd, SIOCGIFINDEX, &ifr);
    sl.sll_ifindex = ifr.ifr_ifindex;
    bind(fd, (struct sockaddr *)&sl, sizeof(sl));
    
  3. example(PACKET):

    int sock;
    struct sockaddr addr;
    sock = socket(PF_PACKET, SOCK_PACKET, ETH_P_IP);
    memset(&addr, 0x00, sizeof(addr));
    addr.sa_family = PF_PACKET;
    strncpy(addr.sa_data, "eth0", sizeof(addr.sa_data));
    bind(sock, &addr, sizeof(addr));
    

SO_BINDTODEVICE套接口选项说明:
SO_BINDTODEVICE
Bind  this  socket  to  a particular device like “eth0”, as specified in the passed interface name.  If the name is an empty string or the option length is zero, the socket device binding  is  removed.   The  passed option  is a variable-length null-terminated interface name string with the maximum size of IFNAMSIZ.  If a socket is bound to an interface, only packets received from that particular interface are processed by  the socket.  Note  that  this  only works for some socket types, particularly AF_INET sockets.  It is not supported for packet sockets (use normal bind(8) there).
Referenced from:https://blog.csdn.net/networkangle/article/details/52549758?tdsourcetag=s_pcqq_aiomsg

c++ udp send big packet

UDP datagram 大小限制 65,535 bytes

测试socket udp 单次传输数据上限(sendto()函数data不能超过65507字节。udp头占8字节,ip头占20字节,加起来正好65535字节)

void udp_test()
{
    char *p = new char[65507];
    string strsend;
    strsend.assign(p, 65507);
    int ret = udp_send("127.0.0.1", 8888, strsend);
    printf("udp_send ret=%d\n", ret);
    delete[] p;
}

udp_send ip=127.0.0.1,port=8888
send buffer size = 163840
sendto ret = 65507
udp_send ret=65507

void udp_test()
{
    char *p = new char[65508];
    string strsend;
    strsend.assign(p, 65508);
    int ret = udp_send("127.0.0.1", 8888, strsend);
    printf("udp_send ret=%d\n", ret);
    delete[] p;
}

udp_send ip=127.0.0.1,port=8888
send buffer size = 163840
sendto ret = -1
udp_send ret=-1

UDP仅提供数据报作为IP数据包的数据部分,IP数据包具有16位长度的字段,因此将数据限制为2 ^ 16字节(包括标题),或UDP数据部分的65507字节(假设没有ipv4选项) ,除了用UDP处理更大的数据包外,除了将它们分成几个数据包并自行处理重组等之外,没有其他方法。

UDP规范在UDP报头中为您提供16位的数据包大小,这意味着您一次发送的字节数不能超过65k。您必须将数据拆分为多个数据包。使用TCP代替UDP将使事情变得更加简单,因为可以确保完整性和接收顺序。

linux socket 设置缓冲区大小时内核会翻倍
示例代码:

#include <sys/socket.h>
#include <stdio.h>

int main(int argc, char **argv)
{
 int sockfd, sendbuff;
 socklen_t optlen;

 sockfd = socket(AF_INET, SOCK_DGRAM, 0);
 if(sockfd == -1)
     printf("Error");

 int res = 0;

 // Get buffer size
 optlen = sizeof(sendbuff);
 res = getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuff, &optlen);

 if(res == -1)
     printf("Error getsockopt one");
 else
     printf("send buffer size = %d\n", sendbuff);

 // Set buffer size
 sendbuff = 98304;

 printf("sets the send buffer to %d\n", sendbuff);
 res = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff));

 if(res == -1)
     printf("Error setsockopt");


 // Get buffer size
 optlen = sizeof(sendbuff);
 res = getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuff, &optlen);

 if(res == -1)
     printf("Error getsockopt two");
 else
     printf("send buffer size = %d\n", sendbuff);

 return 0;
}

运行结果:

send buffer size = 129024
sets the send buffer to 98304
new send buffer size = 196608

官方手册说明

SO_SNDBUFSets or gets the maximum socket send buffer in bytes. The ker-nel doubles this value (to allow space for bookkeeping overhead) when it is set using setsockopt(), and this doubled value is returned by getsockopt(). The default value is set by the wmem_default sysctl and the maximum allowed value is set by the wmem_max sysctl. The minimum (doubled) value for this option is 2048.

编译结果

./protoc --version

libprotoc 3.17.3

git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf
git submodule update --init --recursive
mkdir x64
cd x64
../configure --prefix=$(pwd)/install
make
make install

运行编译好的程序

./install/bin/protoc --version

libprotoc 3.17.3
查看所有库文件

tree install/lib/

install/lib/
├── libprotobuf.a
├── libprotobuf.la
├── libprotobuf-lite.a
├── libprotobuf-lite.la
├── libprotobuf-lite.so -> libprotobuf-lite.so.28.0.3
├── libprotobuf-lite.so.28 -> libprotobuf-lite.so.28.0.3
├── libprotobuf-lite.so.28.0.3
├── libprotobuf.so -> libprotobuf.so.28.0.3
├── libprotobuf.so.28 -> libprotobuf.so.28.0.3
├── libprotobuf.so.28.0.3
├── libprotoc.a
├── libprotoc.la
├── libprotoc.so -> libprotoc.so.28.0.3
├── libprotoc.so.28 -> libprotoc.so.28.0.3
├── libprotoc.so.28.0.3
└── pkgconfig

├── protobuf-lite.pc
└── protobuf.pc

编译arm版本protobuf库

cd ../
mkdir arm
cd arm/
CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ ../configure --prefix=$(pwd)/install --host=arm-linux
make -j8
make install
file ./install/bin/protoc 

./install/bin/protoc: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=cea786b94a0b64b866983c8b4541df34e9cc7c1d, with debug_info, not stripped

protobuf/arm$ tree ./install/lib/

./install/lib/
├── libprotobuf.a
├── libprotobuf.la
├── libprotobuf-lite.a
├── libprotobuf-lite.la
├── libprotobuf-lite.so -> libprotobuf-lite.so.28.0.3
├── libprotobuf-lite.so.28 -> libprotobuf-lite.so.28.0.3
├── libprotobuf-lite.so.28.0.3
├── libprotobuf.so -> libprotobuf.so.28.0.3
├── libprotobuf.so.28 -> libprotobuf.so.28.0.3
├── libprotobuf.so.28.0.3
├── libprotoc.a
├── libprotoc.la
├── libprotoc.so -> libprotoc.so.28.0.3
├── libprotoc.so.28 -> libprotoc.so.28.0.3
├── libprotoc.so.28.0.3
└── pkgconfig

├── protobuf-lite.pc
└── protobuf.pc

使用git 直接git clone github的库,不会报googletest 相关的警告,自动下载好了。如果自己在github.com protobuf release哪儿下载的放在,编译时候会报一个没有googletest的警告。好烦。。

configure: WARNING: no configuration information is in third_party/googletest