Linux 练习十二 (Linux网络编程socket + 源码练习)
创始人
2025-05-29 03:44:14

文章目录

  • 1 计算机网络基础知识
    • 1.1 OSI参考模型和TCP/IP参考模型
    • 1.2 TCP 协议
      • 1.2.1 概述
      • 1.2.2 建立TCP连接——三次握手
      • 1.2.3 断开TCP连接——四次挥手
  • 2 socket概念
    • 2.1 socket类型
    • 2.2 socket信息数据类型
    • 2.3 数据存储方式转换
    • 2.4 地址格式的转换
    • 2.5 域名转化为地址
  • 3 socket编程
    • 3.1 TCP协议流程图
      • 3.1.1 服务器端所用到所有函数介绍:
      • 3.1.2 客户端所用到的函数介绍
      • 3.1.3 示例:TCP传输数据
      • 3.1.4 socket优化方法
      • 3.1.5 客户端服务器聊天通信
    • 3.2 UDP协议流程图
      • 3.2.1 UDP用到函数介绍:
      • 3.2.2 示例:使用UDP连接传输数据
      • 3.2.3 示例使用UDP实现即时聊天
    • 3.3 设置socket套接字选项函数setsockopt的用法
    • 3.4 单播、广播、组播概念
    • 3.5 DDos网络攻击(SYN Flooding 泛洪网络攻击)
    • 3.6 文件描述符属性修改,以及文件描述符的传递
      • 3.6.1 fcntl 非阻塞示例

使用环境:Ubuntu18.04
使用工具:VMWare workstations ,xshell

  作者在学习Linux的过程中对常用的命令进行记录,通过思维导图的方式梳理知识点,并且通过xshell连接vmware中ubuntu虚拟机进行操作,并将练习的截图注解,每句话对应相应的命令,读者可以无障碍跟练。
  本次练习的重点在于Linux的网络编程。
作者在编写这部分内容时,正在准备面试,所以文章中理论部分简化了很多,重点在于socket和epoll的时间编程,后期有时间必定补上网络的理论知识。

1 计算机网络基础知识

1.1 OSI参考模型和TCP/IP参考模型

在这里插入图片描述

  • tcp/ip 模型 4 层:
    应用层 {http 超文本传输协议 ftp 文件传输协议 telnet 远程登录 ssh 安全外壳协议 stmp 简单邮件发送pop3 收邮件}
    传输层 {tcp 传输控制协议,udp 用户数据包协议}
    网络层 {ip 网际互联协议 icmp 网络控制消息协议 igmp 网络组管理协议}
    网络接口层 {arp 地址转换协议,rarp 反向地址转换协议,mpls 多协议标签交换}
    TCP 协议:传输控制协议 面向连接的协议 能保证传输安全可靠 速度慢(有 3 次握手)
    UDP 协议:用户数据包协议 非面向连接 速度快 不可靠
    通常是 ip 地址后面跟上端口号:ip 用来定位主机 port 区别应用(进程)
    http 的端口号 80 ssh–>22 telnet–>23 ftp–>21 用户自己定义的通常要大于 1024

  • TCP/IP 协议族的每一层的作用:
    网络接口层:负责将二进制流转换为数据帧,并进行数据帧的发送和接收。要注意的是数据帧是独立的网络信息传输单元。
    网络层:负责将数据帧封装成 IP 数据报,并运行必要的路由算法。
    传输层:负责端对端之间的通信会话连接和建立。传输协议的选择根据数据传输方式而定。
    应用层:负责应用程序的网络访问,这里通过端口号来识别各个不同的进程。
    在这里插入图片描述

  • TCP/IP 协议族的每一层协议的相关注解:

·ARP:(地址转换协议)用于获得同一物理网络中的硬件主机地址。是设备通过自己知道的 IP 地址来获得自己不知道的物理地址的协议。
·RARP:反向地址转换协议(RARP:Reverse Address Resolution Protocol)RARP允许局域网的物理机器从网关服务器的 ARP 表或者缓存上请求其 IP 地址。网络管理员在局域网网关路由器里创建一个表以映射物理地址(MAC)和与其对应的 IP 地址。当设置一台新的机器时,其 RARP 客户机程序需要向路由器上的 RARP 服务器请求相应的 IP 地址。假设在路由表中已经设置了一个记录,RARP 服务器将会返回 IP 地址给机器,此机器就会存储起来以便日后使用。 RARP 可以使用于以太网、光纤分布式数据接口及令牌环 LAN ·IP:(网际互联协议)负责在主机和网络之间寻址和路由数据包。
·ICMP:(网络控制消息协议)用于发送报告有关数据包的传送错误的协议。
·IGMP:(网络组管理协议)被 IP 主机用来向本地多路广播路由器报告主机组成员的协议。主机与本地路由器之间使用 Internet 组管理协议(IGMP,Internet Group Management Protocol)来进行组播组成员信息的交互。
·TCP:(传输控制协议)为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到响应的应用程序。
·UDP:(用户数据包协议)提供了无连接通信,且不对传送包进行可靠的保证。适合于一次传输少量数据。

1.2 TCP 协议

1.2.1 概述

  TCP 是 TCP/IP 体系中面向连接的运输层协议,它提供全双工和可靠交付的服务。它采用许多机制来确保端到端结点之间的可靠数据传输,如采用序列号、确认重传、滑动窗口等。
  首先,TCP 要为所发送的每一个报文段加上序列号,保证每一个报文段能被接收方接收,并只被正确的接收一次。
  其次,TCP 采用具有重传功能的积极确认技术作为可靠数据流传输服务的基础。这里“确认”是指接收端在正确收到报文段之后向发送端回送一个确认(ACK)信息。发送方将每个已发送的报文段备份在自己的缓冲区里,而且在收到相应的确认之前是不会丢弃所保存的报文段的。“积极”是指发送发在每一个报文段发送完毕的同时启动一个定时器,加入定时器的定时期满而关于报文段的确认信息还没有达到,则发送发认为该报文段已经丢失并主动重发。为了避免由于网络延时引起迟到的确认和重复的确认,TCP 规定在确认信息中捎带一个报文段的序号,使接收方能正确的将报文段与确认联系起来。
  最后,采用可变长的滑动窗口协议进行流量控制,以防止由于发送端与接收端之间的不匹配而引起的数据丢失。这里所采用的滑动窗口协议与数据链路层的滑动窗口协议在工作原理上完全相同,唯一的区别在于滑动窗口协议用于传输层是为了在端对端节点之间实现流量控制,而用于数据链路层是为了在相邻节点之间实现流量控制。TCP 采用可变长的滑动窗口,使得发送端与接收端可根据自己的 CPU 和数据缓存资源对数据发送和接收能力来进行动态调整,从而灵活性更强,也更合理。

1.2.2 建立TCP连接——三次握手

  在利用 TCP 实现源主机和目的主机通信时,目的主机必须同意,否则 TCP 连接无法建立。为了确保 TCP 连接的成功建立,TCP 采用了一种称为三次握手的方式,三次握手方式使得“序号/确认号”系统能够正常工作,从而使它们的序号达成同步。如果三次握手成功,则连接建立成功,可以开始传送数据信息。
三次握手的过程是:
1)源主机 A 的 TCP 向主机 B 发送连接请求报文段,其首部中的 SYN(同步)标志位应置为 1,表示想跟目标主机 B 建立连接,进行通信,并发送一个同步序列号 X(例:SEQ=100)进行同步,表明在后面传送数据时的第一个数据字节的序号为 X+1(即 101)。
2)目标主机 B 的 TCP 收到连接请求报文段后,如同意,则发回确认。再确认报中应将 ACK 位和SYN 位置为 1.确认号为 X+1,同时也为自己选择一个序号 Y。
3)源主机 A 的 TCP 收到目标主机 B 的确认后要想目标主机 B 给出确认。其 ACK 置为 1,确认号为 Y+1,而自己的序号为 X+1。TCP 的标准规定,SYN 置 1 的报文段要消耗掉一个序号。
在这里插入图片描述
  运行客户进程的源主机 A 的 TCP 通知上层应用进程,连接已经建立。当源主机 A 向目标主机 B 发送第一个数据报文段时,其序号仍为 X+1,因为前一个确认报文段并不消耗序号。
  当运行服务进程的目标主机 B 的 TCP 收到源主机 A 的确认后,也通知其上层应用进程,连接已经建立。至此建立了一个全双工的连接。
  三次握手:为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到
响应的应用程序。
TCP 的三次握手最主要是防止已过期的连接再次传到被连接的主机。

1.2.3 断开TCP连接——四次挥手

由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动,一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP 客户端发送一个 FIN,用来关闭客户到服务器的数据传送。
(2) 服务器收到这个 FIN,它发回一个 ACK,确认序号为收到的序号加 1。和 SYN 一样,一个FIN 将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个 FIN 给客户端。
(4) 客户端发回 ACK 报文确认,并将确认序号设置为收到序号加 1
在这里插入图片描述
特殊情况: 有时候断开连接的发起方可以主动断开连接,那么四次挥手会出现三次握手的情况,即最后一次不会回应。如何理解这种模式呢?用下面的对话来记忆。
在这里插入图片描述在这里插入图片描述

2 socket概念

  Linux 中的网络编程是通过 socket 接口来进行的。socket 是一种特殊的 I/O 接口,它也是一种文件描述符。它是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。
  每一个 socket 都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket 也有一个类似于打开文件的函数调用,该函数返回一个整型的 socket 描述符,随后的连接建立、数据传输等操作都是通过 socket来实现的;

2.1 socket类型

(1)流式 socket(SOCK_STREAM) ——>用于 TCP 通信
流式套接字提供可靠的、面向连接的通信流;它使用 TCP 协议,从而保证了数据传输的正确性和顺序性。
(2)数据报 socket(SOCK_DGRAM) ——>用于 UDP 通信
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议 UDP。
(3)原始 socket(SOCK_RAW) ——>用于新的网络协议实现的测试等
原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。

2.2 socket信息数据类型

#include
struct sockaddr
{unsigned short sa_family; /*地址族*/char sa_data[14]; /*14 字节的协议地址,包含该 socket 的 IP 地址和端口号。*/
};
struct sockaddr_in
{short int sin_family; /*地址族,AF_INET(ipv4)/AF_INET6(ipv6)*/unsigned short int sin_port; /*端口号*/struct in_addr sin_addr; /*IP 地址*/unsigned char sin_zero[8]; /*填充 0 以保持与 struct sockaddr 同样大小*/
};
typedef uint32_t in_addr_t;
struct in_addr
{in_addr_t s_addr;
};

2.3 数据存储方式转换

  计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式。内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式。

  • eg:对于内存中存放的数 0x12345678 来说
    如果是采用大端模式存放的,则其真实的数是:0x12345678
    如果是采用小端模式存放的,则其真实的数是:0x78563412

  端口号和 IP 地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。这里用到四个函数:htons(),ntohs(), htonl()ntohl().这四个地址分别实现网络字节序和主机字节序的转化,这里的 h代表 host,n 代表 network,s 代表 short,l 代表 long。通常 16 位的 IP 端口号用 s 代表,而 IP 地址用l 来代表。

#include 
uint16_t htons(uint16_t hostshort);	//将一个无符号短整型的数据转换为网络字节序,大端模式	
uint32_t htonl(uint32_t hostlong);	//将一个无符号长整型的数据转换为网络字节序,大端模式	
uint16_t ntohs(uint16_t netshort);	//将一个16位数由网络字节顺序转换为主机字节顺序,小端模式
uint32_t ntohl(uint32_t netlong);	//将一个32位数由网络字节顺序转换为主机字节顺序,小端模式

示例 htons.c

#include
#include int main()
{uint16_t port=0x1234,nport;nport=htons(port);//把主机字节序转为网络字节序printf("nport=%x\n",nport);	//打印3412return 0;
}

示例 ntons.c

#include
#include int main()
{uint16_t port=0x1234,nport;nport=htons(port);//把主机字节序转为网络字节序printf("nport=%x\n",nport);	//打印3412//把网络字节序转为主机字节序port=0;port=ntohs(nport);printf("port=%x\n",port);	//打印1234return 0;
}

2.4 地址格式的转换

  通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制 Ipv6 地址),而在通常使用的 socket 编程中使用的则是 32 位的网络字节序的二进制值,这就需要将这两个数值进行转换。这里在 Ipv4 中用到的函数有 inet_aton()inet_addr()inet_ntoa(),而 IPV4 和 Ipv6 兼容的函数有 inet_pton()和 inet_ntop()。

  • IPV4函数原型:
#include 
#include 
#include 
int inet_aton(const char *straddr, struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);
in_addr_t inet_addr(const char *straddr); // in_addr_t 就是 unsigned long int ,代表 s_addr

函数 inet_aton(const char *straddr, struct in_addr *addrptr); 将点分十进制的IP转换为网络字节序的32位二进制数。成功返回1,失败返回0。
参数straddr:存放输入的点分十进制IP地址
参数affrptr:传出参数,转换后的32位网络地址
函数 inet_ntoa():将网络字节序的 32 位二进制数值转换为点分十进制的 IP 地址。
函数 inet_addr():功能与 inet_aton 相同,但是结果传递的方式不同。inet_addr()若成功则返回32 位二进制的网络字节序地址。

  • IPV6和IPV4的函数原型,两者都可以转化
#include 
int inet_pton(int family, const char *src, void *dst);
const char *inet_ntop(int family, const void *src, char *dst, socklen_t len)

函数 inet_pton 跟 inet_aton 实现的功能类似,只多了一个family参数,如果指定为AF_INET,表示是IPV4,如果执行AF_INET6,表示时IPV6协议。
函数 inet_ntop 跟 inet_ntoa 类似,其中 len 表示表示转换之后的长度(字符串的长度)。

示例 2-4-1:

#include
#include
#include
#includeint main()
{char ip[] = "192.168.0.101";struct in_addr myaddr;int iRet = inet_aton(ip,&myaddr);	//地址转2进制,16进制打印出来printf("%x\n",myaddr.s_addr);printf("%x\n",inet_addr(ip));		//地址转2进制,16进制作为返回值iRet = inet_pton(AF_INET,ip,&myaddr);//地址转2进制,指定是IPV4printf("%x\n",myaddr.s_addr);myaddr.s_addr = 0xac100ac4;printf("%s\n",inet_ntoa(myaddr));	//将32位二进制地址转为十进制inet_ntop(AF_INET,&myaddr,ip,16);	//将32位二进制地址转为十进制,指定IPV4puts(ip);return 0;
}

2.5 域名转化为地址

举个例子,所谓的网址就是域名,比如www.baidu.com,域名和地址的转换主要用到了DNS协议,这里不做解释,读者有兴趣自己去了解一下。我们主要介绍linux中如何将域名和地址进行转换的函数。

#include 
struct hostent* gethostbyname(const char* hostname);
struct hostent* gethostbyaddr(const char* addr, size_t len, int family);struct hostent
{
char *h_name; /*正式主机名*/
char **h_aliases; /*主机别名*/
int h_addrtype; /*主机 IP 地址类型 IPv4 为 AF_INET*/
int h_length; /*主机 IP 地址字节长度,对于 IPv4 是 4 字节,即 32 位*/
char **h_addr_list; /*主机的 IP 地址列表*/
};#define h_addr h_addr_list[0] /*保存的是 ip地址

函数 gethostbyname():用于将域名(www.baidu.com)或主机名转换为 IP 地址。参数 hostname 指向存放域名或主机名的字符串。
函数 gethostbyaddr():用于将 IP 地址转换为域名或主机名。参数 addr 是一个 IP 地址,此时这个ip 地址不是普通的字符串,而是要通过函数 inet_aton()转换。len 为 IP 地址的长度,AF_INET 为 4。family 可用 AF_INET:Ipv4 或 AF_INET6:Ipv6。

示例 4-2-2:将www.baidu.com转换为ip地址

#include
#include
#includeint main()
{char *ptr = "www.baidu.com";char **pptr;	char str[32] = {'\0'};struct hostent *hptr;	//定义一个hostent结构体if((hptr = gethostbyname(ptr)) == NULL){	//尝试获取www.baidu.com的地址结构printf("%s转换ip失败\n",ptr);return 0;}printf("规范主机名为:%s\n",hptr->h_name);for(pptr = hptr->aliases;*pptr!=NULL;pptr++){printf("域名别称:%s\n",*pptr);}return 0;
}

3 socket编程

简单介绍一下传输层的协议有两种,TCP和UDP。TCP面向连接,UDP无连接。TCP开销大,UDP开销小。TCP适合做需要稳定传输的工作,比如文件传输、数据传输等功能,数据可靠性高,UDP适合做视频会议、语音聊天等功能,数据包丢失影响也不大。

3.1 TCP协议流程图

在这里插入图片描述

3.1.1 服务器端所用到所有函数介绍:

  1. 头文件
#include
#include
#include
#include
#include
#include
#include 
#include 
  1. socket函数:生成套接字描述符
    原型:int socket(int domain,int type,int protocol);通常为 0. 返回值:成功则返回套接口描述符,失败返回-1。
    参数:
    domain:{ AF_INET:Ipv4 网络协议 AF_INET6:IPv6 网络协议}
    type:{ tcp:SOCK_STREAM udp:SOCK_DGRAM}
    protocol:指定 socket 所使用的传输协议编号。

  2. bind函数:用来绑定端口号和IP地址,使其相关联
    函数int bind(int sockfd,struct sockaddr * my_addr,int addrllen);返回值:成功则返回 0,失败返回-1
    参数:
    sockfd:为前面 socket 的返回值。
    my_addr:为结构体指针变量
    addrlen:sockaddr 的结构体长度。通常是计算 sizeof(struct my_addr);

对于不同的socket fomain定义了一个通用的数据结构,sockaddr结构体会因为使用的不同的socketdomain有不同的定义。

//例如使用 AF_INET domain,其 socketaddr 结构定义便为
struct sockaddr_in //常用的结构体
{unsigned short int sin_family; //即为 sa_family AF_INETuint16_t sin_port; //为使用的 port 编号struct in_addr sin_addr; //为 IP 地址unsigned char sin_zero[8]; //未使用
};
struct in_addr
{uint32_t s_addr;
};
  1. listen 函数:使服务器的这个接口和IP处于监听状态,等待网络中某一客户机的连接请求。如果客户端有链接请求,端口就会接受这个连接。
    函数:int listen(int sockfd,int backlog); ,成功则返回 0,失败返回-1
    参数:
    sockfd:为前面的socket的返回值,就是套接字的描述符
    backlog:指定同时处理的最大连接要求。最大设为128。

  2. accept函数:接受远程计算机的连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求。当 accept 函数接受一个连接时,会返回一个新的 socket 标识符,以后的数据传输和读取就要通过这个新的 socket 编号来处理,原来参数中的 socket 也可以继续使用,继续监听其它客户机的连接请求。
    函数:int accept(int sfd,struct sockaddr * addr,int * addrlen); ,成功则返回新的 socket 处理代码 new_fd,失败返回-1
    参数:
    sfd:套接字描述符
    addr:结构体指针变量,和bind的结构体是同种类型,系统内核会自动把远程主机的信息(地址和端口号)保存在结构体中。
    addrlen:结构体长度

  3. recv函数,用新的套接字来接收远端主机传来的数据,并把数据存到由参数 buf 指向的内存空间
    原型:int recv(int sockfd,void *buf,int len,unsigned int ,成功则返回实际接收到的字符数,可能会少于你所指定的接收长度。失败返回-1。
    参数:
    socketfd:用来传输文件的套接字描述符,就是accept接受链接后返回的套接字描述符。
    buf:缓冲区地址
    len:缓冲区长度
    flag:一般置为0

  4. send函数,用新的套接字发送数据给指定的远端主机
    原型:int send(int sfd,const void * msg,int len,unsigned int flags);,成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1
    参数:
    sfd:accept返回的传输数据套接字描述符
    msg:发送的数据
    len:发送数据长度
    flag:一般为0

  5. close函数,文件部分close可以关闭一个文件,网络编程部分close可以关闭连接
    原型:int close(int fd);,若文件顺利关闭则返回 0,发生错误时返回-1
    参数:
    fd:此处可以为socket描述符sfd

3.1.2 客户端所用到的函数介绍

  1. connect函数:用来请求连接远程服务器,将参数 sockfd 的 socket 连至参数 serv_addr 指定的服务器IP 和端口号。
    原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen) 成功则返回 0,失败返回-1
    参数:
    sockfd:用于连接的套接字描述符
    serv_addr:结构体指针变量,存储需要连接的远程服务器的IP和端口号
    addrlen:结构体变量长度

3.1.3 示例:TCP传输数据

通用头文件head.h

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define ARGS_CHECK(argc,num) {if(argc!=num) {printf("error args\n");return -1;}}
#define ERROR_CHECK(ret,retval,func_name) {if(ret==retval) {printf("errno=%d,",errno);fflush(stdout);perror(func_name);return -1;}}
#define THREAD_ERR_CHECK(ret,func_name) {if(ret!=0) {printf("%s failed,%d %s\n",func_name,ret,strerror(ret));return -1;}}

服务器端代码:server.c

#include"head.h"int main(int argc,char** argv)
{ARGS_CHECK(argc,3);int sfd;sfd = socket(AF_INET,SOCK_STREAM,0);	//AF_INET表示IPV4,SOCK_STREAM表示TCPERROR_CHECK(sfd,-1,"socket");printf("socket文件描述符%d\n",sfd);struct sockaddr_in ser_addr;		//定义一个服务端描述结构体bzero(&ser_addr,sizeof(ser_addr));	//清空结构体ser_addr.sin_family = AF_INET;		//设置IPV4通信ser_addr.sin_addr.s_addr = inet_addr(argv[1]);	//设置服务端地址:把ip的点分十进制转换为网络字节序ser_addr.sin_port = htons(atoi(argv[2]));		//设置服务端的通信端口:把端口号转换为网络字节序//开始绑定:用服务端描述结构体和socket描述符绑定 int ret = bind(sfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr)); ERROR_CHECK(ret,-1,"bind");//开始监听,端口就开启了ret=listen(sfd,10);ERROR_CHECK(ret,-1,"listen");int new_fd;	//这个是传输数据的socket描述符struct sockaddr_in client_addr;		//客户端描述结构体bzero(&client_addr,sizeof(client_addr));	//清空结构体socklen_t addr_len = sizeof(client_addr);	//设置长度//accept自动完成三次握手,如果没有成功,accept会阻塞new_fd = accept(sfd,(struct sockaddr*)&client_addr,&addr_len);ERROR_CHECK(new_fd,-1,"accept");printf("client ip = %s,port = %d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//服务器端先接收数据char buf[128] = {0}; 	//设置缓冲区ret = recv(new_fd,buf,sizeof(buf),0);	//接收数据到缓冲区ERROR_CHECK(ret,-1,"recv");printf("我是服务端,接收的数据是%s\n",buf);//服务器发送数据send(new_fd,"world",5,0);	//发送数据给客户端socket描述符close(new_fd);	//关闭数据传输close(sfd);    	//关闭通信控制                 	                                                return 0;
}

客户端代码:client.c

#include"head.h"int main(int argc,char** argv)
{ARGS_CHECK(argc,3);int sfd;sfd = socket(AF_INET,SOCK_STREAM,0);	//同上初始化是一个socket描述符,IPV4和TCP协议的ERROR_CHECK(sfd,-1,"socket");printf("sfd=%d\n",sfd);struct sockaddr_in ser_addr;bzero(&ser_addr,sizeof(ser_addr));//清空ser_addr.sin_family=AF_INET;//代表要进行ipv4通信ser_addr.sin_addr.s_addr=inet_addr(argv[1]);//把ip的点分十进制转为网络字节序ser_addr.sin_port=htons(atoi(argv[2]));//把端口转为网络字节序//客户端连接服务器int ret=connect(sfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));ERROR_CHECK(ret,-1,"connect");//客户端先发数据ret=send(sfd,"hello",5,0);ERROR_CHECK(ret,-1,"send");//客户端接收数据char buf[128]={0};recv(sfd,buf,sizeof(buf),0);printf("我是客户端,接收的数据是=%s\n",buf);close(sfd);return 0;
}

本机测试:
在这里插入图片描述

3.1.4 socket优化方法

  1. 多线程传输(开销较大,不常用)
  2. 调用fcntl 函数将sockfd设置为非阻塞模式
  3. 多路选择select 函数实现实时聊天

3.1.5 客户端服务器聊天通信

使用select 监控实现实时聊天的效果。
相同功能的注释简写,头文件和上面的一样。

服务器端server.c

#include"head.h"int main(int argc,char** argv)
{ARGS_CHECK(argc,3);int sfd;//初始化socket标识符sfd = socket(AF_INET,SOCK_STREAM,0);ERROR_CHECK(sfd,-1,"socket");printf("sfd=%d\n",sfd);//定义服务端描述结构体struct sockaddr_in ser_addr;bzero(&ser_addr,sizeof(ser_addr));	ser_addr.sin_family = AF_INET;ser_addr.sin_addr.s_addr = inet_addr(argv[1]);ser_addr.sin_port = htons(atoi(argv[2]));//网络标识符和服务器结构体绑定int ret = bind(sfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));ERROR_CHECK(ret,-1,"bind");//对网络标识符进行监听ret = listen(sfd,10);ERROR_CHECK(ret,-1,"listen");int new_fd;	//定义一个传输文件的标识符struct sockaddr_in client_addr;	//定义一个客户端描述结构体bzero(&client_addr,sizeof(client_addr));socklen_t addr_len = sizeof(client_addr);//用accept完成三次握手,如果没有连接,accept会阻塞new_fd = accept(sfd,(struct sockaddr*)&client_addr,&addr_len);ERROR_CHECK(new_fd,-1,"accept");printf("client ip=%s,port=%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//编写即时聊天char buf[128] = {0};	//缓冲区fd_set rdset;	//建立一个文件描述集合while(1){//初始化文件描述集合,并将标准输入输出和网络传输描述符加入集合中,用于select监控FD_ZERO(&rdset);FD_SET(STDIN_FILENO,&rdset);FD_SET(new_fd,&rdset);//用select监控rdset集合中所有文件描述符,必须+1ret = select(new_fd+1,&rdset,NULL,NULL,NULL);//如果标准输入输出是否准备好,则清空缓冲区准备读if(FD_ISSET(STDIN_FILENO,&rdset){bzero(buf,sizeof(buf));//如果从标准输入中读取的内容为空,则退出聊天ret = read(STDIN_FILENO,buf,sizeof(buf));if(!ret){printf("服务器:想结束聊天\n");break;}//如果读入内容不为空,则将标准输入的内容发送给客户端send(new_fd,buf,strlen(buf)-1,0);}//如果网络传输描述符是否准备好,也清空缓冲区if(FD_ISSET(new_fd,&rdset)){bzero(buf,sizeof(buf));//如果从客户端接收到的字符数为0,则退出聊天ret = recv(new_fd,buf,sizeof(buf),0);ERROR_CHECK(ret,-1,"recv");if(!ret){printf("对方离开了聊天\n");break;}printf("客户端发送:%s\n",buf);}}close(new_fd);	close(sfd);return 0;
}
#include"head.h"int main(int argc,char** argv)
{ARGS_CHECK(argc,3);int sfd;sfd = socket(AF_INET,SOCK_STREAM,0);	//同上初始化是一个socket描述符,IPV4和TCP协议的ERROR_CHECK(sfd,-1,"socket");printf("sfd=%d\n",sfd);struct sockaddr_in ser_addr;bzero(&ser_addr,sizeof(ser_addr));//清空ser_addr.sin_family=AF_INET;//代表要进行ipv4通信ser_addr.sin_addr.s_addr=inet_addr(argv[1]);//把ip的点分十进制转为网络字节序ser_addr.sin_port=htons(atoi(argv[2]));//把端口转为网络字节序//客户端连接服务器int ret=connect(sfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));ERROR_CHECK(ret,-1,"connect");//即时聊天char buf[128] = {0};fd_set rdset;while(1){//初始化文件描述集合,并将标准输入输出和网络传输描述符加入集合中,用于select监控FD_ZERO(&rdset);FD_SET(STDIN_FILENO,&rdset);FD_SET(new_fd,&rdset);//监控集合中哪一个描述符就绪ret=select(sfd+1,&rdset,NULL,NULL,NULL);//如果标准输入输出是否准备好,则清空缓冲区准备读if(FD_ISSET(STDIN_FILENO,&rdset){bzero(buf,sizeof(buf));//如果从标准输入中读取的内容为空,则退出聊天ret = read(STDIN_FILENO,buf,sizeof(buf));if(!ret){printf("服务器:想结束聊天\n");break;}//如果读入内容不为空,则将标准输入的内容发送给客户端send(new_fd,buf,strlen(buf)-1,0);}//如果网络传输描述符是否准备好,也清空缓冲区if(FD_ISSET(new_fd,&rdset)){bzero(buf,sizeof(buf));//如果从客户端接收到的字符数为0,则退出聊天ret = recv(new_fd,buf,sizeof(buf),0);ERROR_CHECK(ret,-1,"recv");if(!ret){printf("对方离开了聊天\n");break;}printf("服务端发送:%s\n",buf);}}close(sfd);return 0;
}

在这里插入图片描述

3.2 UDP协议流程图

在这里插入图片描述

3.2.1 UDP用到函数介绍:

  1. sendto函数,返回实际发送的数据字节长度或在出现发送错误时返回-1。
    函数int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
    该函数比 send()函数多了两个参数,to 表示目地机的 IP 地址和端口号信息,而 tolen 常常被赋值为sizeof (struct sockaddr)。

  2. recvfrom函数,返回接收到的字节数或当出现错误时返回-1,并置相应的 errno。
    函数int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); from 是一个 struct sockaddr 类型的变量,该变量保存连接机的 IP 地址及端口号。fromlen 常置为 sizeof(struct sockaddr)。

3.2.2 示例:使用UDP连接传输数据

UDP服务端server.c

#include"head.h"int main(int argc,char** argv)
{ARGS_CHECK(argc,3);int sfd=socket(AF_INET,SOCK_DGRAM,0);//SOCK_DGRAM表示使用UDP协议ERROR_CHECK(sfd,-1,"socket");struct sockaddr_in ser_addr;bzero(&ser_addr,sizeof(ser_addr));//清空ser_addr.sin_family=AF_INET;//代表要进行ipv4通信ser_addr.sin_addr.s_addr=inet_addr(argv[1]);//把ip的点分十进制转为网络字节序ser_addr.sin_port=htons(atoi(argv[2]));//把端口转为网络字节序int ret=bind(sfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));ERROR_CHECK(ret,-1,"bind");char buf[128]={0};	//设置缓冲区struct sockaddr_in client_addr;//客户端的socket结构体bzero(&client_addr,sizeof(client_addr));socklen_t addr_len=sizeof(client_addr);//服务器先recvfromret=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&addr_len);printf("client ip=%s,port=%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));printf("%s\n",buf);sendto(sfd,"world",5,0,(struct sockaddr*)&client_addr,sizeof(client_addr));close(sfd);return 0;
}

客户端Client.c

#include"head.h"int main(int argc,char** argv)
{ARGS_CHECK(argc,3);int sfd=socket(AF_INET,SOCK_DGRAM,0);	//创建socket描述符ERROR_CHECK(sfd,-1,"socket");struct sockaddr_in ser_addr;bzero(&ser_addr,sizeof(ser_addr));//清空ser_addr.sin_family=AF_INET;//代表要进行ipv4通信ser_addr.sin_addr.s_addr=inet_addr(argv[1]);//把ip的点分十进制转为网络字节序ser_addr.sin_port=htons(atoi(argv[2]));//把端口转为网络字节序sendto(sfd,"hello",5,0,(struct sockaddr*)&ser_addr,sizeof(ser_addr));char buf[128]={0};	//缓冲区recvfrom(sfd,buf,sizeof(buf),0,NULL,NULL);printf("%s\n",buf);close(sfd);return 0;
}

在这里插入图片描述

3.2.3 示例使用UDP实现即时聊天

服务端server.c

#include"head.h"int main(int argc,char** argv)
{ARGS_CHECK(argc,3);int sfd = socket(AF_INET,SOCK_DGRAM,0);ERROR_CHECK(sfd,-1,"socket");struct sockaddr_in ser_addr;ser_addr.sin_family = AF_INET;ser_addr.sin_addr.s_addr = inet_addr(argv[1]);ser_addr.sin_port = htons(atoi(argv[2]));int ret = bind(sfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));ERROR_CHECK(ret,-1,"bind");char buf[128] = {0};struct sockaddr_in client_addr;	//客户端的socketbzero(&client_addr,sizeof(client_addr));socklen_t addr_len = sizeof(client_addr);//服务器先等待接收信息ret = recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&addr_len);printf("client ip=%s,port=%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));fd_set rdset;//文件描述符集合while(1){FD_ZERO(&rdset);FD_SET(STDIN_FILENO,&rdset);FD_SET(sfd,&rdset);//监控哪一个描述符就绪ret=select(sfd+1,&rdset,NULL,NULL,NULL);if(FD_ISSET(STDIN_FILENO,&rdset)){//读取标准输入,并发给对方bzero(buf,sizeof(buf));int ret = read(STDIN_FILENO,buf,sizeof(buf));sendto(sfd,buf,strlen(buf)-1,0,(struct sockaddr*)&client_addr,sizeof(client_addr));}if(FD_ISSET(sfd,&rdset)){//sfd可读,对方发了数据,读取bzero(buf,sizeof(buf));ret=recvfrom(sfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&client_addr,&addr_len);printf("客户端发送:%s\n",buf);}}return 0;
}

客户端client.c

#include"head.h"int main(int argc,char** argv)
{ARGS_CHECK(argc,3);int sfd=socket(AF_INET,SOCK_DGRAM,0);ERROR_CHECK(sfd,-1,"socket");struct sockaddr_in ser_addr;bzero(&ser_addr,sizeof(ser_addr));//清空ser_addr.sin_family=AF_INET;//代表要进行ipv4通信ser_addr.sin_addr.s_addr=inet_addr(argv[1]);//把ip的点分十进制转为网络字节序ser_addr.sin_port=htons(atoi(argv[2]));//把端口转为网络字节序sendto(sfd,"hello",5,0,(struct sockaddr*)&ser_addr,sizeof(ser_addr));char buf[128]={0};fd_set rdset;int ret;while(1){//清空集合并写入要监控的描述符FD_ZERO(&rdset);FD_SET(STDIN_FILENO,&rdset);FD_SET(sfd,&rdset);//监控哪一个描述符就绪ret=select(sfd+1,&rdset,NULL,NULL,NULL);if(FD_ISSET(STDIN_FILENO,&rdset)){//读取标准输入,并发给对方bzero(buf,sizeof(buf));ret = read(STDIN_FILENO,buf,sizeof(buf));sendto(sfd,buf,strlen(buf)-1,0,(struct sockaddr*)&ser_addr,sizeof(ser_addr));}if(FD_ISSET(sfd,&rdset)){//sfd可读,对方发了数据,读取bzero(buf,sizeof(buf));ret=recvfrom(sfd,buf,sizeof(buf),0,NULL,NULL);printf("服务端发送:%s\n",buf);}}close(sfd);return 0;
}

在这里插入图片描述

3.3 设置socket套接字选项函数setsockopt的用法

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

参数:
sockfd:套接字描述符
optname:需要设置的选项名
optval:指针,指向存放选项值的缓冲区
optlen:optval缓冲区长度
level:选择定义的层次:支持 SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP 和 IPPROTO_IPV

  • SOL_SOCKET可设置的选项
选项说明数据类型
SO_BROADCAST允许发送广播数据int
SO_DEBUG允许调试int
SO_DONTROUTE不查找路由int
SO_ERROR获得套接字错误int
SO_KEEPALIVE保持连接int
SO_LINGER延迟关闭连接struct linger
SO_OOBINLINE带外数据放入正常数据流int
SO_RCVBUF接收缓冲区大小int
SO_SNDBUF发送缓冲区大小int
SO_RCVLOWAT接收缓冲区下限int
SO_SNDLOWAT发送缓冲区下限int
SO_RCVTIMEO接收超时struct timeval
SO_SNDTIMEO发送超时struct timeval
SO_REUSEADDR允许重用本地地址和端口int
SO_TYPE获得套接字类型int
SO_BSDCOMPAT与 BSD 系统兼容int
  • IPPROTO_IP可设置的选项
选项说明数据类型
IP_HDRINCL在数据包中包含 IP 首部int
IP_OPTINOSIP首部选项int
IP_TOS服务类型
IP_TTL生存时间int
  • IPPRO_TCP可设置的选项
选项说明数据类型
TCP_MAXSEGTCP 最大数据段的大小int
TCP_NODELAY不使用 Nagle 算法int

3.4 单播、广播、组播概念

对于单播而言,单播用于两个主机之间的端对端通信。
对于广播而言,广播用于一个主机对整个局域网上所有主机上的数据通信。
对于多播而言,也称为“组播”,将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。

  • 多播的程序设计也要使用 setsockopt()函数和 getsockopt()函数来实现。其中对于 setsockopt 的第二个参数 level 不再是 SOL_SOCKET,而是 IPPROTO_IP;第三个参数 optname 常用选项如下:
  1. IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP 加入或者退出一个组播组,通过选项IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP,对一个结构 struct ip_mreq 类型的变量进行控制。
struct ip_mreq
{struct in_addr imr_multiaddr; /*加入或者退出的多播组 IP 地址*/struct in_addr imr_interface; /*加入或者退出的网络接口 IP 地址,本机 IP*/
};
  1. 选项 IP_ADD_MEMBERSHIP 用于加入某个多播组,之后就可以向这个多播组发送数据或者从多播组接收数据。此选项的值为 mreq 结构,成员 imr_multiaddr 是需要加入的多播组 IP 地址,成员 imr_interface是本机需要加入多播组的网络接口 IP地址。
struct ip_mreq mreq;
memset(&mreq, 0, sizeof(struct ip_mreq));
mreq.imr_interface.s_addr = INADDR_ANY;
mreq.imr_multiaddr.s_addr = inet_addr("224.1.1.1");
if(-1 == setsockopt(sfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(struct ip_mreq)))
{perror("setsockopt");exit(-1);
}
  1. 选项 IP_ADD_MEMBERSHIP 每次只能加入一个网络接口的 IP 地址到多播组,但并不是一个多播组仅允许一个主机 IP 地址加入,可以多次调用 IP_ADD_MEMBERSHIP 选项来实现多个 IP 地址加入同一个广播组,或者同一个 IP 地址加入多个广播组。
  2. 选项 IP_DROP_MEMBERSHIP 用于从一个多播组中退出。

3.5 DDos网络攻击(SYN Flooding 泛洪网络攻击)

Linux内核中如何对SYN进行处理:首先收到SYN包,SYN包请求等待连接成为半连接状态,内核会建立一个半连接队列,将SYN请求加入队列,接着发送ack即syn给客户端,等待客户端的回应,完成三次握手建立连接。

利用三次握手的特性,此时服务端收到了来自攻击者很多大量的SYN包,内核的队列就会占用很大空间,导致后来到的正常连接就无法建立连接,这种攻击方式叫做DOS攻击,又叫泛洪攻击,非常形象,就是发送洪水一般的SYN包,将服务器淹没。
在这里插入图片描述

  • SYN Flood 攻击防护手段
  1. 增加半连接队列长度
  2. 减少SYN+ACK的重传次数
  3. 启用syn cookie屏蔽伪请求

第一种和第二种没有办法根除问题,第三种可以有效防御,但是缺陷很明显,添加额外的字段进行验证,必然会有空间和时间上的损耗。

3.6 文件描述符属性修改,以及文件描述符的传递

  1. fcntl函数:用于改变已打开的文件描述符的性质。
#include 
#include 
#include 
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);

参数fd:打算设置的文件描述符
参数cmd:操作的指令

指令功能
F_DUPFD用来查找大于或等于参数 arg 的最小且仍未使用的文件描述词,并且复制参数 fd 的文件描述词。执行成功则返回新复制的文件描述词。新描述符与 fd 共享同一文件表项,但是新描述符有它自己的一套文件描述符标志,其中 FD_CLOEXEC 文件描述符标志被清除。请参考 dup2()。
F_GETFD取得 close-on-exec 旗标。若此旗标的 FD_CLOEXEC 位为 0,代表在调用 exec()相关函数时文件将不会关闭。
F_SETFD设置 close-on-exec 旗标。该旗标以参数 arg 的 FD_CLOEXEC 位决定。
F_GETFL取得文件描述词状态旗标,此旗标为 open()的参数 flags。
F_SETFL设置文件描述词状态旗标,参数 arg 为新旗标,但只允许 O_APPEND、O_NONBLOCK和 O_ASYNC 位的改变,其他位的改变将不受影响。
  1. Socketpair函数int socketpair(int domain, int type, int protocol, int sv[2]); 前面 3 个参数参照 socket,domain 变为AF_LOCAL,sv[2],放置 fd[2]。
  2. Sendmsg函数ssize_t sendmsg (int sfd, const struct msghdr *msg, int flags); 系统调用,用于发送消息到另一个套接字。sfd套接字描述符,flags和上述相同。结构体struct msghdr:
struct msghdr {void *msg_name;	//套接字地址socklen_t msg_namelen;	//套接字地址长度struct iovec *msg_iov;	//IO向量地址size_t msg_iovlen;		//IO向量数组大小void *msg_control;		//附属数据区size_t msg_controllen;	//数据区大小int msg_flags;
};
  1. Recvmsg函数int recvmsg(int sfd, struct msghdr *msg, unsigned int flags); 接收数据
  2. Writev函数,一次写入多个buf内容
ssize_t writev(int sfd, const struct iovec *iov, int iovcnt)
struct iovec {void *iov_base; /* Starting address */size_t iov_len; /* Number of bytes to transfer */
};
  1. Readv函数,ssize_t readv(int fd, const struct iovec *iov, int iovcn一次读取多个buf内容
  2. Cmsg函数,size_t CMSG_LEN(size_t length);用来设定*msg_control指针

3.6.1 fcntl 非阻塞示例

非阻塞的意思就是不等待的意思,如果进程在规定时间没有从缓冲区拿到数据就会报错。
如此案例,如果不输入内容,5秒后就会返回。

#include
#include
#include
#include
#include
#includeint fcntl_nonblock(int fd)
{int status=fcntl(fd,F_GETFL);//拿出标准输入所有的属性status=status|O_NONBLOCK;//修改其为非阻塞int ret=fcntl(fd,F_SETFL,status);//修改fd的状态ERROR_CHECK(ret,-1,"fcntl");return 0;
}//修改标准输入为非阻塞
int main()
{sleep(5);char buf[128]={0};fcntl_nonblock(STDIN_FILENO);//将标准输入输出改为非阻塞int ret=read(STDIN_FILENO,buf,sizeof(buf));	printf("ret=%d,buf=%s,errno=%d\n",ret,buf,errno);return 0;
}

相关内容

热门资讯

考研数二第四讲 分段函数的复合... 分段函数的复合函数求分段函数的复合函数,这是考研高数中的一个重要考点。专升本的高数不考...
7座MPV车型推荐,7座MPV... 本篇文章极速百科给大家谈谈7座MPV车型推荐,7座MPV车型大全,以及7座mpv汽车大全2020对应...
支付宝怎么取消自动续费(苹果手... 今天给各位分享支付宝怎么取消自动续费的知识,其中也会对苹果手机支付宝怎么取消自动续费进行解释,如果能...
TH是什么意思(Things是... 本篇文章极速百科给大家谈谈TH是什么意思,以及Things是什么意思翻译对应的知识点,希望对各位有所...
上汽大众途观怎么样(上汽大众途... 本篇文章极速百科给大家谈谈上汽大众途观怎么样,以及上汽大众途观l2022版质量对应的知识点,希望对各...
solidworks转urdf... 是用solidworks成功导出了一次urdf,记录一下导出时各参数的说明。 基座的...
如何买火车票网上订票?网上买火... 今天给各位分享如何买火车票网上订票?网上买火车票怎么买的知识,其中也会对怎样买网上火车票进行解释,如...
加美机油质量怎么样?加美润滑油... 本篇文章极速百科给大家谈谈加美机油质量怎么样?加美润滑油排名第几,以及加美机油咋样对应的知识点,希望...
哈弗E2012款基本型配置-参... 今天给各位分享哈弗E2012款基本型配置-参数配置详解的知识,其中也会对哈弗二多少钱进行解释,如果能...
买拉法要满足什么条件(购买拉法... 今天给各位分享买拉法要满足什么条件的知识,其中也会对购买拉法的几条要求进行解释,如果能碰巧解决你现在...
JAVA并发编程之锁 1、乐观锁和悲观锁 1.1、悲观锁 认为自己在使用数据的时候一定有别的线程来修改数据,...
mysql数据库提权 0x00数据库帐号密码获取方式数据库帐号密码获取方式:1.网站存在高权限SQL注入点2...
橱窗男孩蔚来(橱窗小男孩看车壁... 今天给各位分享橱窗男孩蔚来的知识,其中也会对橱窗小男孩看车壁纸蔚来进行解释,如果能碰巧解决你现在面临...
包含铜雀春深锁二乔的典故是什么... 今天给各位分享铜雀春深锁二乔的典故是什么的知识,其中也会对进行解释,如果能碰巧解决你现在面临的问题,...
天津新地标津沽棒(天津新地标津... 本篇文章极速百科给大家谈谈天津新地标津沽棒,以及天津新地标津沽棒简介对应的知识点,希望对各位有所帮助...
meet的过去式是什么(see... 今天给各位分享meet的过去式是什么的知识,其中也会对see的过去式是什么进行解释,如果能碰巧解决你...
基于Hi3861平台的Open... 一、前言 本篇文章基于Hi3861平台的BearPi-HM_Nano开发板+E53IA1扩展板,进行...
中通面试题分享 redis有遇到过什么瓶颈 redis分布式锁怎么实现的,有哪些问题 布隆过滤器怎么实...
【Linux】GDB的安装与使... 安装安装gdb的具体步骤如下:1、查看当前gdb安装情况rpm -qa | grep ...
算法做题技巧:前缀和 什么是前缀 “前缀”是在计算机科学中广泛使用的一个数学术语。 从字面上解释,就是指一个...
家用轿车哪款比较好?家用轿车排... 本篇文章极速百科给大家谈谈家用轿车哪款比较好?家用轿车排行榜前十名2022,以及家用轿车排行榜202...
智能电表电量清零方法和智能电表... 今天给各位分享智能电表电量清零方法和智能电表故障分析及解决方法...的知识,其中也会对智能电表怎样复...
与中山公园有关的历史事件(中山... 今天给各位分享与中山公园有关的历史事件的知识,其中也会对中山公园故事进行解释,如果能碰巧解决你现在面...
上虞车辆违章查询系统官方入口(... 今天给各位分享上虞车辆违章查询系统官方入口的知识,其中也会对上虞区违章查询进行解释,如果能碰巧解决你...
记录--vue中封装一个右键菜... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 组件介绍 关于web...
xxl-job 的 API 接... 以下是使用 xxl-job 的 API 接口添加任务的 Java 源代码示例:impo...
【运维】运维常用命令 shell大全读取文件每一行内容文件是否存在数组定义和循环取值变量循环流程控制语句:c...
特斯拉降价引发新能源车市连锁反... 本篇文章极速百科给大家谈谈特斯拉降价引发新能源车市连锁反应,以及特斯拉降价背后的逻辑对应的知识点,希...
广东车辆违章查询系统官方入口(... 本篇文章极速百科给大家谈谈广东车辆违章查询系统官方入口,以及广东省车辆违章查询易车宝对应的知识点,希...
滴滴打车下架了吗?滴滴现在还能... 今天给各位分享滴滴打车下架了吗?滴滴现在还能用吗的知识,其中也会对滴滴打车已经下架了吗?进行解释,如...