项目--五子棋(前置知识)
本项目使用的系统环境是Ubuntu20.04
环境搭建
下载工具的安装
先来补充一个小知识:Ubuntu系统和CentOS系统的 包管理机制不同,用来查询软件源的命令也不同:
- Ubuntu系统使用的是apt包管理系统:
rpm
命令主要用于基于RPM包管理的系统,比如CentOS、Red Hat Enterprise Linux等。而Ubuntu 20.04默认使用的是apt
(Advanced Package Tool)包管理工具,它的数据库结构和查询命令与rpm
是完全不同的体系。所以使用rpm -qa | grep wget
在Ubuntu系统中查询不到结果,因为Ubuntu并不依赖rpm
来管理已安装的软件包信息。 - 正确的查询方式:在Ubuntu 20.04中,要查看是否安装了某个命令,可以使用
dpkg -l | grep 指令
命令。dpkg
是Debian系操作系统(Ubuntu基于Debian)中用于管理软件包的基础工具,dpkg -l
会列出系统中已安装软件包的相关信息,再通过管道符|
配合grep
命令筛选出包含对应命令的相关内容,就能确认是否安装了该命令及其版本等情况。
更换软件源
-
需要更换软件源的情况:
- 网络访问限制:默认的Ubuntu官方软件源服务器可能位于国外,国内用户访问时可能由于网络环境等因素(比如网络带宽限制、连接不稳定等)导致下载软件包速度很慢,甚至出现下载失败的情况。此时更换为国内的镜像源,像阿里云、清华源等,能显著提升下载速度,使软件安装、更新等操作更顺畅。
- 特定软件版本需求:有时候官方源里提供的软件版本未必是你期望的,比如某些软件在官方源中版本较旧,而一些国内的镜像源可能会对部分常用软件进行更及时的版本同步,更换软件源后就有机会获取到更新的版本。
-
可以不更换的情况:
- 网络环境良好:如果所在网络环境对访问Ubuntu官方软件源没有限制,且下载速度能满足日常使用需求,例如在一些网络配置较高且国际网络访问顺畅的办公或科研环境中,那么不更换软件源也是可以正常进行软件的安装、更新等操作的。
- 对软件版本无特殊要求:若平时只是使用一些常规的、稳定版本的软件,且对获取最新版本没有迫切需求,依靠官方源提供的软件版本就能满足日常使用,也可不更换软件源。
更换软件源的步骤
以下是在Ubuntu 20.04系统下更换软件源的常见操作步骤:
- 备份原有的软件源列表文件:
打开终端,输入以下命令备份默认的软件源配置文件sources.list
:
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
这样做是为了在后续若因更换软件源出现问题时,可以方便地恢复到原来的配置。
- 选择合适的镜像源并编辑
sources.list
文件:
常见的国内镜像源有阿里云源、清华源、中科大源等,以阿里云源为例来介绍编辑过程。
- 首先打开
sources.list
文件进行编辑,可以使用文本编辑器(如nano
或vim
),这里以nano
为例:
sudo nano /etc/apt/sources.list
- 然后将原文件中的内容全部删除(可以使用快捷键,如在
nano
中按Ctrl + K
删除整行内容,多次操作来清除全部内容),接着将以下适合Ubuntu 20.04的清华源内容复制粘贴进去(不同Ubuntu版本对应的源内容有差别,需注意选择正确的版本):
deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
- 更新软件源列表:
在终端中输入以下命令来更新软件源列表,让系统获取新的软件包信息:
sudo apt update
至此,软件源就更换完成了,后续可以使用apt
相关命令进行软件的安装、更新等操作,并且下载速度等情况通常会根据所选用的镜像源情况有所改善。
当然,你也可以选择其他镜像源按照类似的步骤进行更换,只是在编辑sources.list
文件时将对应镜像源的正确内容填写进去即可。
以下就是进行安装项目中使用到的工具和软件源了:
安装高版本的编译器
如果不想直接更改系统初始版本的编译器,又想使用高版本的gcc/g++编译器,可以安装开发包,这里我们使用PPA源进行安装:
- 添加PPA源:打开终端,输入以下命令添加
ubuntu-toolchain-r/test
PPA源:
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
- 更新软件包列表:添加PPA源后,更新系统的软件包列表,使系统能够识别新添加的源中的软件包:
sudo apt update
- 安装指定版本的
gcc/g++
开发包:使用以下命令安装所需版本的gcc/g++
开发包,例如安装gcc-9
和g++-9
开发包:
sudo apt install gcc-9 g++-9
安装调试器
sudo apt install gdb
其他
- 安装
wget
工具:sudo apt install wget -y
。可以在安装之前,使用命令dpkg -l | grep wget
查看是否安装了该命令。 - 安装lrzsz: 使用命令
sudo apt install lrzsz
进行安装。 - 安装git:
sudo apt install git
- 安装cmake:
sudo apt install cmake
- 安装boost库
- 安装Jsoncpp库
- 安装MySQL: 参考链接
- 安装WebSocketpp库: 先从GitHub的websocketpp项目中,安装压缩包,并上传到Linux中的一个目录下,解压压缩包,进入websocketpp目录,新建一个build目录,进入build目录,使用
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
命令,最后使用sudo make install
即可。(可以在/usr/include/
查看到websocketpp对应的目录,里面存放着相关头文件)
WebSocketpp
WebSocket介绍
在传统的 web 程序架构中,其交互模式呈现出典型的“一问一答”形态。具体而言,客户端会向服务器发送一个 HTTP 请求,随后服务器针对该请求向客户端返回一个 HTTP 响应。在这样的模式下,服务器往往处于相对被动的地位,因为倘若客户端不主动发起请求,服务器便无法自主地向客户端推送响应消息。
当我们着手开发诸如网页实时聊天或者像本项目所涉及的五子棋游戏这类对实时性要求颇高的程序时,“消息推送”功能就显得尤为关键,即服务器需要能够主动地将消息推送到客户端。然而,若依旧沿用传统的 C/S 通信方案,也就是基于原生的 HTTP 协议来实现这一需求的话,那就只能借助“轮询”的方式了。
所谓轮询,就是客户端要不停地向服务器发送请求,只有这样,当服务器接收到发送方传来的消息后,才能够将新消息推送给接收方。不过,这种轮询的方式存在着明显的弊端:
- 轮询的成本颇高。对于客户端而言,为了保障消息能够及时被获取,需要每隔较短的时间就向服务器发送一个请求,而且这个时间间隔自然是越短越好,如此一来,便会给客户端带来较大的资源消耗和性能压力,成本也就随之升高了。而从服务器端来看,它需要应对众多客户端发起的大量请求,这无疑会给服务器造成极大的负担,使其面临严峻的性能考验。
- 采用轮询方式会导致通信双方无法及时获取到消息的响应。毕竟是依靠客户端不断发起请求来询问服务器是否有新消息,在两次请求的间隔期间,即便服务器已经有了新消息,客户端也无法第一时间知晓,存在消息延迟的问题。
那么,为什么 HTTP 不支持服务器主动向客户端进行消息推送呢?这是由 HTTP 协议本身的设计特点所决定的。HTTP 协议构建在请求 - 响应的模型之上,其设计初衷就是客户端发起请求,服务器针对请求进行响应,整个流程是单向触发的,并没有为服务器主动向客户端推送消息预留相应的机制和通道,所以在原生的 HTTP 协议下,服务器很难做到主动向客户端发送消息,除非借助像轮询这样额外的变通手段,但轮询又存在诸多弊端,这也正是后续 WebSocket 应运而生的重要原因所在。
- WebSocket是从 HTML5 开始支持的⼀种网页端和服务端保持长连接的 消息推送机制。
- WebSocket 更接近于TCP这种级别的通信方式,一旦连接建立完成客户端或者服务器都可以主动地向对方发送数据。
原理解析
WebSocket 协议是用来解决HTTP不支持消息推送问题的,通常使用WebSocket协议是从HTTP协议转换的(WebSocket也能单独使用)。
所以,为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,该请求包含了一些附加头信息,通过这个附加头信息完成握手过程并进行升级协议的过程。具体过程如下:
- 首先,进行 TCP 的三次握手建立连接。
- 然后使用 HTTP 协议进行通信,如完成用户登录、用户注册等工作。
- 在 HTTP 协议中,如果想使用长连接、消息推送的功能,就需要将当前 HTTP 协议的通信连接切换成WebSocket 协议的通信连接。
- 切换协议的过程为:
- 客户端向服务器端发送一个 HTTP 请求,该请求是 WebSocket 协议的切换请求;
- 服务器端收到请求后,服务器端就会向客户端发送一个状态码为 101 的响应;
- 客户端收到响应后,协议切换过程就结束了,之后就可以使用 WebSocket 协议进行通信了。
在协议切换时,客户端发起的请求中的字段有:
- 请求行:
GET /ws HTTP/1.1
,请求方法为 GET、URI通常为 /ws、HTTP版本为 HTTP/1.1。 - 请求报头:
- Connection:
Upgrade
,表示当前连接需要进行升级/切换。 - Upgrade:
WebSocket
,表示当前连接需要升级/切换到 WebSocket 协议。 - Sec-WebSocket-Version:
xxx
,表示 WebSocket 协议的版本(用来让服务器检查当前 WebSocket 协议版本是否支持)。 - Sec-WebSocket-key:
xxx
,是一个密钥信息,双方要进行验证。
- Connection:
服务器查看客户端提供的 WebSocket 协议是否支持,如果服务器支持该版本,就进行响应:
- 状态行:
HTTP/1.1 101 xxx
,HTTP版本为 HTTP/1.1,状态码为101,状态描述为 switch protocol。 - 响应报头:
- Connection:
Upgrade
,表示当前连接需要进行升级/切换。 - Upgrade:
WebSocket
,表示当前连接需要升级/切换到 WebSocket 协议。 - Sec-WebSocket-Accept:
xxx
,是一个密钥信息,根据客户端Sec-WebSocket-key
发送过来的密钥加上一个固定的字符串,采用 SHA 算法 计算出来产生的。
- Connection:
WebSocket报文格式
报文字段比较多,我们重点关注这几个字段:
- FIN: WebSocket 传输数据以消息为概念单位,一个消息有可能由一个或多个帧组成,FIN字段为1,表示末尾帧,为0表示中间帧。
- RSV1~3: 保留字段,只在扩展时使用,若未启用扩展则应置1,若收到不全为0的数据帧,且未协商扩展则立即终止连接。
- opcode: 标志当前数据帧的类型
- 0x0: 表示这是个延续帧,当 opcode 为 0 表示本次数据传输采用了数据分片,当前收到的帧为,其中⼀个分片。
- 0x1: 表示这是一个文本帧。
- 0x2: 表示这是一个二进制帧。
- 0x3-0x7: 保留,暂未使用。
- 0x8: 表示断开连接。
- 0x9: 表示 ping 帧。
- 0xa: 表示 pong 帧。
- 0xb-0xf: 保留,暂未使用。
- mask: 表示 Payload 数据是否被编码,若为1则必有Mask-Key,用于解码 Payload 数据。仅客户端发送给服务端的消息时需要设置。
- Payload length: 数据载荷的长度,单位是字节, 有可能为7位、7+16位、7+64位。假设Payload length = x,则有以下几种情况:
- x为0~126:数据的长度为x字节。
- x为126:后续2个字节代表⼀个16位的无符号整数,该无符号整数的值为数据的长度。
- x为127:后续8个字节代表⼀个64位的⽆符号整数(最高位为0),该无符号整数的值为数据的长度。
- Mask-Key: 当mask为1时存在,长度为4字节,解码规则:
DECODED[i] = ENCODED[i] ^ MASK[i% 4]
。 - Payload data: 报文携带的载荷数据。
WebSocketpp库的使用
先来看一下这个项目所使用到 WebSocketpp 库中的接口和类型:
namespace websocketpp
{
typedef lib::weak_ptr<void> connection_hdl; // 连接类型
template <typename config>
class endpoint : public config::socket_type
{
typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
typedef typename connection_type::ptr connection_ptr;
typedef typename connection_type::message_ptr message_ptr;
typedef lib::function<void(connection_hdl)> open_handler;
typedef lib::function<void(connection_hdl)> close_handler;
typedef lib::function<void(connection_hdl)> http_handler;
typedef lib::function<void(connection_hdl, message_ptr)> message_handler;
/* websocketpp::log::alevel::none 禁⽌打印所有⽇志*/
void set_access_channels(log::level channels); /*设置⽇志打印等级*/
void clear_access_channels(log::level channels); /*清除指定等级的⽇志*/
/*设置指定事件的回调函数*/
void set_open_handler(open_handler h); /*websocket握⼿成功回调处理函数*/
void set_close_handler(close_handler h); /*websocket连接关闭回调处理函数*/
void set_message_handler(message_handler h); /*websocket消息回调处理函数*/
void set_http_handler(http_handler h); /*http请求回调处理函数*/
/*发送数据接⼝*/
void send(connection_hdl hdl, std::string &payload, frame::opcode::value op);
void send(connection_hdl hdl, void *payload, size_t len,
frame::opcode::value op);
/*关闭连接接⼝*/
void close(connection_hdl hdl, close::status::value code,
std::string &reason);
/*获取connection_hdl 对应连接的connection_ptr*/
connection_ptr get_con_from_hdl(connection_hdl hdl);
/*websocketpp基于asio框架实现,init_asio⽤于初始化asio框架中的io_service调度
器*/
void init_asio();
/*设置是否启⽤地址重⽤*/
void set_reuse_addr(bool value);
/*设置endpoint的绑定监听端⼝*/
void listen(uint16_t port);
/*对io_service对象的run接⼝封装,⽤于启动服务器*/
std::size_t run();
/*websocketpp提供的定时器,以毫秒为单位*/
timer_ptr set_timer(long duration, timer_handler callback);
};
template <typename config>
class server : public endpoint<connection<config>, config>
{
/*初始化并启动服务端监听连接的accept事件处理*/
void start_accept();
};
template <typename config>
class connection : public config::transport_type::transport_con_type,
public config::connection_base
{
/*发送数据接⼝*/
error_code send(std::string &payload,
frame::opcode::value op = frame::opcode::text);
/*获取http请求头部*/
std::string const &get_request_header(std::string const &key)
/*获取请求正⽂*/
std::string const &get_request_body();
/*设置响应状态码*/
void set_status(http::status_code::value code);
/*设置http响应正⽂*/
void set_body(std::string const &value);
/*添加http响应头部字段*/
void append_header(std::string const &key, std::string const &val);
/*获取http请求对象*/
request_type const &get_request();
/*获取connection_ptr 对应的 connection_hdl */
connection_hdl get_handle();
};
namespace http
{
namespace parser
{
class parser
{
std::string const &get_header(std::string const &key);
};
class request : public parser
{
/*获取请求⽅法*/
std::string const &get_method();
/*获取请求uri接⼝*/
std::string const &get_uri();
};
}
};
namespace message_buffer
{
/*获取websocket请求中的payload数据类型*/
frame::opcode::value get_opcode();
/*获取websocket中payload数据*/
std::string const &get_payload();
}
namespace log
{
struct alevel
{
static level const none = 0x0;
static level const connect = 0x1;
static level const disconnect = 0x2;
static level const control = 0x4;
static level const frame_header = 0x8;
static level const frame_payload = 0x10;
static level const message_header = 0x20;
static level const message_payload = 0x40;
static level const endpoint = 0x80;
static level const debug_handshake = 0x100;
static level const debug_close = 0x200;
static level const devel = 0x400;
static level const app = 0x800;
static level const http = 0x1000;
static level const fail = 0x2000;
static level const access_core = 0x00003003;
static level const all = 0xffffffff;
};
}
namespace http
{
namespace status_code
{
enum value
{
uninitialized = 0,
continue_code = 100,
switching_protocols = 101,
ok = 200,
created = 201,
accepted = 202,
non_authoritative_information = 203,
no_content = 204,
reset_content = 205,
partial_content = 206,
multiple_choices = 300,
moved_permanently = 301,
found = 302,
see_other = 303,
not_modified = 304,
use_proxy = 305,
temporary_redirect = 307,
bad_request = 400,
unauthorized = 401,
payment_required = 402,
forbidden = 403,
not_found = 404,
method_not_allowed = 405,
not_acceptable = 406,
proxy_authentication_required = 407,
request_timeout = 408,
conflict = 409,
gone = 410,
length_required = 411,
precondition_failed = 412,
request_entity_too_large = 413,
request_uri_too_long = 414,
unsupported_media_type = 415,
request_range_not_satisfiable = 416,
expectation_failed = 417,
im_a_teapot = 418,
upgrade_required = 426,
precondition_required = 428,
too_many_requests = 429,
request_header_fields_too_large = 431,
internal_server_error = 500,
not_implemented = 501,
bad_gateway = 502,
service_unavailable = 503,
gateway_timeout = 504,
http_version_not_supported = 505,
not_extended = 510,
network_authentication_required = 511
};
}
}
namespace frame
{
namespace opcode
{
enum value
{
continuation = 0x0,
text = 0x1,
binary = 0x2,
rsv3 = 0x3,
rsv4 = 0x4,
rsv5 = 0x5,
rsv6 = 0x6,
rsv7 = 0x7,
close = 0x8,
ping = 0x9,
pong = 0xA,
control_rsvb = 0xB,
control_rsvc = 0xC,
control_rsvd = 0xD,
control_rsve = 0xE,
control_rsvf = 0xF,
};
}
}
}
- 日志相关接口:
set_access_channels()
用来设置日志打印等级。由于 WebSocketpp 的打印信息过于繁杂,这里通常不使用该库函数提供的日志信息,所以会将websocketpp::log::alevel::none
作为参数传递给该函数,禁止打印所有日志。 - 指定事件的回调函数: 这些回调函数相关接口,会针对不同的事件设置不同的处理函数。因为 Websocketpp 库在设计搭建服务器时,并不知道这个服务器将来要被用来怎么使用,所以就给不同的事件设置了不同的处理函数指针,这些指针指向用户传递的函数,当服务器接收到指定的数据、触发了指定的事件后就会通过函数指针去调用这些函数,从而完成对应的功能。
set_open_handler()
,设置 WebSocket 协议握手成功的回调处理函数。比如在本项目中,当玩家成功建立 WebSocket 连接、进入房间后,可以通知所有的玩家有一位新玩家进入了房间。set_close_handler()
,设置 WebSocket 协议连接断开的回调处理函数。比如在本项目中,当玩家关闭 WebSocket 连接时,可以清理相关资源,还可以提示一下有人下线了。set_message_handler()
,设置 WebSocket 协议消息到来时的回调处理函数。比如在本项目中,当玩家聊天消息/下棋消息到来时,我们该如何处理这些消息。set_http_handler()
,设置HTTP请求到来时的回调函数。因为 WebSocket 协议是从 HTTP 协议切换过来的,在使用 WebSocket 协议之前,我们还需要HTTP 协议完成一些工作:比如用户在首次访问游戏网站时,需要进行登陆,也就需要获取网页的登录首页、登录完成后进入游戏大厅首页。
- 通信相关接口:
send()
,给客户端发送消息的接口。其声明为void send(connection_hdl hdl, std::string &payload, frame::opcode::value op);
,也就是给指定的连接发送对应的数据。close()
,关闭连接。get_con_from_hdl()
,通过connection_hdl
对象,获取其内部封装的connection_ptr
,通过connection_ptr
对连接进行一系列的操作。
- 其他服务器搭建的接口:
init_asio()
,websocketpp基于asio框架实现,init_asio用于初始化asio框架中的io_service调度器。set_reuse_addr()
,是否设置 TCP 连接IP地址重用。listen()
,设置服务器要绑定监听哪个端口号。run()
,用来启动服务器。set_timer()
,websocketpp提供的定时器,以毫秒为单位。在本项目中,主要用来管理session信息。
搭建一个简单的WebSocketpp服务器
这一步主要是用来让我们熟悉一下WebSocketpp相关接口的使用。
搭建一个服务器的基本步骤如下:
- 实例化一个服务器对象。
- 设置日志输出等级。
- 初始化异步框架
asio
的调度器。 - 注册处理业务的回调函数。
- 设置服务器监听端口号。
- 开始获取新建 TCP 连接。
- 运行服务器。
#include <iostream>
#include <string>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
using wsserver_t = websocketpp::server<websocketpp::config::asio>;
void http_callback(wsserver_t* server, websocketpp::connection_hdl hdl)
{
// 当收到一个HTTP请求时,给客户端返回一个简单页面
// 可以先将当前HTTP连接的请求打印出来,获取连接
wsserver_t::connection_ptr conn = server->get_con_from_hdl(hdl);
std::cout << "body: " << conn->get_request_body() << std::endl;
// 获取请求方法和uri
websocketpp::http::parser::request req = conn->get_request();
std::cout << "method: " << req.get_method() << std::endl;
std::cout << "uri: " << req.get_uri() << std::endl;
// 设置响应格式
std::string body = "<html><body><h1>hello world!</h1></body></html>";
conn->set_body(body);
conn->append_header("Content-Type", "text/html");
conn->set_status(websocketpp::http::status_code::ok);
}
void open_callback(wsserver_t *server, websocketpp::connection_hdl hdl)
{
std::cout << "websocket握手成功!" << std::endl;
}
void close_callback(wsserver_t *server, websocketpp::connection_hdl hdl)
{
std::cout << "websocket连接断开!" << std::endl;}
void message_callback(wsserver_t* server, websocketpp::connection_hdl hdl, wsserver_t::message_ptr message)
{
std::cout << "message: " << message->get_payload() << std::endl;
wsserver_t::connection_ptr conn = server->get_con_from_hdl(hdl);
std::string response = "client say: " + message->get_payload();
conn->send(response, websocketpp::frame::opcode::text);
}
int main()
{
// 1. 实例化一个sever对象
wsserver_t wssvr;
// 2. 设置日志等级
wssvr.set_access_channels(websocketpp::log::alevel::none);
// 3. 初始化asio调度器
wssvr.init_asio();
wssvr.set_reuse_addr(true);
// 4. 注册事件回调函数
wssvr.set_http_handler(std::bind(&http_callback, &wssvr, std::placeholders::_1));
wssvr.set_open_handler(std::bind(&open_callback, &wssvr, std::placeholders::_1));
wssvr.set_close_handler(std::bind(&close_callback, &wssvr, std::placeholders::_1));
wssvr.set_message_handler(std::bind(&message_callback, &wssvr, std::placeholders::_1, std::placeholders::_2));
// 5. 绑定监听端口号
wssvr.listen(8088);
// 6. 获取TCP连接
wssvr.start_accept();
// 7. 启动服务器
wssvr.run();
return 0;
}
JsonCpp
Json介绍
Json
是一种数据交换格式,它采用完全独立于编程语言的文本格式来存储和表示数据。
当有多个数据对象在进行传输或者进行持久化存储时,可以采用Json
数据交换格式进行序列化,转换为一个整体的二进制数据串进行传输。
Json
的数据类型包括对象,数组,字符串,数字等:
- 对象:使用花括号
{ }
括起来的表示一个对象。 - 数组:使用中括号
[ ]
括起来的表示一个数组。 - 字符串:使用常规双引号
" "
括起来的的表示一个字符串。 - 数字:包括整形和浮点数,可以直接使用。
{
"姓名": "xx",
"年龄": 18,
"成绩": [88.2, 99, 70]
}
[
{"姓名": "小明", "年龄": 18, "成绩": [30, 5, 10.2]},
{"姓名": "小红", "年龄": 20, "成绩": [90, 65, 80.9]}
]
JsonCpp介绍
用 JsonCpp 进行序列化和反序列化时,都需要使用到Json::Value
这个类,这个类会对我们的结构化数据进行存储和组织结构化数据的角色。主要作用是中间数据的转存:
- 在序列换之前,要序列化的数据存储在
Json::Value
对象中,然后对Json::Value
对象中的数据进行序列化(使用Writer
类将其转换为字符串)。 - 在进行反序列化时,对一个字符串进行解析,解析得到的各个字段的属性和值会被存放到
Json::Value
对象中,我们通过这个对象获取对应的属性和值。(Json::Value
对象接收解析后的结果反序列化)
该类提供给我们一些成员接口方便我们使用:
class Json::Value
{
Value &operator=(const Value &other); // Value重载了[]和=,因此所有的赋值和获取数据都可以通过
Value &operator[](const std::string &key); // 简单的方式完成 val["name"] = "xx";
Value &operator[](const char *key);
Value removeMember(const char *key); // 移除元素
const Value &operator[](ArrayIndex index) const; // 访问数组,val["score"][0]
Value &append(const Value &value); // 添加数组元素val["score"].append(88);
ArrayIndex size() const; // 获取数组元素个数 val["score"].size();
bool isNull(); // ⽤于判断是否存在某个字段
std::string asString() const; // 转string string name =
val["name"].asString();
const char *asCString() const; // 转char* char *name =
val["name"].asCString();
int asInt() const; // 转int int age = val["age"].asInt();
float asFloat() const; // 转float float weight = val["weight"].asFloat();
bool asBool() const; // 转 bool bool ok = val["ok"].asBool();
};
示例用法:
#include <iostream>
#include <jsoncpp/json/json.h>
int main() {
Json::Value userInfo;
userInfo["name"] = "John Doe";
userInfo["age"] = 30;
userInfo["email"] = "john.doe@example.com";
// 存储数组数据
Json::Value hobbies(Json::arrayValue);
hobbies.append("reading");
hobbies.append("swimming");
userInfo["hobbies"] = hobbies;
// 存储嵌套对象
Json::Value address;
address["street"] = "123 Main St";
address["city"] = "Anytown";
address["zipcode"] = "12345";
userInfo["address"] = address;
}
序列化
将存储的数据序列化为字符串。可以使用 Json::FastWriter
或 Json::StyledWriter
来实现。
Json::FastWriter fastWriter;
std::string output = fastWriter.write(userInfo);
std::cout << output << std::endl;
// 或者使用 StyledWriter 获得更具可读性的输出
Json::StyledWriter styledWriter;
std::string styledOutput = styledWriter.write(userInfo);
std::cout << styledOutput << std::endl;
Json::FastWriter
会将数据快速序列化为紧凑的字符串,而 Json::StyledWriter
会生成带有缩进和换行的更具可读性的 JSON 字符串。
还可以使用StreamWriter
:
class JSON_API StreamWriter
{
virtual int write(Value const &root, std::ostream *sout) = 0;
};
class JSON_API StreamWriterBuilder : public StreamWriter::Factory
{
virtual StreamWriter *newStreamWriter() const;
};
该类允许你将 Json::Value
对象序列化为 JSON 数据并输出到不同的流中。通过 StreamWriterBuilder
可以灵活配置输出格式,然后使用创建的 StreamWriter
实例将数据序列化为所需的流。这样可以满足不同场景下对 JSON 数据序列化的需求,如文件存储、网络传输等。
使用 StreamWriter
的一般步骤
- 创建
StreamWriterBuilder
对象:
通常,你需要使用 Json::StreamWriterBuilder
类来创建 StreamWriter
的实例。这个类提供了一些配置选项,允许你自定义生成的 JSON 输出的格式,例如缩进、精度等。
#include <iostream>
#include <jsoncpp/json/json.h>
#include <sstream>
int main() {
Json::Value root;
root["name"] = "Alice";
root["age"] = 25;
root["score"] = 95.5;
// 创建 StreamWriterBuilder 对象
Json::StreamWriterBuilder builder;
}
- 配置
StreamWriterBuilder
(可选):
你可以通过 builder
对象设置各种选项,例如设置缩进、精度等。
// 设置缩进为 4 个空格
builder["indentation"] = " ";
// 设置精度为 2 位小数
builder["precision"] = 2;
这里将输出的 JSON 数据的缩进设置为 4 个空格,将浮点数的精度设置为 2 位小数。这些设置将影响最终输出的 JSON 字符串的格式。
- 创建
StreamWriter
实例:
使用 builder.newStreamWriter()
方法创建一个 StreamWriter
实例。
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
这里使用 newStreamWriter()
方法创建了一个 StreamWriter
的智能指针,确保资源的自动管理。
- 将
Json::Value
序列化为流:
可以将 Json::Value
对象序列化为不同类型的流,如 std::stringstream
或文件流。
// 序列化为字符串流
std::stringstream ss;
writer->write(root, &ss);
std::cout << ss.str() << std::endl;
// 序列化为文件流
std::ofstream file("output.json");
writer->write(root, &file);
file.close();
上述代码展示了如何将 Json::Value
对象 root
分别序列化为字符串流和文件流。对于字符串流,使用 write()
方法将 root
序列化为 ss
并输出;对于文件流,将结果写入 output.json
文件。
反序列化
class JSON_API CharReader
{
virtual bool parse(char const *beginDoc, char const *endDoc,
Value *root, std::string *errs) = 0;
};
class JSON_API CharReaderBuilder : public CharReader::Factory
{
virtual CharReader *newCharReader() const;
};
CharReader
的优势
- 灵活性: 可以根据不同的需求对解析过程进行配置,如允许注释、设置严格模式等,以适应不同的 JSON 数据来源和格式。
- 错误处理:提供了错误信息反馈机制,当解析失败时,会将错误信息存储在指定的字符串中,方便开发者查找和解决问题。
使用 CharReader
的一般步骤
- 创建
CharReaderBuilder
对象:
通常,要使用 Json::CharReaderBuilder
类来创建 CharReader
的实例。这个类提供了一些配置选项,用于设置解析 JSON 数据时的参数,例如注释的处理、严格模式等。
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
int main() {
std::string jsonStr = "{\"name\":\"Bob\",\"age\":30,\"city\":\"New York\"}";
Json::Value root;
// 创建 CharReaderBuilder 对象
Json::CharReaderBuilder builder;
}
在上述代码中,首先定义了一个包含 JSON 数据的字符串 jsonStr
和一个 Json::Value
对象 root
,并创建了一个 Json::CharReaderBuilder
对象 builder
,它将用于后续的 CharReader
实例创建。
- 配置
CharReaderBuilder
(可选):
你可以通过 builder
对象设置各种选项,例如允许或禁止 JSON 中的注释,设置严格模式等。
// 允许 JSON 中的注释
builder["allowComments"] = true;
// 设置严格模式
builder["strictRoot"] = false;
这里将 allowComments
设置为 true
,允许 JSON 数据中存在注释;将 strictRoot
设置为 false
,表示不强制要求 JSON 数据必须是一个对象或数组作为根元素。
- 创建
CharReader
实例:
使用 builder.newCharReader()
方法创建一个 CharReader
实例。
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
这里使用 newCharReader()
方法创建了一个 CharReader
的智能指针,确保资源的自动管理。
- 将 JSON 字符串解析为
Json::Value
对象:
使用 reader
的 parse()
方法将 JSON 字符串解析为 Json::Value
对象。
std::string errs;
const char* begin = jsonStr.c_str();
const char* end = begin + jsonStr.size();
bool parsingSuccessful = reader->parse(begin, end, &root, &errs);
if (parsingSuccessful) {
std::cout << "Parsing successful" << std::endl;
} else {
std::cout << "Failed to parse JSON: " << errs << std::endl;
}
上述代码展示了如何将 jsonStr
解析为 Json::Value
对象 root
。首先获取 jsonStr
的起始和结束指针,然后调用 reader
的 parse()
方法进行解析。如果解析成功,parsingSuccessful
将为 true
,否则会将错误信息存储在 errs
中。
封装Json工具类
class json_util
{
public:
// 序列化: Json对象 -> 字符串
// 输⼊输出型参数
// root输⼊参数:表⽰要序列化的json对象
// str输出参数: 表⽰序列化之后的字符串
static bool serialize(const Json::Value &root, std::string &str)
{
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::stringstream ss;
int ret = sw->write(root, &ss);
if (ret != 0)
{
std::cout << "Serialize failed!" << std::endl;
return false;
}
str = ss.str();
return true;
}
// 反序列化: 字符串 ->Json对象
// 输⼊输出型参数
// str输⼊参数: 表⽰需要反序列化的字符串
// root输出参数:表⽰反序列化后的json对象
static bool unserialize(const std::string &str, Json::Value &root)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &root,
nullptr);
if (!ret)
{
std::cout << "UnSerialize failed!" << std::endl;
return false;
}
return true;
}
};
MYSQL API
MySQL 是基于 C/S(客户端/服务器)模型实现的,在该架构下,MySQL 数据库的管理由本机的 MySQL 服务器负责。若要对数据库中的数据进行操作,就需要创建一个客户端,客户端通过向服务器发起请求(以 SQL 语句的形式),服务器在接收到请求并执行相应的 SQL 语句后,会将对应的结果返回给客户端。
MySQL API 接口则是帮助开发者实现 MySQL 客户端的关键工具。
下面详细介绍 MySQL 数据库操作流程:
初始化操作
- 初始化 MYSQL 操作句柄:首先要初始化一个 MYSQL 操作句柄,这个句柄包含了众多重要字段,例如其中的 socket 字段用于网络通信,它是客户端与服务器进行数据传输的关键通道。通过初始化句柄,为后续的数据库操作奠定基础。
- 连接 MYSQL 服务器:利用初始化好的句柄,建立与 MySQL 服务器的连接。这一步需要提供正确的服务器地址、端口号、用户名以及密码等信息,确保能够成功连接到目标服务器,从而开启与数据库的交互通道。
- 设置客户端的字符集:为了防止在数据传输和处理过程中出现乱码问题,需要设置客户端的字符集。通常使用统一的字符集,如 UTF - 8,以确保客户端与服务器之间能够正确地识别和处理各种字符数据。
- 选择要操作的数据库:在成功连接到服务器后,需要明确指定要操作的数据库。每个 MySQL 服务器可能管理着多个数据库,通过选择特定的数据库,使得后续的操作都在该指定的数据库环境中进行。
客户端对数据库中数据的操作
客户端对数据库数据的操作主要分为两大类:数据变更操作(新增、修改、删除数据)和数据查询操作。
对于数据变更操作(新增、修改、删除数据),只要对应的 SQL 语句在服务器端成功执行,数据库中的数据就会发生相应的变化,这类操作通常不需要返回特定的结果集,因为其目的在于改变数据库的状态。
而对于数据查询操作,除了要确保查询 SQL 语句执行成功外,还需要将查询结果集保存到客户端本地,以便进一步处理和展示。具体步骤如下:
- 执行 SQL 语句:无论是数据变更操作还是查询操作,都需要通过 MySQL API 接口向服务器发送 SQL 语句,服务器接收到语句后进行解析和执行。
- 将查询结果集保存到本地:若执行的是查询操作,在服务器成功执行 SQL 语句并生成结果集后,客户端需要将该结果集保存到本地内存或其他存储介质中,以便后续操作。
- 获取结果集中的结果条数:保存好结果集后,可以通过相应的函数或方法获取结果集中的行数和列数信息。这对于了解查询结果的规模和结构非常重要,有助于后续的遍历和处理。
- 遍历访问每一条结果:根据获取到的结果集行数和列数,使用循环结构遍历结果集,逐行访问每一条数据记录。在遍历过程中,可以根据业务需求对每条数据进行进一步的处理,如提取特定字段的值、进行数据计算等。
- 释放结果集:当对查询结果集的处理完成后,为了避免内存泄漏和资源浪费,需要及时释放结果集所占用的内存空间。通过调用特定的释放函数,将结果集占用的内存归还给系统。
- 释放 MYSQL 句柄:在完成所有数据库操作后,最后要释放初始化的 MYSQL 操作句柄。这一步同样是为了释放资源,关闭与服务器的连接通道,确保程序在结束数据库操作后能够正确地清理所占用的系统资源。
以下是使用C语言和MySQL C API实现上述MySQL数据库操作流程的示例代码:
#include <mysql/mysql.h>
#include <stdio.h>
#include <stdlib.h>
#define SERVER "localhost"
#define USER "your_username"
#define PASSWORD "your_password"
#define DATABASE "your_database"
void finish_with_error(MYSQL *con) {
fprintf(stderr, "%s\n", mysql_error(con));
mysql_close(con);
exit(1);
}
int main(int argc, char **argv) {
MYSQL *con = mysql_init(NULL);
if (con == NULL) {
fprintf(stderr, "mysql_init() failed\n");
exit(1);
}
if (mysql_real_connect(con, SERVER, USER, PASSWORD, DATABASE, 0, NULL, 0) == NULL) {
finish_with_error(con);
}
if (mysql_set_character_set(con, "utf8")!= 0) {
finish_with_error(con);
}
// 示例:执行插入操作
if (mysql_query(con, "INSERT INTO your_table (column1, column2) VALUES ('value1', 'value2')")) {
finish_with_error(con);
} else {
printf("Insert operation successful\n");
}
// 示例:执行查询操作
if (mysql_query(con, "SELECT * FROM your_table")) {
finish_with_error(con);
}
MYSQL_RES *result = mysql_store_result(con);
if (result == NULL) {
finish_with_error(con);
}
int num_fields = mysql_num_fields(result);
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
for (int i = 0; i < num_fields; i++) {
printf("%s ", row[i]? row[i] : "NULL");
}
printf("\n");
}
mysql_free_result(result);
mysql_close(con);
return 0;
}
代码说明:
-
初始化操作:
- 使用
mysql_init
初始化MySQL操作句柄。 - 使用
mysql_real_connect
连接到MySQL服务器。 - 使用
mysql_set_character_set
设置客户端字符集。 - 使用
mysql_select_db
选择要操作的数据库(在mysql_real_connect
中已经指定了数据库,这里省略)。
- 使用
-
客户端对数据库中数据的操作:
- 插入操作:使用
mysql_query
执行插入SQL语句。 - 查询操作:
- 使用
mysql_query
执行查询SQL语句。 - 使用
mysql_store_result
将查询结果集保存到本地。 - 使用
mysql_num_fields
获取结果集中的列数。 - 使用
mysql_fetch_row
遍历访问每一条结果。 - 使用
mysql_free_result
释放结果集。
- 使用
- 使用
mysql_close
释放MySQL句柄。
- 插入操作:使用
注意: 在编译这段代码时,要指明MySQL动态库mysqlclient
的位置,这里可以使用dpkg -L libmysqlclient-dev | grep ".so"
命令进行查找,我这里是-L/usr/lib/x86_64-linux-gnu/
,然后再链接该动态库-lmysqlclient
。
前端基础
HTML(超文本标记语言)
- 定义:HTML 是用于创建网页的标准标记语言,它通过一系列标签来描述网页的结构和内容,如
<html>
、<head>
、<body>
、<h1>
、<p>
、<a>
等。 - 作用:定义网页的基本框架和元素,包括文本、图片、链接、表格、表单等,是网页的基础骨架,浏览器根据 HTML 代码来呈现网页的内容和布局。
以下是 HTML 中一些常用的标签:
文档结构标签
<html>
:HTML 文档的根标签,包含了整个文档的内容。
<html>
<!-- 文档内容 -->
</html>
<head>
:包含了文档的元数据,如标题、样式表、脚本等,不会直接显示在页面上。
<head>
<title>页面标题</title>
<meta charset='utf-8'>
<link rel="stylesheet" type="text/css" href="styles.css">
<script src="script.js"></script>
</head>
<body>
:包含了页面的可见内容,如文本、图像、链接等。
<body>
<!-- 页面可见内容 -->
</body>
文本标签
<h1>
到<h6>
:用于定义不同级别的标题,<h1>
是最大的标题,<h6>
是最小的标题。
<h1>一级标题</h1>
<h2>二级标题</h2>
<p>
:定义段落。
<p>这是一个段落。</p>
当我们把一段比较长的文本粘贴到 HTML 中,发现并没有分成段落,在 HTML 中使用<p>
标签起到一个段落进行换行。当然也可以在段落中使用<br/>
标签进行换行操作。
<a>
:定义超链接,href
属性指定链接的目标 URL。
<a href="https://www.example.com" target="_blank">链接文本</a>
target是链接打开方式,默认是"_self"
,如果是"_blank"
则用新的标签页打开。
<strong>
:用于强调重要的文本,通常以粗体显示。
<strong>重要内容</strong>
<em>
:用于强调文本,通常以斜体显示。
<em>强调的文本</em>
<span>
:用于对行内元素进行分组或应用样式,通常与 CSS 结合使用。
<span style="color: red;">红色文本</span>
<br>
:插入一个换行符。
<p>这是第一行。<br>这是第二行。</p>
列表标签
<ul>
:定义无序列表,通常与<li>
标签一起使用。
<ul>
<li>列表项 1</li>
<li>列表项 2</li>
</ul>
<ol>
:定义有序列表,通常与<li>
标签一起使用。
<ol>
<li>列表项 1</li>
<li>列表项 2</li>
</ol>
<li>
:定义列表项,用于<ul>
或<ol>
中。
<ul>
<li>无序列表项</li>
</ul>
<ol>
<li>有序列表项</li>
</ol>
图像标签
<img>
:用于插入图像,src
属性指定图像的源文件,alt
属性为图像提供替代文本。
<img src="image.jpg" alt="图像描述" weight=“150px” height="100px">
表格标签
<table>
:定义表格。
<table>
<tr>
<td>单元格 1</td>
<td>单元格 2</td>
</tr>
<tr>
<td>单元格 3</td>
<td>单元格 4</td>
</tr>
</table>
<tr>
:定义表格中的行。
<tr>
<td>行内单元格 1</td>
<td>行内单元格 2</td>
</tr>
<td>
:定义表格中的单元格。
<td>单元格内容</td>
<th>
:定义表格中的表头单元格,通常以粗体显示。
<th>表头单元格</th>
表单标签
表单是让用户输入信息的重要途径。分成两个部分:
- 表单域:包括表单元素的区域,重点是 form 标签。(表单域中可以包含表单控件)
- 表单控件:输入框,提交按钮等,重点是 input 标签。
<form>
:定义表单,action
属性指定表单提交的目标 URL,method
属性指定提交方法(如GET
或POST
)。
<form action="submit.php" method="post">
<!-- 表单元素 -->
</form>
<input>
:用于创建各种输入元素,根据type
属性的不同,可以是文本框、密码框、单选按钮、复选框等。
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" name="submit">
<input type="radio" name="gender" value="male">男
<input type="checkbox" name="hobby" value="reading">阅读
submit
点击后,会向指定的服务器发送请求。
<textarea>
:定义多行文本输入区域。
<textarea rows="4" cols="50">多行文本输入区域</textarea>
<button>
:定义按钮,可用于提交表单或执行其他操作。
<button type="submit">提交</button>
<select>
:定义下拉列表,通常与<option>
标签一起使用。
<select name="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
<option>
:定义下拉列表中的选项。
<option value="beijing">北京</option>
这些只是 HTML 中的一些常用标签,通过组合和使用这些标签,可以创建出丰富多样的网页内容。同时,HTML 还有许多其他标签,可以根据不同的需求和设计来选择使用。
CSS(层叠样式表)
- 定义:CSS 是一种用来为 HTML 文档添加样式的语言,它可以控制网页元素的外观,如颜色、字体、大小、位置、边框、背景等。
- 作用:使网页更加美观和易于阅读,实现网页的布局和排版,将网页的内容与表现形式分离,方便维护和修改网页的样式。例如,可以通过 CSS 设置一个段落的字体颜色为红色,字体大小为 16 像素,段落的外边距为 10 像素等。
以下是 CSS 的基本用法:
一、CSS 的引入方式
-
内联样式:
- 直接在 HTML 元素的
style
属性中添加 CSS 规则,仅对该元素生效。
<p style="color: red; font-size: 16px;">这是一段红色的 16px 大小的文本。</p>
这里,通过
style
属性为<p>
元素设置了颜色为红色,字体大小为 16 像素。 - 直接在 HTML 元素的
-
内部样式表:
- 在 HTML 文档的
<head>
部分使用<style>
元素定义 CSS 规则,可对整个页面中的元素产生影响。
<head> <style> p { color: red; font-size: 16px; } </style> </head> <body> <p>这是一段红色的 16px 大小的文本。</p> </body>
在
<style>
元素中,p
是选择器,用于选择所有的<p>
元素,{}
内是样式规则,包括color: red;
(设置颜色为红色)和font-size: 16px;
(设置字体大小为 16 像素)。 - 在 HTML 文档的
-
外部样式表:
- 创建一个独立的
.css
文件,在 HTML 文档中使用<link>
元素引入。
<head> <link rel="stylesheet" type="text/css" href="styles.css"> </head>
在
styles.css
文件中,可以定义如下样式:p { color: red; font-size: 16px; }
这里,
rel="stylesheet"
表示引入的是样式表,href="styles.css"
表示样式表文件的路径。 - 创建一个独立的
二、CSS 选择器
- 元素选择器:
- 直接使用元素名称作为选择器,对页面中所有该元素应用样式。
上述规则将页面中所有的p { color: red; }
<p>
元素的颜色设置为红色。 - 类选择器:
- 使用
.class
名称作为选择器,需要在 HTML 元素中添加相应的class
属性。
<p class="highlight">这是一段高亮的文本。</p>
这里.highlight { background-color: yellow; }
.highlight
是类选择器,会将所有class
属性为highlight
的元素的背景颜色设置为黄色。 - 使用
- ID 选择器:
- 使用
#id
名称作为选择器,需要在 HTML 元素中添加相应的id
属性。
<p id="unique">这是一段独特的文本。</p>
这里#unique { font-weight: bold; }
#unique
是 ID 选择器,会将id
属性为unique
的元素的字体加粗。 - 使用
- 属性选择器:
- 根据元素的属性来选择元素。
该规则将input[type="text"] { border: 1px solid black; }
type
属性为text
的<input>
元素添加 1 像素的黑色边框。
三、CSS 样式属性
- 字体相关:
font-family
:设置字体类型,如font-family: Arial, sans-serif;
。font-size
:设置字体大小,如font-size: 16px;
。font-weight
:设置字体粗细,如font-weight: bold;
。font-style
:设置字体样式,如font-style: italic;
。
- 颜色和背景相关:
color
:设置文本颜色,如color: red;
。background-color
:设置背景颜色,如background-color: #f0f0f0;
。background-image
:设置背景图像,如background-image: url('image.jpg');
。
- 盒子模型相关:
margin
:设置元素的外边距,如margin: 10px;
表示上下左右外边距都为 10 像素。padding
:设置元素的内边距,如padding: 5px;
表示上下左右内边距都为 5 像素。border
:设置元素的边框,如border: 1px solid black;
表示 1 像素的黑色实线边框。
- 布局相关:
display
:设置元素的显示类型,如display: block;
(块级元素)、display: inline;
(行内元素)、display: flex;
(弹性布局)等。float
:设置元素的浮动,如float: left;
使元素向左浮动。position
:设置元素的定位方式,如position: relative;
(相对定位)、position: absolute;
(绝对定位)等。
四、CSS 伪类和伪元素
- 伪类:
- 用于在元素处于特定状态时添加样式,如
:hover
、:active
、:focus
等。
上述规则将鼠标悬停在a:hover { color: blue; }
<a>
元素上时的颜色设置为蓝色。 - 用于在元素处于特定状态时添加样式,如
- 伪元素:
- 用于添加特殊的元素效果,如
::before
、::after
等。
上述规则会在每个p::before { content: "前缀"; }
<p>
元素的内容前添加 “前缀” 文本。 - 用于添加特殊的元素效果,如
JavaScript(脚本语言)
- 定义:JavaScript 是一种轻量级的脚本语言,可嵌入到 HTML 页面中,由浏览器解释执行。
- 作用:为网页添加交互性和动态效果,例如响应用户的点击、鼠标移动等操作,实现表单验证、动画效果、动态内容更新等功能。例如,当用户点击一个按钮时,通过 JavaScript 可以弹出一个提示框,或者改变网页上某个元素的内容。
以下是 JavaScript 在前端 Web 中控制页面渲染的基本用法:
一、操作 DOM 元素
JavaScript 通过 Document Object Model (DOM) 来控制页面元素,DOM 是一个表示 HTML 文档的树形结构,每个 HTML 元素都是一个节点。
-
获取 DOM 元素:
- 使用
getElementById
方法获取具有特定 ID 的元素。
let element = document.getElementById('elementId');
这里
elementId
是 HTML 元素的id
属性,element
是对该元素的引用。- 使用
getElementsByClassName
方法获取具有特定类名的元素集合。
let elements = document.getElementsByClassName('className');
这里
className
是元素的class
属性,elements
是一个 HTMLCollection 集合,包含所有具有该类名的元素。- 使用
getElementsByTagName
方法获取具有特定标签名的元素集合。
let elements = document.getElementsByTagName('div');
这里会返回一个包含所有
<div>
元素的 HTMLCollection 集合。 - 使用
-
修改元素属性:
- 可以修改元素的属性,如
src
、href
、value
等。
let image = document.getElementById('imageId'); image.src = 'newImage.jpg';
这里将
id
为imageId
的<img>
元素的src
属性修改为newImage.jpg
。- 可以修改元素的样式,使用
style
属性。
let paragraph = document.getElementById('paragraphId'); paragraph.style.color ='red';
这里将
id
为paragraphId
的<p>
元素的颜色修改为红色。 - 可以修改元素的属性,如
二、创建和插入元素
- 创建元素:
- 使用
createElement
方法创建新的元素。
这里创建了一个新的let newDiv = document.createElement('div'); newDiv.textContent = '这是一个新的 div 元素';
<div>
元素,并设置其文本内容。 - 使用
- 插入元素:
- 使用
appendChild
方法将新元素添加到父元素中。
这里将新创建的let parent = document.getElementById('parentId'); parent.appendChild(newDiv);
newDiv
元素添加到id
为parentId
的父元素中。 - 使用
三、修改元素内容
- 使用
textContent
或innerHTML
属性修改元素的文本内容。
let element = document.getElementById('elementId');
element.textContent = '新的文本内容';
- 或者使用
innerHTML
可以插入 HTML 代码。
element.innerHTML = '<strong>新的 HTML 内容</strong>';
四、响应事件
JavaScript 可以对用户的操作做出响应,通过事件处理函数。
添加事件监听器:
- 使用
addEventListener
方法添加事件监听器。
let button = document.getElementById('buttonId');
button.addEventListener('click', function() {
alert('按钮被点击了');
});
这里为 id
为 buttonId
的按钮添加了一个点击事件监听器,当点击按钮时会弹出一个警告框。
可以监听的常见事件包括 click
(点击)、mouseover
(鼠标悬停)、keydown
(按键按下)等。
<html>
<head>
<title>第一个页面</title>
</head>
<body>
<button onclick="test()">普通的button</button>
</body>
<script>
function test(){
alert("这是一个普通的button");
}
</script>
</html>
五、动态样式
JavaScript 可以动态地修改元素的样式,实现页面的动态渲染。
- 使用
classList
属性添加、删除或切换元素的类。
let element = document.getElementById('elementId');
element.classList.add('newClass');
这里为 id
为 elementId
的元素添加了一个名为 newClass
的类。
- 也可以使用
toggle
方法切换类。
element.classList.toggle('active');
六、动画和过渡
JavaScript 可以用于实现动画和过渡效果。
- 使用
setTimeout
和setInterval
:setTimeout
用于在一定延迟后执行操作。
这里在 1000 毫秒(1 秒)后将setTimeout(function() { let element = document.getElementById('elementId'); element.style.opacity = 0; }, 1000);
id
为elementId
的元素的不透明度设置为 0。setInterval
用于周期性地执行操作。
这里每隔 1000 毫秒将let interval = setInterval(function() { let element = document.getElementById('elementId'); element.style.width = (parseInt(element.style.width) + 10) + 'px'; }, 1000);
id
为elementId
的元素的宽度增加 10 像素。
七、示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 控制页面渲染</title>
<style>
.highlight {
background-color: yellow;
}
</style>
</head>
<body>
<div id="container">
<p id="text">原始文本</p>
<button id="changeButton">改变文本</button>
</div>
<script>
let button = document.getElementById('changeButton');
let text = document.getElementById('text');
button.addEventListener('click', function() {
text.textContent = '新的文本内容';
text.classList.add('highlight');
});
</script>
</body>
</html>
在这个示例中:
- 当点击按钮时,使用
addEventListener
监听点击事件。 - 点击按钮后,使用
textContent
修改文本内容。 - 使用
classList.add
为元素添加一个类,改变其背景颜色。
AJAX(异步 JavaScript 和 XML)
- 定义:AJAX 并不是一种新的编程语言,而是几种技术的组合,包括 JavaScript、XMLHttpRequest 对象、DOM 等,用于在不重新加载整个网页的情况下,与服务器交换数据并更新部分网页内容。异步的 HTTP 客户端,可以向服务器发送 HTTP 请求。
- 作用:实现网页的局部刷新,提高用户体验。例如,在一个网页上,当用户在搜索框中输入内容时,通过 AJAX 可以在不刷新整个页面的情况下,向服务器发送请求获取搜索结果,并将结果实时显示在页面上,使网页的交互更加流畅和高效。
这里使用的是jQuery
中的Ajax,本项目的应用场景是:在用户进行登陆时,获取用户名和密码输入框中的内容,并提交给服务器,服务器进行响应后,在浏览器上进行打印。
<html>
<head>
<title>第一个页面</title>
</head>
<body>
<button onclick="test()">普通的button</button>
</body>
<script src="https://cdn.staticfile.net/jquery/1.10.2/jquery.min.js">
</script>
<script>
function test(){
var login_info = {
username: document.getElementById("username").value,
passwd: document.getElementById("password").value
}
// 给服务器发送请求
$.ajax({
type: "post",
url: "http://43.142.67.249:8088/login",
data: JSON.stringify(login_info),
success: function(res, status, xhr){
alert(res);
},
error: function(xhr){
alert(JSON.stringify(xhr));
}
});
}
</script>
</html>
WebSocket
创建一个 websocket 请求,请求服务器创建一个 websocket 长连接,进行持久通信。主要用于聊天输入框内容的获取。
以下是使用 WebSocket 对象发送请求的过程:
一、创建 WebSocket 连接:
首先,在 JavaScript 中创建一个 WebSocket 对象。需要提供一个 WebSocket 服务器的 URL,该 URL 以 ws://
或 wss://
开头(wss://
用于加密的 WebSocket 连接)。例如:
let socket = new WebSocket('ws://example.com/socket');
这行代码创建了一个新的 WebSocket 对象,它将尝试连接到 ws://example.com/socket
的 WebSocket 服务器。
二、监听事件:
- onopen 事件:
当 WebSocket 连接成功建立时,会触发 onopen
事件。可以在此事件处理函数中执行一些操作,例如发送初始消息或设置标志位表示连接已打开。例如:
socket.onopen = function(event) {
console.log('WebSocket 连接已打开');
// 可以在此发送初始消息
socket.send('Hello, Server!');
};
当连接成功打开时,将在控制台打印 WebSocket 连接已打开
,并向服务器发送消息 Hello, Server!
。
- onmessage 事件:
当从服务器接收到消息时,会触发 onmessage
事件。该事件处理函数的参数包含了从服务器接收到的数据,可以是文本或二进制数据。例如:
socket.onmessage = function(event) {
console.log('收到服务器消息: ' + event.data);
// 处理接收到的消息
};
这里将接收到的消息打印到控制台,并可以对消息进行进一步处理,比如更新网页的内容。
- onerror 事件:
如果在连接过程中出现错误,会触发 onerror
事件。可以在此处理错误情况,例如显示错误信息。例如:
socket.onerror = function(event) {
console.error('WebSocket 错误: ' + event);
};
此代码将错误信息输出到控制台,以便调试和处理错误。
- onclose 事件:
当 WebSocket 连接关闭时,会触发 onclose
事件。可以在此进行一些清理工作或通知用户连接已关闭。例如:
socket.onclose = function(event) {
console.log('WebSocket 连接已关闭');
};
三、发送消息:
使用 send()
方法发送消息。可以发送文本消息或二进制数据,例如:
socket.send('This is a message from client');
这将发送文本消息 This is a message from client
到服务器。
四、关闭连接:
可以使用 close()
方法关闭 WebSocket 连接,例如:
socket.close();
这将关闭当前的 WebSocket 连接。
五、示例代码:
let socket = new WebSocket('ws://example.com/socket');
socket.onopen = function(event) {
console.log('WebSocket 连接已打开');
socket.send('Hello, Server!');
};
socket.onmessage = function(event) {
console.log('收到服务器消息: ' + event.data);
// 假设接收到的数据是 JSON 格式
let data = JSON.parse(event.data);
// 更新网页元素
document.getElementById('result').innerHTML = data.message;
};
socket.onerror = function(event) {
console.error('WebSocket 错误: ' + event);
};
socket.onclose = function(event) {
console.log('WebSocket 连接已关闭');
};
// 例如,点击按钮发送消息
document.getElementById('sendButton').onclick = function() {
let message = document.getElementById('messageInput').value;
socket.send(message);
};
在这个示例中:
- 创建了一个 WebSocket 对象并连接到服务器。
- 当连接打开时发送一条消息。
- 接收到消息时,将消息解析为 JSON 并更新页面元素。
- 点击按钮时,将输入框中的消息发送到服务器。
六、WebSocket 优点和应用场景:
- 优点:
- 全双工通信:允许客户端和服务器之间同时进行双向通信,比 AJAX 的请求/响应模式更加灵活。
- 实时性高:适用于实时通信场景,如聊天应用、实时数据更新(如股票价格、实时监控数据)。
- 低延迟:避免了 HTTP 的开销,提高了通信效率。
- 应用场景:
- 在线聊天应用:用户之间可以实时发送和接收消息。
- 在线游戏:玩家之间的实时操作和数据交互。
- 实时数据更新:如金融数据、传感器数据的实时更新等。
WebSocket 提供了一种强大的客户端-服务器通信机制,尤其适用于需要实时双向通信的场景,通过创建 WebSocket 对象,监听相关事件,并使用 send()
方法发送消息,可以实现高效、实时的网络通信。
五子棋项目的前置知识就介绍到这里了……