Linux 网络编程

网络编程:OSI 七层模型、TCP 协议、UDP 协议、三次握手、四次挥手、socket编程及编程实战

//
掌握网络编程,TCP、UDP等,socket函数编程

前置知识:
网络通信
网络通信本质上是一种进程间通信,是位于网络中不同主机上的进程之间的通信,属于 IPC (InterProcess Communication)的一种,通常称为 socket IPC,网络通信是为了解决在网络环境中,不同主机上的应用程序之间的通信问题。
大概可以分为三个层次,如下所示:
(1)、硬件层:网卡设备,收发网络数据
(2)、驱动层:网卡驱动(Linux 内核网卡驱动代码)
(3)、应用层:上层应用程序(调用 socket 接口或更高级别接口实现网络相关应用程序)
在这里插入图片描述
硬件上,两台主机都提供了网卡设备,也就满足了进行网络通信最基本的要求,网卡设备是实现网络数据收发的硬件基础。并且通信的两台主机之间需要建立网络连接,这样两台主机之间才可以进行数据传输,譬如通过网线进行数据传输。网络数据的传输媒介有很多种,大体上分为有线传输(譬如双绞线网线、光纤等)和无线传输(譬如 WIFI、蓝牙、ZigBee、4G/5G/GPRS 等),PC 机通常使用有线网络,而手机等移动设备通常使用无线网络

内核层,提供了网卡驱动程序,可以驱动底层网卡硬件设备,同时向应用层提供 socket 接口

应用层,应用程序基于内核提供的 socket 接口进行应用编程,实现自己的网络应用程序。需要注意的是,socket 接口是内核向应用层提供的一套网络编程接口,所以我们学习网络编程其实就是学习 socket 编程,如何基于 socket 接口编写应用程序

除了 socket 接口之外,在应用层通常还会使用一些更为高级的编程接口,譬如 http、网络控件等,那么这些接口实际上是对 socket 接口的一种更高级别的封装。在正式学习 socket 编程之前,需要先了解一些网络基础知识

1、OSI 七层模型

网络互连模型:OSI 七层模型,七层模型,亦称 OSI(Open System Interconnection)。OSI 七层参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间网络互联的标准体系,一般称为 OSI 参考模型或七层模型。OSI 七层模型是一个网络互连模型,此图片来自正点原子:
在这里插入图片描述

OSI 模型与 TCP/IP 模型
四层模型包括:应用层、传输层、网络层以及网络接口层。而在实际的应用中还是使用TCP/IP 四层模型五层模型是专门为介绍网络原理而设计的
在这里插入图片描述
所有的数据传输,都有三个要素 :源、目的、长度
在这里插入图片描述
在网络传输中需要使用“IP 和 端口”来表示源或目的

互联网中的每一台主机都需要一个唯一的 IP 地址以标识自己的身份,网络中传输的数据包通过 IP 地址找到对应的目标主机;一台主机通常只有一个IP 地址,但主机上运行的网络进程却通常不止一个,譬如 Windows 电脑上运行着QQ、微信、钉钉、网页浏览器等,这些进程都需要进行网络连接,它们都可通过网络发送/接收数据,那么这里就有一个问题?主机接收到网络数据之后,如何确定该数据是哪个进程对应的接收数据呢?其实就是通常端口号来确定的

端口号本质上就是一个数字编号,用来在一台主机中唯一标识一个能上网(能够进行网络通信)的进程,端口号的取值范围为 0~65535。一台主机通常只有一个 IP 地址,但是可能有多个端口号,每个端口号表示一个能上网的进程。一台拥有 IP地址的主机可以提供许多服务,比如 Web 服务、FTP 服务、SMTP 服务等,这些服务都是能够进行网络通信的进程,IP地址只能区分网络中不同的主机,并不能区分主机中的这些进程,显然不能只靠 IP 地址,因此才有了端口号。通过“IP地址+端口号”来区分主机不同的进程
在这里插入图片描述

网络传输中的 2 个对象:serverclient
访问网站,这涉及 2 个对象:网站服务器,浏览器。网站服务器平时安静地呆着,浏览器主动发起数据请求。网站服务器、浏览器可以抽象成 2 个软件的概念:server 程序、client 程序
在这里插入图片描述

2、两种传输方式TCP/UDP :

传输控制协议 TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,能够提供可靠的交付。

  • 工作在传输层,对上服务 socket 接口,对下调用 IP 层;
  • 面向连接的传输协议,通信之前必须通过三次握手与客户端建立连接关系后才可通信;
  • 提供可靠传输,不怕丢包、乱序。
    (面向连接、确认与重传、全双工通信、基于字节流而非报文、流量控制-滑动窗口协议、差错控制、拥塞控制)

用户数据包协议 UDP(User Datagram Protocol):无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能提供“尽最大努力交付”。

TCP 向它的应用程序提供了面向连接的服务。这种服务有 2 个特点:可靠传输流量控制(即发送方/接收方速率匹配)。它包括了应用层报文划分为短报文,并提供拥塞控制机制。

UDP 协议向它的应用程序提供无连接服务。它没有可靠性没有流量控制,也没有拥塞控制

既然 TCP 提供了可靠数据传输服务,而 UDP 不能提供,那么 TCP 是否总是首选呢?
答案是否定的,因为有许多应用更适合用 UDP,举个例子:视频通话时,使用 UDP,偶尔的丢包、偶尔的花屏时可以忍受的;如果使用 TCP,每个数据包都要确保可靠传输,当它出错时就重传,这会导致后续的数据包被阻滞,视频效果反而不好。

使用 UDP 时,有如下特点:
关于何时发送什么数据控制的更为精细,采用 UDP 时只要应用进程将数据传递给 UDP,UDP 就会立即将其传递给网络层。而 TCP 有重传机制,而不管可靠交付需要多长时间。但是实时应用通常不希望过分的延迟报文段的传送,且能容忍一部分数据丢失。无需建立连接,不会引入建立连接时的延迟。无连接状态,能支持更多的活跃客户。分组首部开销较小。

  • 网络层:负责将被称为数据包(datagram)的网络层分组从一台主机移动到另一台主机。
  • 链路层:因特网的网络层通过源和目的地之间的一系列路由器路由数据报
  • 物理层:在物理层上所传数据的单位是比特。物理层的任务就是透明地传送比特流

PS:TCP/IP 协议它其实是一个协议族包含了众多的协议,譬如应用层协议 HTTP、FTP、MQTT…以及传输层协议 TCP、UDP 等这些都属于 TCP/IP 协议

3、三次握手、四次挥手**

3.1 建立 TCP 连接:三次握手
“三次握手”其实是指建立 TCP 连接的一个过程
三次握手示例图:
在这里插入图片描述
在这里插入图片描述
比如打电话:
甲:“喂,你能听到我的声音吗?”
乙:“我听得到呀,你能听到我的声音吗?”
甲:“我能听到你,………”

经过三次的互相确认,大家就会认为对方对听的到自己说话,才开始接下来的沟通交流,否则,如果不进行确认,那么你在说话的时候,对方不一定能听到你的声音。所以,TCP 的三次握手是为了保证传输的安全、可靠。

3.2 关闭 TCP 连接:四次挥手
在这里插入图片描述四次挥手即终止 TCP 连接,就是指断开一个 TCP 连接时,需要客户端和服务端总共发送 4 个包以确认连接的断开。(在 socket 编程中,这一过程由客户端或服务端任一方执行 close 来触发)

由于 TCP 连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个 FIN 来终止这一方向的连接,收到一个 FIN 只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个 TCP 连接上仍然能够发送数据,直到这一方向也发送了 FIN。

4、socket编程

Linux 下的网络编程,我们一般称为 socket 编程,socket是内核向应用层提供的一套网络编程接口,用户基于 socket 接口可开发自己的网络相关应用程序

/
socket 函数
int socket(int domain, int type,int protocol);

参数 domain 用于指定一个通信域,对于 TCP/IP 协议来说,通常选择 AF_INET 就可以了

参数 protocol 通常设置为 0,表示为给定的通信域套接字类型选择默认协议

调用 socket()与调用 open()函数很类似,调用成功情况下,均会返回用于文件 I/O 的文件描述符,只不过对于 socket()来说,其返回的文件描述符一般称为 socket 描述符。当不再需要该文件描述符时,可调用close()函数来关闭套接字,释放相应的资源;

调用失败,则会返回-1,并且会设置 errno 变量以指示错误类型;

///
bind()函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind()函数用于将一个 IP 地址或端口号与一个套接字进行绑定(将套接字与地址进行关联)。将一个客户端的套接字关联上一个地址没有多少新意,可以让系统选一个默认的地址。一般来讲,会将一个服务器的套接字绑定到一个众所周知的地址—即一个固定的与服务器进行通信的客户端应用程序提前就知道的地址(注意这里说的地址包括 IP 地址和端口号)。因为对于客户端来说,它与服务器进行通信,首先需要知道服务器的 IP 地址以及对应的端口号,所以通常服务器的 IP 地址以及端口号都是众所周知的。
调用 bind()函数将参数 sockfd 指定的套接字与一个地址 addr 进行绑定,成功返回 0,失败情况下返回1,并设置 errno 以提示错误原因

参数 addr 是一个指针,指向一个 struct sockaddr 类型变量:

struct sockaddr {
 sa_family_t sa_family;
 char sa_data[14];
}

sa_data 是一个 char 类型数组,一共 14 个字节,在这 14 个字节中就包括了 IP 地址、端口
号等信息,这个结构对用户并不友好,它把这些信息都封装在了 sa_data 数组中,这样使得用户是无法对sa_data 数组进行赋值。事实上,这是一个通用的 socket 地址结构体

一般我们在使用的时候都会使用 struct sockaddr_in 结构体,sockaddr_in 和 sockaddr 是并列的结构(占用的空间是一样的),指向 sockaddr_in 的结构体的指针也可以指向 sockaddr 的结构体,并代替它,而且sockaddr_in 结构对用户将更加友好,在使用的时候进行类型转换就可以了。该结构体内容如下所示:

struct sockaddr_in {
 sa_family_t sin_family; /* 协议族 */
 in_port_t sin_port; /* 端口号 */
 struct in_addr sin_addr; /* IP 地址 */
 unsigned char sin_zero[8];
};

这个结构体的第一个字段是与 sockaddr 结构体是一致的,而剩下的字段就是 sa_data 数组连续的 14字节信息里面的内容,只不过从新定义了成员变量而已,sin_port 字段是我们需要填写的端口号信息,sin_addr字段是我们需要填写的 IP 地址信息,剩下 sin_zero 区域的 8 字节保留未用。

最后一个参数 addrlen 指定了 addr 所指向的结构体对应的字节长度。

bind使用示例:

struct sockaddr_in socket_addr;
memset(&socket_addr, 0x0, sizeof(socket_addr)); //清零
//填充变量
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket_addr.sin_port = htons(5555);
//将地址与套接字进行关联、绑定
bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(socket_addr));

bind()函数并不是总是需要调用的,只有用户进程想与一个具体的 IP地址或端口号相关联的时候才需要调用这个函数。如果用户进程没有这个必要,那么程序可以依赖内核的自动的选址机制来完成自动地址选择,通常在客户端应用程序中会这样做
/

listen()函数
listen()函数只能在服务器进程中使用,让服务器进程进入监听状态,等待客户端的连接请求,listen()函数在一般在 bind()函数之后调用,在 accept()函数之前调用
int listen(int sockfd, int backlog);

accept()函数
服务器调用 listen()函数之后,就会进入到监听状态,等待客户端的连接请求,使用 accept()函数获取客户端的连接请求并建立连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

connect()函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数用于客户端应用程序中,客户端调用 connect()函数将套接字 sockfd 与远程服务器进行连接

客户端通过 connect()函数请求与服务器建立连接,对于 TCP 连接来说,调用该函数将发生 TCP 连接的握手过程,并最终建立一个 TCP 连接,而对于 UDP 协议来说,调用这个函数只是在 sockfd 中记录服务器IP 地址与端口号,而不发送任何数据

补充: IP 地址格式转换函数
inet_ntop()、inet_pton()与 inet_ntoa()、inet_aton() 类似,但它们还支持 IPv6 地址。它们将二进制 Ipv4 或Ipv6 地址转换成以点分十进制表示的字符串形式,或将点分十进制表示的字符串形式转换成二进制 Ipv4 或Ipv6 地址。
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#define IPV4_ADDR "192.168.1.222"
int main(void)
{
 struct in_addr addr;
 inet_pton(AF_INET, IPV4_ADDR, &addr);
 printf("ip addr: 0x%x\n", addr.s_addr);
 exit(0);
}

示例:在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main(void)
{
 struct in_addr addr;
 char buf[20] = {0};
 addr.s_addr = 0xde01a8c0;
 inet_ntop(AF_INET, &addr, buf, sizeof(buf));
 printf("ip addr: %s\n", buf);
 exit(0);
}

示例:在这里插入图片描述

//

一个简单的 TCP 服务器应用程序示例代码
编写服务器应用程序的流程如下:
①、调用 socket()函数打开套接字,得到套接字描述符;
②、调用 bind()函数将套接字与 IP 地址、端口号进行绑定
③、调用 listen()函数让服务器进程进入监听状态;
④、调用 accept()函数获取客户端的连接请求并建立连接;
⑤、调用 read/recv、write/send 与客户端进行通信
⑥、调用 close()关闭套接字。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define SERVER_PORT 8888 //端口号不能发生冲突,不常用的端口号通常大于 5000

int main(void)
{
 	struct sockaddr_in server_addr = {0};
 	struct sockaddr_in client_addr = {0};
 	char ip_str[20] = {0};
 	int sockfd, connfd;
 	int addrlen = sizeof(client_addr);
 	char recvbuf[512];
 	int ret;
	
	/* 打开套接字,得到套接字描述符 */
 	sockfd = socket(AF_INET, SOCK_STREAM, 0);
 	if (0 > sockfd) {
 	perror("socket error");
 	exit(EXIT_FAILURE);
 	}
	
	/* 将套接字与指定端口号进行绑定 */
 	server_addr.sin_family = AF_INET;
 	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 	server_addr.sin_port = htons(SERVER_PORT);
 	ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
 	if (0 > ret) {
 	perror("bind error");
 	close(sockfd);
 	exit(EXIT_FAILURE);
 	}

	/* 使服务器进入监听状态 */
 	ret = listen(sockfd, 50);
 	if (0 > ret) {
 	perror("listen error");
 	close(sockfd);
 	exit(EXIT_FAILURE);
 	}

	/* 阻塞等待客户端连接 */
 	connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
 	if (0 > connfd) {
	perror("accept error");
 	close(sockfd);
 	exit(EXIT_FAILURE);
 	}
	
	printf("有客户端接入...\n");
 	inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
 	
 	//第一个.sin_addr是为了从struct sockaddr_in结构体中获取struct in_addr类型的成员,这
 	//个成员专门用于存储 IP 地址相关的信息。第二个.s_addr是为了从struct in_addr结构体中获
 	//取实际存储 IP 地址的 32 位整数成员,这样才能将正确的地址数据传递给inet_ntop函数进行转换
 	
 	printf("客户端主机的 IP 地址: %s\n", ip_str);
 	printf("客户端进程的端口号: %d\n", client_addr.sin_port);

	/* 接收客户端发送过来的数据 */
 	for ( ; ; ) {
 	// 接收缓冲区清零
 	memset(recvbuf, 0x0, sizeof(recvbuf));
 	// 读数据
 	ret = recv(connfd, recvbuf, sizeof(recvbuf), 0);
 	if(0 >= ret) {
 	perror("recv error");
 	close(connfd);
 	break;
 	}
	// 将读取到的数据以字符串形式打印出来
 	printf("from client: %s\n", recvbuf);
 	
 	// 如果读取到"exit"则关闭套接字退出程序
 	if (0 == strncmp("exit", recvbuf, 4)) {
 	printf("server exit...\n");
 	close(connfd);
 	break;
 	}
 }

/* 关闭套接字 */
 close(sockfd);
 exit(EXIT_SUCCESS);
	
}

编写客户端程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define SERVER_PORT 8888 //服务器的端口号
#define SERVER_IP "输入自己服务器的IP地址" //服务器的 IP 地址
int main(void)
{
 	struct sockaddr_in server_addr = {0};
 	char buf[512];
 	int sockfd;
 	int ret;
 	/* 打开套接字,得到套接字描述符 */
 	sockfd = socket(AF_INET, SOCK_STREAM, 0);
 	if (0 > sockfd) {
 	perror("socket error");
	exit(EXIT_FAILURE);
 }
 	/* 调用 connect 连接远端服务器 */
 	server_addr.sin_family = AF_INET;
 	server_addr.sin_port = htons(SERVER_PORT); //端口号
 	inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP 地址
 	ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
 	if (0 > ret) {
 	perror("connect error");
 	close(sockfd);
 	exit(EXIT_FAILURE);
 	}
 	printf("服务器连接成功...\n\n");
 /* 向服务器发送数据 */
 	for ( ; ; ) {
 	// 清理缓冲区
 	memset(buf, 0x0, sizeof(buf));
 	// 接收用户输入的字符串数据
 	printf("Please enter a string: ");
 	fgets(buf, sizeof(buf), stdin);
 	// 将用户输入的数据发送给服务器
 	ret = send(sockfd, buf, strlen(buf), 0);
 	if(0 > ret){
 	perror("send error");
 	break;
 	}
 	//输入了"exit",退出循环
 	if(0 == strncmp(buf, "exit", 4))
 	break;
 	}
 close(sockfd);
 exit(EXIT_SUCCESS);
}

编译服务器和客户端程序

客户端运行之后将会去连接远端服务器,连接成功便会打印出信息“服务器连接成功…”,此时服务器也会监测到客户端连接,会打印相应的信息

接下来我们便可以在客户端处输入字符串,客户端程序会将我们输入的字符串信息发送给服务器,服务器接收到之后将其打印出来

在这里插入图片描述

PS:
见到了这种编译方式 :¥{CC} -o … … ,之前一直用gcc -o … …

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/914679.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中文核心期刊论文模板免费下载

大家好&#xff0c;今天我要和大家分享一个对学术研究人员非常有帮助的资源——中文核心期刊论文模板。这个模板严格遵循中文核心期刊的出版标准&#xff0c;旨在帮助作者按照期刊的基本格式和内容要求撰写论文&#xff0c;确保论文结构清晰、规范&#xff0c;从而提高投稿效率…

Linux之DNS服务器

一、DNS 简介 定义与作用&#xff1a;DNS&#xff08;Domain Name System&#xff09;是互联网上的一项服务&#xff0c;作为将域名和 IP 地址相互映射的分布式数据库&#xff0c;使人更方便地访问互联网。使用 53 端口&#xff0c;通常以 UDP 查询&#xff0c;未查到完整信息…

已解决:spark代码中sqlContext.createDataframe空指针异常

这段代码是使用local模式运行spark代码。但是在获取了spark.sqlContext之后&#xff0c;用sqlContext将rdd算子转换为Dataframe的时候报错空指针异常 Exception in thread "main" org.apache.spark.sql.AnalysisException: java.lang.RuntimeException: java.lang.Nu…

物联网低功耗广域网LoRa开发(一):LoRa物联网行业解决方案

一、LoRa的优势以及与其他无线通信技术对比 &#xff08;一&#xff09;LoRa的优势 1、164dB链路预算 、距离>15km 2、快速、灵活的基础设施易组网且投资成本较少 3、LoRa节点模块仅用于通讯电池寿命长达10年 4、免牌照的频段 网关/路由器建设和运营 、节点/终端成本低…

【2024最新】渗透测试工具大全(超详细),收藏这一篇就够了!

【2024最新】渗透测试工具大全&#xff08;超详细&#xff09;&#xff0c;收藏这一篇就够了&#xff01; 黑客/网安大礼包&#xff1a;&#x1f449;基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 所有工具仅能在取得足够合…

Redis - 哨兵(Sentinel)

Redis 的主从复制模式下&#xff0c;⼀旦主节点由于故障不能提供服务&#xff0c;需要⼈⼯进⾏主从切换&#xff0c;同时⼤量 的客⼾端需要被通知切换到新的主节点上&#xff0c;对于上了⼀定规模的应⽤来说&#xff0c;这种⽅案是⽆法接受的&#xff0c; 于是Redis从2.8开始提…

双十二入手什么比较划算?双十二母婴好物推荐

随着双十二购物狂欢节的临近&#xff0c;双十二入手什么比较划算&#xff1f;许多准父母和有小孩的家庭都在寻找最佳的母婴产品优惠。在这个特别的日子里&#xff0c;各大电商平台都会推出一系列针对母婴用品的折扣和促销活动&#xff0c;使得这个时期成为囤货和更新宝宝生活必…

[运维][Nginx]Nginx学习(1/5)--Nginx基础

Nginx简介 背景介绍 Nginx一个具有高性能的【HTTP】和【反向代理】的【WEB服务器】&#xff0c;同时也是一个【POP3/SMTP/IMAP代理服务器】&#xff0c;是由伊戈尔赛索耶夫(俄罗斯人)使用C语言编写的&#xff0c;Nginx的第一个版本是2004年10月4号发布的0.1.0版本。另外值得一…

手动安装Ubuntu系统中的network-manager包(其它包同理)

自己手闲把系统中的network-manager包给删了&#xff0c;导致的结果就是Ubuntu系统彻底没有网络。结果再装network-manager时&#xff0c;没有网络根本装不了&#xff0c;网上的方法都试了也没用&#xff0c;然后就自己源码装&#xff0c;这篇文章就是记录一下怎么手动下载包然…

【 ElementUI 组件Steps 步骤条使用新手详细教程】

本文介绍如何使用 ElementUI 组件库中的步骤条组件完成分步表单设计。 效果图&#xff1a; 基础用法​ 简单的步骤条。 设置 active 属性&#xff0c;接受一个 Number&#xff0c;表明步骤的 index&#xff0c;从 0 开始。 需要定宽的步骤条时&#xff0c;设置 space 属性即…

Java项目实战II基于微信小程序的童装商城(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 基于微信小…

基于Cocos Creator开发的打砖块游戏

一、简介 Cocos简而言之就是一个开发工具&#xff0c;详见官方网站TypeScript简而言之就是开发语言&#xff0c;是JavaScript的一个超集详解官网 今天我们就来学习如何写一个打砖块的游戏&#xff0c;很简单的一个入门级小游戏。 二、实现过程 2.1 布局部分 首先来一个整体…

BigDecimal 详解

《阿里巴巴 Java 开发手册》中提到&#xff1a;“为了避免精度丢失&#xff0c;可以使用 BigDecimal 来进行浮点数的运算”。 浮点数的运算竟然还会有精度丢失的风险吗&#xff1f;确实会&#xff01; 示例代码&#xff1a; float a 2.0f - 1.9f; float b 1.8f - 1.7f; Sys…

使用git命令实现对gitee仓库的下载、更新、上传、上传更新操作。

博客内容为使用git命令实现对gitee仓库的下载、更新、上传、上传更新操作。 1、下载(检出) 使用 git clone 命令 项目仓库地址 eg: git clone https://gitee.com/zzzzzed/ChinessChess.git 如果本地已经下载了该项目则跳过该步骤。 注意使用 git clone 首次检出需要输入用户名…

攻防世界38-FlatScience-CTFWeb

攻防世界38-FlatScience-Web 点开这个here看到一堆pdf,感觉没用&#xff0c;扫描一下 试试弱口令先 源码里有&#xff1a; 好吧0.0 试试存不存在sql注入 根本没回显&#xff0c;转战login.php先 输入1’,发现sql注入 看到提示 访问后得源码 <?php ob_start(); ?>…

JavaWeb后端开发案例——苍穹外卖day01

day1遇到问题&#xff1a; 1.前端界面打不开&#xff0c;把nginx.conf文件中localhost:80改成81即可 2.前后端联调时&#xff0c;前端登录没反应&#xff0c;application.yml中默认用的8080端口被占用&#xff0c;就改用了8081端口&#xff0c;修改的时候需要改两个地方&…

常用中间件介绍

1. RabbitMQ RabbitMQ是一个基于AMQP&#xff08;Advanced Message Queuing Protocol&#xff0c;高级消息队列协议&#xff09;的开源消息代理软件&#xff0c;实现了面向消息的中间件。它支持消息持久化、队列、交换机&#xff08;Exchange&#xff09;和绑定&#xff08;Bin…

【HAProxy06】企业级反向代理HAProxy调度算法之其他算法

HAProxy 调度算法 HAProxy通过固定参数 balance 指明对后端服务器的调度算法&#xff0c;该参数可以配置在listen或backend选项中。 HAProxy的调度算法分为静态和动态调度算法&#xff0c;但是有些算法可以根据不同的参数实现静态和动态算法 相互转换。 官方文档&#xff1…

什么是 eCPRI,它对 5G 和 Open RAN 有何贡献?

这里写目录标题 eCPRI 协议平面&#xff1a;功能分解eCPRI与CPRI的区别CPRI具有以下特点&#xff1a;eCPRI具有以下特点&#xff1a;eCPRI 的优势 所需带宽减少 10 倍适用于 5G 和 Open RAN 的 eCPRI&#xff1a; 通用公共无线接口&#xff08;CPRI&#xff09;是一种行业合作&…

【网页设计】HTML5 和 CSS3 提高

目标 能够说出 3~5 个 HTML5 新增布局和表单标签能够说出 CSS3 的新增特性有哪些 1. HTML5 的新特性 注&#xff1a;该部分所有内容可参考菜鸟教程菜鸟教程 - 学的不仅是技术&#xff0c;更是梦想&#xff01; (runoob.com) HTML5 的新增特性主要是针对于以前的不足&#xf…