socket选项

boost库的socket选项设置

Posted by chensong on 2019-02-02 00::57::46

前言

socket选项

正文

一, sockopt参数

设置socket的参数的函数

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname,
              void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
              const void *optval, socklen_t optlen);

1,sockopt的选项

level option name 数据类型 说明
  SO_BROADCAST int 允许发送广播数据报
  SO_DEBUG int 打开调试信息
  SO_REUSEADDR int 重用本地地址
  SO_TYPE int 获取socket类型
  SO_ERROR int 获取并清除socket错误状态
  SO_DONTROUTE int 不查看路由表,直接将数据发送给本地局域网内的主机。含义和send系统调用的MSG_DONTROUTE标志类似
  SO_RCVBUF int TCP接受缓冲区大小
  SO_SNDBUF int TCP发送缓冲区大小
  SO_KEEPALIVE int 发送周期性保活报文以维持连接
  SO_OOBINLINE int 接收到的带外数据将保留在普通数据的输入队列中(在线保留)
  SO_LINGER linger 若有数据待发送, 则延迟关闭
  SO_RCVLOWAT int TCP接收缓冲区低水位标记
  SO_SNDLOWAT int TCP发送缓冲区低水位标记
  SO_RCVTIMEO itmeval recv,recvmsg,accept [返回-1, 设置errno为EAGAIN或EWOULDBLOCK]
  SO_SNDTIMEO timeval send, sendmsg [返回-1, 设置errno为EAGAIN或EWOULDBLOCK], connect [[返回-1, 设置errno为EWOULDBLOCK]]
SOL_SOCKET(通用socket选项, 与协议无关) SO_USELOOPBACK 路由套接字取得所发送数据的副本  
  IP_TOS int 服务类型
IPPROTO_IPv4(IPV4选项) IP_TTL int 保活时间
  IPV6_NEXTHOP sockaddr_in6 下一跳IP地址
  IPV6_RECVPKTINFO int 接收分组信息
  IPV6_DONTFRAG int 禁止分片
IPPROTO_IPv6(IPv6选项) IPV6_RECVTCLASS int 接收通讯类型
  TCP_MAXSNG int TCP最大报文段大小
IPPROTO_TCP(TCP选项) TCP_NODELAY int 禁止Nagle算法

getsockopt和setsockopt这两个函数成功时返回0,失败时返回-1并且设置errno

对于服务器而言, 有部分socket选项只能在调用listen系统调用前针对监听socket设置的才有效。这是因为连接socket只能由accept调用返回,而accept从listen监听队列中接受的连接至少已经完成了TCP三次握手的前两个步骤(因为listen监听队列中的连接至少已进入SYN_RCVD状态),这说明服务器已经往被接受连接上发送出了TCP同步报文段。但有的socket选项却应该在TCP同步报文段中设置,比如TCP最大报文段选项。对这种情况,

linux给开放人员提供的解决方案是:

对于监听socket设置这些socket选项, 那么accept返回的连接socket将自动继承这些选项。 这些socket选项包含:

  1. SO_DEBUG
  2. SO_DONTROUTE
  3. SO_KEEPALIVE
  4. SO_LINGER
  5. SO_OOBINLINE
  6. SO_RCVBUF
  7. SO_RCVLOWAT
  8. SO_SNDBUF
  9. SO_SNDLOWAT
  10. TCP_MAXSEG
  11. TCP_NODELAY

client 而言 这些socket选项应该在connect函数之前设置, 因为connect调用成功返回之后, TCP三次握手已经完成了

二, SO_SNDBUF 和 SO_RECVBUF 选项

SO_SNDBUF 和 SO_RECVBUF 选项分别表示TCP发送缓冲区和接受缓冲区的大小。

要理解socket套接字全双工的工作原理

发送缓冲区和接受缓冲区的修改配置文件

/proc/sys/net/ipv4/tcp_wmem /proc/sys/net/ipv4/tcp_rmem

三个参数分别指定是缓冲区

  1. 最小值
  2. 默认值
  3. 最大值

测试程序

client

#include <sys/socket.h>
#include <arpa/inet.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <cstring>


#define   BUFFER_SIZE   512

int main(int argc, char *argv[])
{
  if (argc <= 2)
  {
    printf("usage: %s ip_address port_number send_bufer_seize\n", basename(argv[0]));
    return 1;
  }
  const char * ip = argv[1];
  
  int port = atoi(argv[2]);
  
  struct sockaddr_in server_address;
  bzero(&server_address, sizeof(server_address));
  server_address.sin_family = AF_INET;
  inet_pton(AF_INET, ip, &server_address.sin_addr);
  server_address.sin_port = htons(port);
  // create socket
  int sock = socket(PF_INET, SOCK_STREAM, 0);
  if(sock <= 0)
  {
    close(sock);
    printf("create socket error = %d\n", errno);
    return 0;
  }
  
  int sendbuf = atoi(argv[3]);
  int len = sizeof(sendbuf);
  
  // setting TCP SEDBUFF size read 
  setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof(sendbuf));
  getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, (socklen_t *)&len);
  printf("the tcp send buffer size after setting is %d\n", sendbuf);
  
  if (connect(sock, (struct sockaddr *)&server_address, sizeof(server_address)) != -1)
  {
    char buffer[BUFFER_SIZE];
    memset(buffer, 'a', BUFFER_SIZE);
    send(sock, buffer, BUFFER_SIZE, 0);
    
  }
  close(sock);
  return 0;
}

server

#include <sys/socket.h>
#include <arpa/inet.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <cstring>
#include <cerrno>


#define BUFFER_SIZE   1024


int main(int argc, char *argv[])
{
  if (argc <= 2)
  {
    printf("usage: %s ip_address port_number recv_buffer_seize\n", basename(argv[0]));
    return 1;
  }
  int ret = 0;
  const char * ip = argv[1];
  int port = atoi(argv[2]);
  
  struct sockaddr_in address;
  bzero(&address, sizeof(address));
  address.sin_family = AF_INET;
  inet_pton(AF_INET, ip, &address.sin_addr);
  address.sin_port = htons(port);
  
  
  // create socket
  int sock = socket(PF_INET, SOCK_STREAM, 0);
  if(sock <= 0)
  {
    close(sock);
    printf("create socket error = %d\n", errno);
    return 0;
  }
  
  int recvbuf = atoi(argv[3]);
  int len = sizeof(recvbuf);
  
  // setting TCP SEDBUFF size read 
  setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &recvbuf, sizeof(recvbuf));
  getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &recvbuf, (socklen_t *)&len);
  printf("the tcp recv buffer size after setting is %d\n", recvbuf);
  
  ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
  if (ret == -1)
  {
    close(sock);
    printf("bind socket error = %d\n", errno);
    return 1;
  }
  
  ret = listen(sock, 5);
  if (ret == -1)
  {
    close(sock);
    printf("listen socket error = %d\n", errno);
    return 1;
  }
  
  struct sockaddr_in client;
  socklen_t client_addrlength = sizeof(client);
  int connfd = accept(sock, (struct sockaddr*) &client, &client_addrlength);
  if (connfd < 0)
  {
    printf("errno is : %d\n", errno);
  }
  else
  {
    char buffer[BUFFER_SIZE];
    memset(buffer, '\0', BUFFER_SIZE);
    while(recv(connfd, buffer, BUFFER_SIZE - 1, 0) > 0) {}
    close(connfd);
  }
  
  close(sock);
  return 0;
}

开放端口

/sbin/iptables -I INPUT -p tcp –dport 9992 -j ACCEPT

tcpdump 抓包

tcpdump -nt -i ens33 port 9992

效果图片

结语