IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 网络编程——套接字的多种可选项 -> 正文阅读

[网络协议]网络编程——套接字的多种可选项

参考

  1. 《TCP/IP网络编程》 尹圣雨

套接字多种可选项

在这里插入图片描述

上表中列出了一部分套接字可选项。IPPROTO_IP层可选项是IP协议相关事项,IPPROTO_TCP层可选项是TCP协议相关事项,SOL_SOCKET层是套接字相关的通用可选项

对可选项进行读取(Get)

#include <sys/socket.h>

int getsockopt(int sock, int level, int optname, void* optval, socklen_t* optlen);

成功时返回0,失败时返回-1
(1)sock
用于查看选项套接字文件描述符

(2)level
要查看的可选项的协议层

(3)optname
要查看的可选项名

(4)optval
保存查看结果的缓冲地址值

(5)optlen
向第四个参数optval传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数

查看套接字类型示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
void error_handling(char* message);

int main(int argc, char* argv[])
{
    int tcp_sock, udp_sock;
    int sock_type;
    socklen_t optlen;
    int state;

    optlen = sizeof(sock_type);
    tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
    udp_sock = socket(PF_INET, SOCK_DGRAM, 0);
    printf("SOCK_STREAM: %d \n", SOCK_STREAM);
    printf("SOCK_DGRAM: %d \n", SOCK_DGRAM);

    state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    if (state)
    {
        error_handling("getsockopt() error!");
    }
    printf("Socket type two: %d \n", sock_type);

    state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    if (state)
    {
        error_handling("getsockopt() error!");
    }
    printf("Socket type two: %d \n", sock_type);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

对可选项进行设置(Set)

#include <sys/socket.h>

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

成功时返回0,失败时返回-1
(1)sock
用于更改可选项的套接字文件描述符

(2)level
要更改的可选项协议层

(3)optname
要更改的可选项名

(4)optval
保存要更改的选项信息的缓冲地址值

(5)optlen
向第四个参数optval传递的可选项信息的字节数

查看和修改I/O缓冲大小(SO_SNDBUF和SO_RCVBUF)

SO_RCVBUF是输入缓冲大小相关可选项,SO_SNDBUF是输出缓冲大小可选项

查看创建套接字时默认的I/O缓冲大小

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
void error_handling(char* message);

int main(int argc, char* argv[])
{
    int sock;
    int snd_buf, rcv_buf, state;
    socklen_t len;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    len = sizeof(snd_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
    if (state)
    {
        error_handling("getsockopt() error");
    }

    len = sizeof(rcv_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
    if (state)
    {
        error_handling("getsockopt() error");
    }
    printf("Input buffer size: %d \n", rcv_buf);
    printf("Output buffer size: %d \n", snd_buf);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

修改I/O缓冲大小

缓冲大小的设置需谨慎处理,因此不会完全按照我们的要求进行

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
void error_handling(char* message);

int main(int argc, char* argv[])
{
    int sock;
    int snd_buf = 1024*3, rcv_buf = 1024*3;
    int state;
    socklen_t len;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, sizeof(rcv_buf));
    if (state)
    {
        error_handling("setsockopt() error!");
    }

    state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, sizeof(snd_buf));
    if (state)
    {
        error_handling("setsockopt() error!");
    }
    // 验证I/O缓冲的修改
    len = sizeof(snd_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
    if (state)
    {
        error_handling("getsockopt() error!");
    }

    len = sizeof(rcv_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
    if (state)
    {
        error_handling("getsockopt() error!");
    }

    printf("Input buffer size: %d \n", rcv_buf);
    printf("Output buffer size: %d \n", snd_buf);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

SO_REUSEADDR

Time-wait状态

通常都是由客户端先请求断开连接,但如果服务器端先向客户端发送FIN消息,那么服务器端重新运行时将产生问题。如果用同一端口号重新运行服务器端,将输出“bind() error”消息,并且无法再次运行。但在这种情况下,再过大约3分钟即可重新运行服务器端

造成这一结果的原因是服务器端处于Time-wait状态。套接字经过四次握手过程后并非立即消除,而是要经过一段时间的Time-wait状态。只有先断开连接(先发送FIN消息的)主机才经过Time-wait状态。因此,若服务器端先断开连接,则无法立即重新运行。套接字处在Time-wait过程时,相应端口是正在使用的状态。因此,bind函数调用过程中会发生错误

实际上,不管是服务器端还是客户端,套接字都会有Time-wait过程。先断开连接的套接字必然会经过Time-wait过程。但无需考虑客户端Time-wait状态。因为客户端套接字的端口号是任意指定的。与服务器端不同,客户端每次运行程序时都会动态分配端口号,因此无需过多关注Time-wait状态

地址再分配

考虑系统发生故障从而紧急停止的情况。这时需要尽快重启服务器端以提供给服务,但因处于Time-wait状态而必须等到几分钟。解决方案是在套接字的可选项中更改SO_REUSEADDR的状态。SO_REUSEADDR的默认值为0,这就意味着无法分配Time-wait状态下的套接字端口号,因此需要将这个值设为1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define TRUE 1
#define FALSE 0
void error_handling(char* message);

int main(int argc, char* argv[])
{
    int serv_sock, clnt_sock;
    char message[30];
    int option, str_len;
    socklen_t optlen, clnt_adr_sz;
    struct sockaddr_in serv_adr, clnt_adr;
    if (argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
        error_handling("socket() error");
    }

    optlen = sizeof(option);
    option = TRUE;
    setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&option, option);

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)))
    {
        error_handling("bind() error");
    }
    if (listen(serv_sock, 5) == -1)
    {
        error_handling("listen error");
    }
    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    
    while ((str_len = read(clnt_sock, message, sizeof(message))) != 0)
    {
        write(clnt_sock, message, str_len);
        write(1, message, str_len);
    }
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

TCP_NODELAY

Nagle算法

Nagle算法应用于TCP层,防止因数据过多而发生网络过载。只有收到前一数据的ACK消息时,Nagle算法才发送下一数据

TCP套接字默认使用Nagle算法交换数据,因此最大限度地进行缓冲,直到收到ACK。未使用Nagle算法的发送过程与ACK解释与否无关,因此数据到达输出缓冲后将立即被发送出去。不使用Nagle算法将对网络流量(Traffic:指网络负载或混乱程度)产生负面影响

但网络流量未受太大影响时,不使用Nagle算法要比使用它时传输速度快。一般情况下,不使用Nagle算法可以提高传输速度。但如果无条件放弃使用Nagle算法,就会增加过多的网络流量,反而会影响传输。因此,未准确判断数据特性时不应禁用Nagle算法

禁用Nagle算法

禁用Nagle只需将套接字可选项TCP_NODELAY改为1即可:

int opt_val = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, sizeof(opt_val));

可以通过TCP_NODELAY的值查看Nagle算法的设置状态:

int opt_val;
socklen_t opt_len;
opt_len = sizeof(opt_val);
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, &opt_len);

基于Windows的实现

在Windows平台与Linux平台下并无区别

对可选项进行读取

#include <WinSock2.h>

int getsockopt(SOCKET sock, int level, int optname, char* optval, int* optlen);

成功时返回0,失败时返回SOCKET_ERROR
(1)sock
要查看可选项的套接字句柄
(2)level
要查看的可选项协议层
(3)optname
要查看的可选项名
(4)optval
保存查看结果的缓冲地址值
(5)optlen
向第四个参数optval传递的缓冲大小。调用结束后,该变量中保存通过第四个参数返回的可选项字节数

对可选项进行修改

#include <WinSock2.h>

int setsockopt(SOCKET sock, int level, int optname, const char* optval, int optlen);

成功时返回0,失败时返回SOCKET_ERROR
(1)sock
要更改可选项的套接字句柄
(2)level
要更改的可选项协议层
(3)optname
要更改的可选项名
(4)optval
保存要更改的可选项信息的缓冲地址值
(5)optlen
传入第四个参数optval的可选项信息的字节数

修改并查看I/O缓冲大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
void ErrorHandling(char* message);
void ShowSocketBufSize(SOCKET sock);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSock;
	int sndBuf, rcvBuf, state;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() error!");
	}

	hSock = socket(PF_INET, SOCK_STREAM, 0);
	ShowSocketBufSize(hSock);

	sndBuf = 1024 * 3;
	rcvBuf = 1024 * 3;
	state = setsockopt(hSock, SOL_SOCKET, SO_SNDBUF, (char*)&sndBuf, sizeof(sndBuf));
	if (state == SOCKET_ERROR)
	{
		ErrorHandling("setsockopt() error!");
	}

	state = setsockopt(hSock, SOL_SOCKET, SO_RCVBUF, (char*)&rcvBuf, sizeof(rcvBuf));
	if (state == SOCKET_ERROR)
	{
		ErrorHandling("setsockopt() error!");
	}

	ShowSocketBufSize(hSock);
	closesocket(hSock);
	WSACleanup();
	return 0;
}

void ShowSocketBufSize(SOCKET sock)
{
	int sndBuf, rcvBuf, state, len;

	len = sizeof(sndBuf);
	state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sndBuf, &len);
	if (state == SOCKET_ERROR)
	{
		ErrorHandling("getsockopt() error");
	}

	len = sizeof(rcvBuf);
	state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcvBuf, &len);
	if (state == SOCKET_ERROR)
	{
		ErrorHandling("getsockopt() error");
	}

	printf("Input buffer size: %d \n", rcvBuf);
	printf("Output buffer size: %d \n", sndBuf);
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-10-30 12:49:11  更:2021-10-30 12:49:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:43:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码