# socket 选项

用于查看或设置 socket 的参数和状态

1
2
3
4
5
6
7
8
9
10
11
12
int getsockopt(int sockfd, int level, int optname,char* optval,int* oplen);
int setsockopt(int sockfd, int level, int optname,char* optval,int* oplen);
/**
sockfd 套接字
level 选项所在协议层
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项. 
optname 选项名称
optval 选项首地址
optlen 选项值的缓冲区长度
*/

# 应用层选项 SO_

# SO_BROADCAST

打开或关闭广播和组播功能

选项为 1 表示打开广播,默认 0 表示关闭广播

# 广播基本原理

广播 (Broadcast) 是指封包在计算机网络中传输时,目的地址为网络中所有设备的一种传输方式。这里所说的 “所有设备” 也被限定在一个范围之中,这个范围被称为 “广播域”。

直接广播(Directed Broadcast)用于向特定区域内 (已知目标主机网络地址) 主机传输数据。使用上,若已知目标主机 IP 地址及子网掩码为 192.168.1.0/24,那么广播地址即为 192.168.1.255。(需要注意的是,255.255.255.255 为受限广播地址,不能被使用),按照该地址发送数据包时,路由器将会把数据包发送给 192.168.1.1 ~ 192.168.1.254 下所有主机。

本地广播(Limited Broadcast)用于本地网络内的通信 (只能用于局域网下)。无论特定主机位于何种 IP 网络上,当前的主机始终可以使用 255.255.255.255 这个地址向本地网络上的每个节点发送数据包。比如,在 192.168.0 网段下的任意一台主机,向 255.255.255.255 发送数据包,那么在 192.168.0 网段下所有的主机将会收到该数据包,同时,该数据包也不会被转发到其他网段去。

# 广播实现

  1. 发送方

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #include <winsock2.h>
    #include <windows.h>
    #include "stdio.h"
    #pragma comment(lib, "ws2_32.lib")
    #define PORT 8080
    #define BROADCAST_ADDR "255.255.255.255"
    int main()
    {
    // 初始化DLL
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    struct sockaddr_in addr;
    char buf[1024];

    // 创建UDP套接字
    SOCKET sendfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sendfd < 0)
    {
    perror("socket");
    exit(1);
    }

    // 设置广播选项
    int opt = 1;
    setsockopt(sendfd, SOL_SOCKET, SO_BROADCAST, (char *)&opt, sizeof(opt));

    // 设置广播地址
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    inet_pton(AF_INET, BROADCAST_ADDR, &addr.sin_addr);

    // 广播发送消息
    if (sendto(sendfd, buf, strlen(buf), 0, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
    perror("sendto");
    exit(1);
    }

    // 关闭套接字
    close(sendfd);

    return 0;
    }

  2. 接收方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <winsock2.h>
#include <windows.h>
#include "stdio.h"
#pragma comment(lib, "ws2_32.lib")
#define PORT 8080

int main()
{

// 初始化DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

struct sockaddr_in addr;
char buf[1024];

// 创建UDP套接字
SOCKET recvfd = socket(AF_INET, SOCK_DGRAM, 0);
if (recvfd < 0)
{
perror("socket");
exit(1);
}

// 设置接收方地址
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 绑定套接字
if (bind(recvfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind");
exit(1);
}

// 接收消息
while (1)
{
int len = recvfrom(recvfd, buf, sizeof(buf), 0, NULL, 0);
if (len < 0)
{
perror("recvfrom");
exit(1);
}
buf[len] = '\0';
printf("recv msg from broadcast: %s\n", buf);
}

// 关闭套接字
close(recvfd);
}

# 组播技术

组播是通过 D 类 IP 地址进行的。D 类地址的前 4 位为 1110,后面 28 位为组播的组 标识。因此,D 类地址的范围是 224.0.0.0 到 239.255.255.255。由于 IP 地址是整个因特网通用的,因此组播地址也有一些 “周知” 地址,这些地址只允许特定的应用。 下面列出一 些 “周知” 的组播地址:

地址用途
224.0.0.0保留,不分配给任何组
224.0.0.1本子网上所有主机
224.0.0.2本子网上所有网关
224.0.1.1NTP (网络时间协议) 组

组播技术步骤

  • 加入一个组播组。
  • 在指定的端口上进行侦听。
  • 接收并处理到达的组播数据报。
  • 离开该组播组。
  • 关闭句柄。

# 组播选项

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct ip_mreq {
struct in_addr imr_multiaddr;//加入到的组播地址
struct in_addr imr_interface;//本机地址
}
/**
IPv4选项
IP_ADD_MEMBERSHIP struct ip_mreq 加入到组播组中
IP_DROP_MEMBERSHIP struct ip_mreq 从组播组中退出
IP_MULTICAST_IF struct ip_mreq 指定提交组播报文的接口
IP_MULTICAST_TTL u_char 指定提交组播报文的TTL
IP_MULTICAST_LOOP u_char 使组播报文环路有效或无效
*/

# 组播实现

  1. 发送方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <ws2tcpip.h>
#include <ws2ipdef.h>
#include <sys/types.h>

#pragma comment(lib, "ws2_32.lib")
#define PORT 1234
#define MULTICAST_GROUP "224.0.0.1"

int main()
{
// 初始化DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

struct sockaddr_in addr;
char buf[1024];

// 创建UDP套接字
SOCKET sendfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sendfd < 0)
{
perror("socket");
exit(1);
}

// 设置组播选项
int opt = 1;
setsockopt(sendfd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&opt, sizeof(opt));

// 设置组播地址
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_pton(AF_INET, MULTICAST_GROUP, &addr.sin_addr);

// 组播发送消息
if (sendto(sendfd, buf, strlen(buf), 0, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("sendto");
exit(1);
}

// 关闭套接字
close(sendfd);

return 0;
}

  1. 接收方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define PORT 1234
#define MULTICAST_GROUP "224.0.0.1"

int main()
{
// 初始化DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

struct sockaddr_in local_addr;
char buf[1024];

// 设置要加入组播的地址
struct ip_mreq mreq;

// 创建UDP套接字
SOCKET recvfd = socket(AF_INET, SOCK_DGRAM, 0);
if (recvfd < 0)
{
perror("socket");
exit(1);
}

// 设置组播地址
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(PORT);
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 绑定套接字到本地地址
if (bind(recvfd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0)
{
perror("bind");
exit(1);
}

// 加入到组播组
inet_pton(AF_INET, MULTICAST_GROUP, &mreq.imr_multiaddr);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(recvfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq));

// 接收消息
while (1)
{
int len = recvfrom(recvfd, buf, sizeof(buf), 0, NULL, 0);
if (len < 0)
{
perror("recvfrom");
exit(1);
}
buf[len] = '\0';
printf("recv msg from multicast: %s\n", buf);
}

// 退出组播组
setsockopt(recvfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&mreq, sizeof(mreq));

// 关闭套接字
close(recvfd);

return 0;
}

# SO_DEBUG

TCP 套接宇支持此选项。对于一个 TCp 套接字,当设置这个选项时,内核将保存这个套接宇使用 TCP 协议发迭和接收的所有数据包的详细信息,它们将被保存在内核的一个循环缓冲区中。这个选项是标志选项。

#

# SO_DONTROUTE

这个选项指定发送数据包时不使用通常的 ip 协议路由选择机制,而是直接根据目的地址的网络地址来选择合适的网络设备接口。当找不到匹配的接口时,如目标机器不是点到点连接的另一端,或者没有在同一个广擂类型网络上,发送函数将以错误返回,错误类型为 ENETUNREACH。这个选项是标志选项 。这个选项通常由路由器在不使用路由表的情况下,将数据包从某个接口发送出去。

# SO_ERROR

当套接字发生异步错误时,内核将在这个套接字的一个变量 so_error 中设置这个错误。这个错误被称为套接宇未处理错误。例如,当 TCP 套接字调用函数 connect 建立连接时,如果这个连接因为低层的 TCP 协议发现连接请求的服务无效而失败的话,TCP 协议将这个变量设置为 ECONNREFUSED.

# SO_KEEPALIVE

当在 TCP 套接字上设置了 SO-KEEPALIVE 选项时,如果两个小时之内,在套接宇的任何方向上都没有进行过数据的交换,TCP 协议将自动发送一个 “保持活动探测数据段至对方机器。对方主机 TCP 协议必须处理这个数据段。

  • 对方接收一切正常:以期望的 ACK 响应。2 小时后,TCP 将发出另一个探测分节。
  • 对方已崩溃且已重新启动:以 RST 响应。套接口的待处理错误被置为 ECONNRESET,套接 口本身则被关闭。
  • 对方无任何响应:源自 berkeley 的 TCP 发送另外 8 个探测分节,相隔 75 秒一个,试图得到一个响应。一共尝试 9 次,即在发出第一个探测分节 11 分钟 15 秒后若仍无响应就放弃。套接口的待处理错误被置为 ETIMEOUT,套接口本身则被关闭。如 ICMP 错误是 “host unreachable (主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。

# SO_LINGER

SO_LINGER 选项用来设置延迟关闭的时间,等待套接字发送缓冲区中的数据发送完成。

没有设置该选项时,在调用 close () 后,在发送完 FIN 后会立即进行一些清理工作并返回。如果设置了 SO_LINGER 选项,并且等待时间为正值,则在清理之前会等待一段时间。

# SO_RCVBUF 和 SO_SNDBUF

每个套接字维护一个发送缓冲区和一个接收缓冲区。TCP 和 UDP 协议用接收缓冲区来存放接收的数据,到达的数据首先被保存在这个缓冲区中,当应用程序读取数据时,将这些数据从这个缓冲区拷贝到用户缓冲区中.在 TCP 协议发送的每个数据包的窗口域中存放有套接字的接收缓冲区的可用空间大小,通过将套接字接收缓冲区的可用空间 大小通知另一端的 TCP 协议,从而实现流量控制.

# SO_RCVLOWAT 和 SO_SNDLOWAT

每个套接字都有一个接收下限标记和一个发送上限标记。函数 select 使用这两个标志。这两个套接字选项使我们能够改变这两个标记的值。

# SO_RCVTIMEO 和 SO_SNDTIMEO

这两个套接字选项使我们能够在套接字接收和发送数据时设置一个超时值。这个选项的值是一个指向 timeval 结构的指针。如果超时值设置为 0 秒和 0 毫秒,则超时被禁止。默认情况下,禁止超时。

# SO_REUSEADDR

在以下 3 种情况可以设置选项 SO_REUSEADDR:

  1. 快速重新启动服务器

    当终止服务器时,连接套接字将在 TIME—WAIT 状态保持 MSL 时间的两倍,这些连接仍然使用服务器的公认端口号作为它们的本地端口号,默认情况下,服务器重新启动时,函数调用 bind 将失败。套接字选项 SO—REUSEADDR 允许服务器重新启动并绑定它的公认端口号。通常在如下操作之后,会遇到这种情况:

①一个服务器启动。

②一个连接请求到达,服务器产生一个子进程来处理这个客户机请求。

③服务器终止,但是子进程尚未完成对客户机请求的处理。

④服务器重新启动。

  1. 启动一个服务器的多个实例

选项 SO—REUSEADDR 允许同一个服务器的多个实例在同一个端口号上启动,只要每个实例绑定不同的本地 IP 地址。