五:优雅断连 & 域名<=>IP & 套接字多种选项

这篇具有很好参考价值的文章主要介绍了五:优雅断连 & 域名<=>IP & 套接字多种选项。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 优雅地断开套接字连接

1.1 基于TCP的半关闭

TCP断开连接过程比建立连接过程更重要,因为连接过程一般不会出问题,但是断开连接过程有可能发生预想不到的情况,所以应该了解半关闭(Half-close)。

  • 单方面断开带来的问题
    Linux的close函数和Windows的closesocket函数意味着完全断开连接,既不能传输数据,也不能接收。因此,一些情况下,某一方单独断开连接显得不太优雅。例如:
    五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip
    主机A和主机B进行通信,A向B发送完数据后,调用close断开连接,此时A将无法在发送和接收数据,那么B发送给A的数据也只能销毁了。

  • 套接字和流
    两台主机通过套接字建立连接后进行可交换数据状态,又称“流形成的状态”。即可把建立套接字后可交换数据的状态看作一种流。流是单方向的,所以一个套接字有两个流(输入和输出)。
    五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip
    可以看到主机的输入流与另一主机的输出流相连,主机的输出流与另一主机的输入流相连。

  • 针对优雅断开的shutdown函数

#include <sys/socket.h>
/**
* @param[2] : howto 传递断开方式信息
* 	可选值如下:
* 	SHUT_RD :断开输入流,无法接收数据
* 	SHUT_WD :断开输出流,无法发送数据
* 	SHUT_RDWR:同时断开IO流
*/
int shutdown(int sock, int howto);

有了半关闭我们知道对方是否关闭便可以做出更加效率的操作,不用傻等对面消息了。

  • 基于半关闭的文件传输程序
五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip

file_server.c

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

#define BUF_SIZE 30  //C语言数组只能是常量,而不是const只读变量
void ErrorHandler(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    int servSd, clntSd;
    FILE* fp;
    char buf[BUF_SIZE];
    int readCnt;

    struct sockaddr_in servAddr, clntAddr;
    socklen_t clntAddrSz;

    if (argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    fp = fopen("file_server.c", "rb");
    servSd = socket(PF_INET, SOCK_STREAM, 0);

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

    bind(servSd, (struct sockaddr*)&servAddr, sizeof(servAddr));
    listen(servSd, 5);

    clntAddrSz = sizeof(clntAddr);
    clntSd = accept(servSd, (struct sockaddr*)&clntAddr, &clntAddrSz);

    while (1) {
        readCnt = fread((void*)buf, 1, BUF_SIZE, fp); 
        if (readCnt < BUF_SIZE) {
            write(clntSd, buf, readCnt);
            break;
        }
        write(clntSd, buf, BUF_SIZE); //传输文件数据
    }

    shutdown(clntSd, SHUT_WR);
    read(clntSd, buf, BUF_SIZE);
    printf("Messsage from client: %s \n", buf);

    fclose(fp);
    close(clntSd);
    close(servSd);
    return 0;
}

file_clinet.c

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

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sd;
	FILE *fp;
	
	char buf[BUF_SIZE];
	int read_cnt;
	struct sockaddr_in serv_adr;
	if(argc!=3) {
		printf("Usage: %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	fp=fopen("receive.dat", "wb");
	sd=socket(PF_INET, SOCK_STREAM, 0);   

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

	connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
	
	while((read_cnt=read(sd, buf, BUF_SIZE ))!=0) //直到收到EOF
		fwrite((void*)buf, 1, read_cnt, fp);
	
	puts("Received file data");
	write(sd, "Thank you", 10);
	fclose(fp);
	close(sd);
	return 0;
}

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

1.2 基于win的实现

windows平台同样使用shutdown函数完成半关闭,只是传递的参数名有所不同。

#include <winsock2.h>

/**
* @param[1]: 要断开的套接字句柄
* @param[2]: 断开方式
* 			SD_RECIEVE:断开输入流
* 			SD_SEND:	断开输出流
* 			SD_BOTH:	同时断开IO
* @return success: 0; fail: SOCKET_ERROR
*/
int shutdown(SOCKET sock, int howto);

链接: win实现

2 域名和网络系统

2.1 域名系统

DNS是对IP地址和域名进行香花转换的系统,其核心是DNS服务器。

什么是域名

      提供网络服务的服务器端也是通过IP地址进行区分的,但是几乎不可能以非常难记的IP地址形式交换服务器端地址信息。因此,将容易记、易表述的域名分配并取代IP地址。

DNS服务器

    在浏览器地址栏输入Naver网站的IP地址22.122.195.5即可浏览Naver网站主页。但我们通常输入Naver网站的域名www.naver.com访问网站。二者之间有何区别?
    从进入Naver网站主页这一结果看,没有区别,但是接入过程不同。域名是赋予服务器端的虚拟地址,而非实际地址。因此需要将虚拟地址转化为实际地址。这时DNS便发挥作用。
    具体过程参考:链接: link

2.2 IP地址和域名系统之间的转换

域名系统必要性:IP地址比域名发生变更的概率要高

  1. 利用域名获取IP地址
#include <netdb.h>

/**
*@return 成功返回结构体指针,失败返回NULL指针
*/
struct hostent* gethostbyname(const char* hostname);

struct hostent
{
	char* h_name;  //官方域名
	char** h_aliases; //其他域名
	int h_addrtype;  //如果是IPv4,则变量存有AF_INET
	int h_length;    //保存IP地址长度,IPv4是4字节,IPv6是16字节 
	char** h_addr_list;  //以数组形式保存域名对应的IP地址
						//考虑到通用性,而不是只给IPv4用,所以采用char*而不是in_addr*, 
						//又因为此时void*还没标准化,所以采用char*更通用。
}
  • 获取百度ip的例子
    五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>

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

int main(int argc, char* argv[]) {
    struct hostent* host;
    if (argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    host = gethostbyname(argv[1]);
    if (!host) {
        ErrorHandler("gethost... error");
    }

    printf("official name : %s\n", host->h_name);
    for (int i = 0; host->h_aliases[i]; i++) {
        printf("Alisea %d: %s \n", i+1, host->h_aliases[i]);
    }
    printf("Address type: %s \n", (host->h_addrtype == AF_INET) ? "AF_INET" : "AF_INET6");
    for (int i = 0; host->h_addr_list[i]; i++) {
        printf("IP Adddr %d: %s \n", i+1, inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
    }
    return 0;
}
  1. 利用IP地址获取域名
#include <netdb.h>

struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family);

腾讯的DNS服务器IP示例
五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip

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

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

int main(int argc, char**argv) {
    if (argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }
    struct hostent *host;
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    host = gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET);
    if (!host) {
        ErrorHandler("get host...error");
    }
    printf("official name : %s\n", host->h_name);
    for (int i = 0; host->h_aliases[i]; i++) {
        printf("Alisea %d: %s \n", i+1, host->h_aliases[i]);
    }
    printf("Address type: %s \n", (host->h_addrtype == AF_INET) ? "AF_INET" : "AF_INET6");
    for (int i = 0; host->h_addr_list[i]; i++) {
        printf("IP Adddr %d: %s \n", i+1, inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
    }
    return 0;
}

3 套接字多种可选项

3.1 套接字可选项与IO缓冲大小

  • 套接字多种可选项
    有时需要更改套接字特性,下表是一部分
    五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip
    五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip
    从表中看出,套接字可选项是分层的。IPPROTO_IP层可选项是IP协议相关事项,IPPROTO_TCP层可选项是TCP协议相关的事项,SOL_SOCKET层是套接字相关的通用可选项。
  • getsockopt & setsockopt
#include <sys/socket.h>

/**
* @param[1] sock 查看选项套接字的文件描述符
* @param[2] level 要查看的可选项的协议层
* @param[3] optname 要查看的可选项名
* @param[4] optval 保存查看结果的缓冲地址值
* @param[5] optlen 向第四个参数传递的缓冲大小
* @retval 成功0, 失败-1
*/
int getsockopt(int sock, int level, int optname, void* optval, socklen_t *optlen);

/**
* @param[1] sock 查看选项套接字的文件描述符
* @param[2] level 要查看的可选项的协议层
* @param[3] optname 要查看的可选项名
* @param[4] optval 保存查看结果的缓冲地址值
* @param[5] optlen 向第四个参数传递的缓冲大小
* @retval 成功0, 失败-1
*/
int setsockopt(int sock, int level, int optname, const void* optval, socklen_t *optlen);
  • sock_type.c
    五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>

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

int main(int argc, char** atgv) {
    int tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
    int udp_sock = socket(PF_INET, SOCK_DGRAM, 0);
    int sockType;
    socklen_t optlen = sizeof(sockType);

    int state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sockType, &optlen);
    if (state == -1) {
        ErrorHandler("getsockopt error");
    }
    printf("Socket type one: %d \n", sockType);

    state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void*)&sockType, &optlen);
    if (state == -1) {
        ErrorHandler("getsockopt error");
    }
    printf("Socket type two: %d \n", sockType);

    return 0;
}

注:套接字类型(tcp/udp)只能在创建时决定,后续不能更改。

  • SO_SNDBUF & SO_RECVBUF
    SO_RECVBUF是输入缓冲大小相关可选项,SO_SNDBUF是输出缓冲区大小相关可选项,这俩既可以读取,也可以更改。
    五:优雅断连 & 域名<=>IP & 套接字多种选项,# TCP/IP网络编程,tcp/ip

注:系统不能放任你修改缓冲区,所以要设置一个合理的值。

示例

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

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

int main(int argc, char** argv) {
/*--------------------------修改前----------------------------------------------*/
    int sndBuf;
    int len = sizeof(sndBuf);
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    int state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&sndBuf, &len);
    if (state) {
        ErrorHandler("getsockopt error");
    }

    int recvBuf;
    len = sizeof(recvBuf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&recvBuf, &len);
    if (state) {
        ErrorHandler("getsockopt error");
    }

    printf("input buffer size : %d, output buffer size : %d \n", recvBuf, sndBuf);

/*------------------修改后--------------------------------------------------------*/
    sndBuf = 1024*30;
    recvBuf = 1024*30;

    state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvBuf, sizeof(recvBuf));
    if (state) {
        ErrorHandler("setsockopt error");
    }

    state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&sndBuf, sizeof(sndBuf));
    if (state) {
        ErrorHandler("setsockopt error");
    }

    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&sndBuf, &len);
    if (state) {
        ErrorHandler("getsockopt error");
    }

    
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&recvBuf, &len);
    if (state) {
        ErrorHandler("getsockopt error");
    }

    printf("input buffer size : %d, output buffer size : %d \n", recvBuf, sndBuf);

    return 0;
}

3.2 SO_REUSEADDR

   之前,我们遇到过服务端,服务端断开连接后同一端口无法立即使用,这是由于套接字主动关闭之后会进入time_wait状态。
  此状态有两个作用:①:允许老的重复报文分组在网络中消逝。②:保证TCP全双工连接的正确关闭。
  Time-wait看似重要,但不一定讨喜,因为如果系统发生故障而紧急重启,此时由于time-wait导致服务无法立即恢复,则引发了严重的问题。
  解决方案就是在套接字选项中更改SO_REUSEADDR的状态。适当调整该参数,可将time-wait状态下的套接字端口号重新分配给新的套接字。具体做法如下:

optlen = sizeof(option);
option = true;
setsockopt(servSock, SOL_SOCKET, SO_REUSEADDR, (void*)&option, optlen);

3.3 TCP_NODEALY

Nagle算法

  在使用一些协议通讯的时候,比如Telnet,会有一个字节字节的发送的情景,每次发送一个字节的有用数据,就会产生41个字节长的分组,20个字节的IP Header 和 20个字节的TCP Header,这就导致了1个字节的有用信息要浪费掉40个字节的头部信息,这是一笔巨大的字节开销,而且这种Small packet在广域网上会增加拥塞的出现。
  如何解决这种问题? Nagle就提出了一种通过减少需要通过网络发送包的数量来提高TCP/IP传输的效率,这就是Nagle算法。
  Nagle算法主要是避免发送小的数据包,要求TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的小分组,并在确认到来时以一个分组的方式发出去。

禁用Nagle算法
  在默认的情况下,Nagle算法是默认开启的,Nagle算法比较适用于发送方发送大批量的小数据,并且接收方作出及时回应的场合,这样可以降低包的传输个数。同时协议也要求提供一个方法给上层来禁止掉Nagle算法

  当你的应用不是连续请求+应答的模型的时候,而是需要实时的单项的发送数据并及时获取响应,这种case就明显不太适合Nagle算法,明显有delay的。

  linux提供了TCP_NODELAY的选项来禁用Nagle算法。文章来源地址https://www.toymoban.com/news/detail-825977.html

//将套接字选项TCP_NODELAY改为1

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

到了这里,关于五:优雅断连 & 域名<=>IP & 套接字多种选项的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • TCP/IP网络编程(一) 理解网络编程和套接字

    网络编程和套接字概要 网络编程就是编写程序使两台联网的计算机相互交换数据 为了与远程计算机进行数据传输,需要连接因特网,而编程种的套接字就是用来连接该网络的工具。 构建套接字 1.调用soecket函数创建套接字 2.调用bind函数给套接字分配地址 3.调用listen函数将套

    2024年02月11日
    浏览(147)
  • TCP/IP网络编程(二) 套接字协议及其数据传输特性

    关于协议 如果相隔比较远的两人进行通话,必须先决定通话方式,如果一方选择电话,另一方也必须选择电话,否则接受不到消息。 总之,协议就是为了完成数据交换而定好的约定。 创建套接字 协议族 通过socket函数的第一个参数传递套接字中使用的协议分类信息,此协议

    2024年02月10日
    浏览(59)
  • socket套接字通信 TCP传输控制协议/IP网络协议 5.18

    B/S :浏览器和服务器 C/S :客户机和服务器 网络的层次结构和每层所使用协议的集合 网络采用分层管理的方法,将网络的功能划分为不同的模块 OSI模型: 共7种: 数据的封装与传递过程: 网络传输数据大小user data: 6~1460 网络传输中容易发生拆包和粘包,所以接收和发送的字节

    2024年02月05日
    浏览(76)
  • Linux错误(3)Linux里IP套接字sendmsg出现EPERM错误

    Linux错误(3)之Linux里IP套接字sendmsg出现EPERM错误 Author: Once Day Date: 2024年2月21日 漫漫长路才刚刚开始… 全系列文章可参考专栏: Mermaid使用指南_Once_day的博客-CSDN博客 参考文档: c - How to fix EPERM error when trying to use sendto() with Ethernet socket(AF_INET, ..., ...) (IP output packets) on Linux - Stack O

    2024年04月24日
    浏览(40)
  • TCP/IP网络编程 第十五章:套接字和标准I/O

    标准I/O函数的两个优点 将标准I/O函数用于数据通信并非难事。但仅掌握函数使用方法并没有太大意义,至少应该 了解这些函数具有的优点。下面列出的是标准I/O函数的两大优点: □标准I/O函数具有良好的移植性(Portability) □标准I/O函数可以利用缓冲提高性能。 关于移植性无需

    2024年02月16日
    浏览(49)
  • 网卡收发包系统结构收发包流程,tcp/ip协议,socket套接字缓冲区,滑动窗口,mtu/mss

    MTU和MSS的区别 TCP 的 MTU MSS MTU是在数据链路层的载荷大小也就是传给网络层的大小,mss是在传输层的载荷大小也就是传给应用层的大小 mss是根据mtu得到的 1、MTU: Maximum Transmit Unit,最大传输单元,即物理接口(数据链路层)提供给其上层(通常是IP层)最大一次传输数据的大

    2024年02月08日
    浏览(42)
  • 网络编程套接字 | UDP套接字

    前面的文章中我们叙述了网络编程套接字的一些预备知识点,从本文开始我们就将开始UDP套接字的编写。本文中的服务端与客户端都是在阿里云的云服务器进行编写与测试的。 在v1的版本中我们先来使用一下前面讲过得一些接口,简单的构建一个udp服务器: 然后运行上述的程

    2024年02月09日
    浏览(85)
  • 套接字通信(附带单线程TCP套接字通信代码)

    1. 概念 1.1 局域网和广域网 局域网(LAN)和广域网(WAN)是两种不同范围的计算机网络,它们用于连接多台计算机以实现数据共享和通信。 局域网(LAN): 定义: 局域网是一个较小范围内的网络,通常限定在某个地理区域,比如一个办公室、学校或者家庭。 范围: LAN 的范

    2024年01月21日
    浏览(44)
  • 网络编程之 Socket 套接字(使用数据报套接字和流套接字分别实现一个小程序(附源码))

    网络编程是指网络上的主机,通过不同的进程,以编程的方式实现 网络通信(或称为网络数据传输) 只要满足不同的进程就可以进行通信,所以即便是在同一个主机,只要不同的进程,基于网络传输数据,也属于网络编程 在一次网络传输中: 发送端: 数据的 发送方进程

    2024年02月03日
    浏览(56)
  • 【JavaEE】网络编程之TCP套接字、UDP套接字

    目录 1.网络编程的基本概念 1.1为什么需要网络编程  1.2服务端与用户端 1.3网络编程五元组  1.4套接字的概念 2.UDP套接字编程 2.1UDP套接字的特点  2.2UDP套接字API 2.2.1DatagramSocket类 2.2.2DatagramPacket类  2.2.3基于UDP的回显程序 2.2.4基于UDP的单词查询  3.TCP套接字编程 3.1TCP套接字的特

    2023年04月20日
    浏览(69)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包