为了账号安全,请及时绑定邮箱和手机立即绑定
1. 端口

在学习Socket编程之前我们要了解一下概念。端口不是物理设备,而是促进服务器和客户端之间通信的抽象概念。端口是由一个 2 的 16 次幂的整数表示的,所以,一台机器最多可以有65536个端口(0~65535)。端口一共分为三个种类:知名端口:0 ~ 1023(例如:80端口用于http,25端口用于smtp)。注册端口:1024 ~ 49151。动态/私有端口:49152 ~ 65535。

3. 端口号

端口号是用 16 bit 无符号整数表示的,取值范围是 0~65535,总共可以分配 65536 个端口号。端口号属于稀缺资源,是由 Internet Assigned Numbers Authority (IANA)统一管理和分配的。端口号当前分配状况:0 ~ 1023此区间内的端口号叫做知名端口号,已经被系统或者是一些知名的服务所占用,比如:端口号用途20 , 21用于 FTP 协议23用于 telnet 协议80用于著名的 HTTP 服务443用于 HTTPS 服务1023 ~ 65535此区间端口号也有很多被知名的应用占有,比如:端口号用途1433用于 SQL Server 服务等等1935用于 RTMP 服务3306用于 MySQL 服务8080作为 HTTP 服务的另外一个端口号

3. 端口号概念

所谓的端口,就好像是门牌号一样,客户端可以通过 ip 地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号 。

1. 端口号概念

所谓的端口,就好像是门牌号一样,客户端可以通过 ip 地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号 。而 MySQL 服务默认指定的端口号为 3306,这个在之前介绍安装 MySQL 的时候,其中配置 my.ini 文件的内容时候,其中就有端口号配置,内容如下:

3.1 Server 监听端口

首先由服务端初始化 Socket 接口,然后绑定并监听自己的端口号,此时服务端会阻塞式等待客户端连接。

3.2 Client 连接端口

客户端可以在需要发送消息的时候初始化 Socket 接口,设置服务端的 IP 地址和端口号就可以连接到服务器,接着在连接成功之后,双方就完成了连接的建立。

4. 获取当前访问端口

可以使用 Request 对象中的 port() 方法获取当前访问的端口号,代码如下: public function getInfo(Request $request){ halt($request->port()); }执行结果如下图所示:Tips: 所谓的端口,就好像是门牌号一样,客户端可以通过ip地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号 。

3. RESTful 风格后端接口

前后端分离时,后端接口可不能太随意,目前后端接口编写大多遵循 RESTful 风格。做后端接口的公司这么多,如果大家都定义自己的规范,是不利于公司之间的合作的。如果大家都能遵循一个规范来开发接口,很明显相关人员都能省心不少。RESTful 就是一种非常流行的 HTTP 接口规范,简单明了,使用的人也多,用它准没错。规范的意义,就是提供标准,提高效率。汽车行业标准化程度已经很高了,软件行业还需努力!(图片来源于网络,版权归原作者所有)

1.2 端口映射访问容器

将宿主机的本地端口,与指定容器的服务端口进行映射绑定,之后访问宿主机端口时,会将请求自动转发到容器的端口上,实现外部对容器内网络服务的访问。创建名为 n0 的 nginx 容器,映射宿主机 8000 端口到它的 80 端口docker run -d -t -p 8000:80 --name n0 nginxTips:指定的宿主机端口必须是未被占用的端口,否则操作会失败,且生成一个无法正常启动的容器 n0, 需要手动删除。使用 docker port n0 查看 n0 的端口映射信息,显示如下:80/tcp -> 0.0.0.0:8000打开浏览器,地址栏输入 http://localhost:8000 或 http:// 宿主机 IP:8000, 都能访问到 n0 的 nginx 服务。如果需要绑定多个容器端口,可以连续使用 -p 参数多次指定docker run -d -t -p 8001:80 -p 8433:443 --name n1 ngin如果不想主动指定宿主机端口,可以使用 -P 参数,宿主机随机使用一个可用端口与容器端口进行映射docker run -d -t -P --name n2 nginx如果只想使用宿主机上特定的网口与容器进行映射docker run -d -t -p 192.168.1.13:8002:80 --name n3 nginxTips:此处 192.168.1.13 指代 宿主机映射网口的 IP 地址,需要根据网口的实际 IP 更改 *。我们执行 docker ps 可能出现如下几个的 nginx 容器:再执行 iptables -t nat -nL 查看下防火墙:比对上面两个的输出,不难发现,这种端口转发方式的本质是通过配置 iptables 规则转发实现的,效率较低,如果容器的服务端口数量过多,需要配置较多的映射,占用大量宿主机端口,也不便于管理。不再使用的容器记得删除掉,释放资源和空间docker rm -f n0 n1 n2 n3

6. EXPOSE:指定容器将要监听的端口

用法:EXPOSE 端口号示例:EXPOSE 8080启动容器时,如果我们使用自动映射 -P 或 --net=host 宿主机网络模式,容器中 EXPOSE 标记暴露的端口与宿主机网络会自动建立关联。如果没有指定 EXPOSE,使用 -p 手动指定端口映射参数也可以访问到容器内提供服务的端口。EXPOSE 显式地标明镜像开放端口,一定程度上提供了操作的便利,也提高了 Dockerfile 的可读性和可维护性。

4. 通过进程 PID 查找端口号

nginx 软件服务启动之后默认的端口号是 80,可以使用如下命通过 PID 查找到端口号,命令如下:netstat -apn | grep 12471执行结果如下:Tips:如图所示,可以知道进程 PID = 12471 这个进程符合 nginx 启动的端口号。

3. 修改监听地址和端口

上面说到要在浏览器中输入 localhost:5000 才能看到运行效果。其中 localhost 代表本地 IP 地址,你也可以把 localhost 改成 127.0.0.1,效果和 localhost 是一样的。那么 5000 代表什么呢?其实 5000 是一个端口号,你可以把端口号理解为是门牌号。我们的电脑会为每一个应用程序划分运行区间,每一个运行区间的标识就是端口号,我们可以通过端口号来访问对应的应用程序,这和你在酒店根据门牌号找到房间是一个道理。默认情况下,Flask 应用程序监听地址 127.0.0.1 和端口 5000。如果你不想让 Flask 程序运行在 5000 端口,也可以通过参数设置可以修改默认的监听地址和端口,比如我们想让 Flask 程序运行在一个比较吉利的端口 8888 上面,可以这样修改代码:from flask import Flaskapp = Flask(__name__)@app.route('/')def hello_world(): return '<b>Hello World</b>'if __name__ == '__main__': app.run(host = '0.0.0.0', port = 8888)设定 app.run 的函数参数 host 为 ‘0.0.0.0’,表示监听每一个可用的网络接口;设定 app.run 的函数参数 port 为 8888,表示监听端口 8888。

1. 桥接网络模式与端口映射模式

Tips:桥接宿主机网络与 端口映射模式的设定参考 Docker 网络 部分内容。桥接模式,多个宿主机位于同一个局域网,将每一个宿主机上的容器网络桥接到宿主机网络中,容器和宿主机同在一个局域网中互相通信。每台宿主机上的容器都直接从局域网中获取IP地址,容易导致IP地址冲突。端口映射模式,是将容器的服务所运行的端口映射到宿主机的某一个端口,然后其他的容器通过宿主机的对应端口进行访问。只要宿主机间能互相通信,容器之间就能通过宿主机的指定端口进行通信。但是这种方式需要对每一个容器都映射端口,而且宿主机的端口也有限。

2. 接口的好处

编写 API 有什么好处呢?由于 API 就是把 Web 应用的功能全部封装了,所以,通过 API 操作数据,可以极大地把前端和后端的代码隔离,使得后端代码更易于测试,前端代码编写更加简单。此外,如果我们把前端页面看作是一种用于展示的客户端,那么 API 就是为客户端提供数据、操作数据的接口。这种设计可以获得极高的扩展性。例如:我们经常使用的淘宝商城就有很多的客户端,Web, iOS 和 Android 。这些客户端其实都是共用的一套后端代码。但是当我们在 Web 端搜索商品时得到的结果和在 iOS 和 Android 端得到的结果却是一样的。这是因为,我们在不同用户端搜索的时候,访问了后端同一个 API 。这样后端针对前端的同一种需求,只需开发一种接口,就可满足前端不同终端对于该资源的调用,而无需针对不同终端做差异化开发,这便大大降低了开发工作量,节约了开发时间。

3.1 服务端

public class Server { public static void main(String[] args) { // 创建一个serverSocket监听本地的6000端口 try(ServerSocket server = new ServerSocket (6000)) { // 当有客户端连接上就建立一个socket通道 Socket socket = server.accept(); OutputStream outputStream = socket.getOutputStream(); // 有客户端连接上来就主动发送问候语 outputStream.write("hello".getBytes()); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } }}

2. 前后端分离

前后端分离这种概念和技术,早就流行多年了。具体点说,前端编写 HTML 页面,然后通过 Ajax 请求后端接口;后端把接口封装成 API ,返回 JSON 格式的数据;前端接收到 JSON 返回数据后渲染到页面。前端工程师根本不需要懂后端,调用后端接口就行。后端使用 Spring Boot 控制器返回 JSON 十分简单,给方法添加个注解,就能将返回值序列化为 JSON 。前端干前端的活,后端干后端的活,职责分明,界限明确。这就是前后端分离的好处啊!

5.1 服务端启动类

public class NettyServer { public static void main(String[] args) { //线程组-主要是监听客户端请求 NioEventLoopGroup bossGroup = new NioEventLoopGroup(); //线程组-主要是处理具体业务 NioEventLoopGroup workerGroup = new NioEventLoopGroup(); //启动类 ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap //指定线程组 .group(bossGroup, workerGroup) //指定 NIO 模式 .channel(NioServerSocketChannel.class) //双向链表管理 .childHandler(new ChannelInitializer<NioSocketChannel>() { protected void initChannel(NioSocketChannel ch) { //责任链,指定自定义处理业务的 Handler ch.pipeline().addLast(new NettyServerHandler()); } }); //绑定端口号 serverBootstrap.bind(80); }}代码说明:以上都是模板代码,需要变动的是根据不同的业务自定义对应的 Handler,并且在 initChannel () 添加逻辑处理器;根据实际情况指定 bind () 方法的端口号,注意的是端口号不能和其它端口号冲突;这里大家先熟练掌握模板代码的编写,后面章节会讲解 NioEventLoopGroup、Pipeline 等核心组件的原理。

3.自动生成接口文档

在前后端分离的项目中,在完成接口的开发之后,后端开发人员需要为前端人员编写接口文档,介绍接口调用方法和需要传递的参数。在 Django Rest framework 编写接口后,可以自动生成接口文档,这无疑减轻了不少工作量。接口文档以网页的方式呈现,在生产接口文档前,需要做如下操作:

3. 前后端分离模式

在前后端分离的 Web 应用中,后端此时扮演的角色只是提供前端所需的数据,不再负责样式的渲染。前端的展示样式,完全由前端负责,针对不同的终端,进行不同的渲染,这样不仅提高了用户体验,还在很大程度上降低了前后端的耦合度。由于不同终端所需的数据几乎一样,后端仅需专注于业务逻辑,为前端提供数据即可,不再需要适配不同终端,提供不同页面,这便大大降低了开发工作量。在前后端分离的应用模式中,我们通常将后端开发的每个视图都称为一个接口,或者API,前端通过访问接口来对数据进行增删改查。前后端分离模式示意图

2.6 修改客户端配置

修改客户端的配置文件 application.properties ,以便指定客户端指向的服务端的地址。由于刚刚服务端已经占用了 8080 端口,所以将客户端的端口设置为 8091 。还有一个必要设置是客户端的名称,当我们监控的项目实例比较多时,需要通过客户端名称来区分。实例:# 配置端口server.port=8091# 配置监控管理端地址spring.boot.admin.client.url=http://127.0.0.1:8080# 客户端的名称,用于区分不同的客户端spring.boot.admin.client.instance.name=CLIENT1TIps:此处指定监控管理端地址使用的是 spring.boot.admin.client.url ,我个人认为应使用 spring.boot.admin.server.url 更加合理。当然大家不用纠结于此,此处只是特别提示。

3.2 爬取客户端渲染的网页

在互联网早期,网站的内容都是一些简单的、静态的页面,服务器后端生成网页内容,然后返回给浏览器,浏览器获取 html 文件之后就可以直接解析展示了,这种生成 HTML 文件的方式被称为服务器端渲染。而随着前端页面的复杂性提高,出现了基于 ajax 技术的前后端分离的开发模式,即后端不提供完整的 html 页面,而是提供一些 api 返回 json 格式的数据,前端调用后端的 API 获取 json 数据,在前端进行 html 页面的拼接,最后后展示在浏览器上,这种生成 HTML 文件的方式被称为客户端渲染。简单的使用 requests 库无法爬取客户端渲染的页面:requests 爬下来的页面内容并不包含真正的数据只能通过调用后端的 API 才能获取页面的数据有两种方式爬取客户端渲染的网页:分析网页的调用后端 API 的接口这种方法需要分析网站的 JavaScript 逻辑,找到调用后端 API 的的代码,分析 API 的相关参数。分析后再用爬虫模拟模拟调用后端 API,从而获取真正的数据。很多情况下,后端 API 的接口接口带着加密参数,有可能花很长时间也无法破解,从而无法调用后端 API。用模拟浏览器的方式来爬取数据在无法解析后端 API 的调用方式的情况下,有一种简单粗暴的方法:直接用模拟浏览器的方式来爬取,比如用 Selenium、Splash 等库模拟浏览器浏览网页,这样爬取到的网页内容包含有真实的数据。这种方法绕过分析 JavaScript 代码逻辑的过程,大大降低了难度。

3. API 接口

Localstorage 是一个简单的 key/value 形式的数据库,以键值对的方式存储,所以提供的接口主要是基于 k/v 的操作。基于提供的接口只能存储简单的一维数组,但是有些业务场景可能会牵涉到多维数据甚至对象的存储,怎么办?建议使用 JSON.stringify() 将数据转化成字符串方式再存储;使用复杂的前端数据库,例如 indexDB,具体不做深入讨论。

4. 前端开发流程

前后端分离开发,实际上前端工作就简化了。我们直接新建项目文件夹 shop-front (商城前端项目文件夹),然后将前端页面放到该文件夹即可。注意该页面不需要放到 Spring Boot 项目目录下,随便找个目录放置即可。实际开发过程中,后端和前端的项目可能都不在一台计算机上。前端核心业务代码如下,由于前端技术不是本节介绍的重点,所以不再详细解释,感兴趣的同学可以从 Git仓库 查看完整代码 。实例: //初始化方法 $(function () { var row = ""; $.ajax({ type: "GET", url: "http://127.0.0.1:8080/goods", //后端接口地址 dataType: "json", contentType: "application/json; charset=utf-8", success: function (res) { $.each(res, function (i, v) { row = "<tr>"; row += "<td>" + v.id + "</td>"; row += "<td>" + v.name + "</td>"; row += "<td>" + v.price + "</td>"; row += "<td>" + v.pic + "</td>"; row += "</tr>"; $("#goodsTable").append(row); }); }, error: function (err) { console.log(err); } }); });开发完该页面后,直接使用浏览器双击打开,查看控制台发现有错误信息提示。浏览器控制台返回错误信息考验英文水平的时候到了!关键是 has been blocked by CORS policy ,意味着被 CORS 策略阻塞了。我们的前端页面请求被 CORS 阻塞了,所以没成功获取到后端接口返回的数据。

2.4 前端应用部署

前端应用的部署更加简单,我们直接在云服务器上下载 nginx 然后解压。打开网址 http://nginx.org/en/download.html ,点击下图中的链接下载即可。nginx 下载链接下载解压后,将前端页面直接放到 nginx/html 目录下即可。当然如果有很多网页,可以先在该目录下建立子目录便于归类网页。我们建立 shop-front 目录(表示商城系统的前端项目),然后将网页放入其中,效果如下:商城系统前端项目目录内容注意还需要修改 goods.html 中访问的后端 URL 地址,假设云服务器的公网 IP 为 x.x.x.x ,则修改为:实例:$.ajax({ type: "GET", url: "http://x.x.x.x:8080/goods", //后端接口地址 dataType: "json", contentType: "application/json; charset=utf-8", success: function (res) { $.each(res, function (i, v) { row = "<tr>"; row += "<td>" + v.id + "</td>"; row += "<td>" + v.name + "</td>"; row += "<td>" + v.price + "</td>"; row += "<td>" + v.pic + "</td>"; row += "</tr>"; $("#goodsTable").append(row); }); }, error: function (err) { console.log(err); } });此处解释下后端地址 http://x.x.x.x:8080/goods , HTTP 代表协议, x.x.x.x 代表云服务器公网地址, 8080 是我们后端项目的启动端口,由于我们没有在配置文件中设置,所以默认就是 8080 ,最后 goods 是控制器中设定的后端接口路径。双击 nginx.exe 启动 nginx ,由于 nginx 默认启动端口是 80 ,所以此时访问 http://x.x.x.x ,效果如下,说明 nginx 启动成功!nginx 已启动成功

2.3 Consul配置-Server端

全局配置-consul_config.json:vi /consul/consul.d/consul_config.json{ "datacenter":"datacenter-1", "data_dir":"/consul/data", "log_level": "INFO", "node_name": "consul-server-01", "bootstrap_expect": 2,"server": true, // ui界面在一台server设置为true,其它设置为false"ui":true, // 如果绑定具体IP,会导致consul集群之间tcp8301端口失败"bind_addr":"0.0.0.0", // 客户端允许访问ip"client_addr":"0.0.0.0","enable_script_checks":true,// 加入集群"start_join":["192.168.0.1", "192.168.0.2", "192.168.0.3"],"retry_join":["192.168.0.1", "192.168.0.2", "192.168.0.3"],"ports": {"dns": 53}}

4. Java UDP 客户端

我们创建一个简单的 UDP 客户端程序,代码如下:import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetSocketAddress;import java.net.SocketAddress;public class UDPClient { private static final int PORT = 9002; private static final String DST_HOST = "127.0.0.1"; private static final int RECV_BUFF_LEN = 1500; private static byte[] inBuff = new byte[RECV_BUFF_LEN]; public static void main(String[] args) { // 创建 UDP 客户端 Socket,选择无参构造方法,由系统分配本地端口号和网络接口 try (DatagramSocket udpClient = new DatagramSocket()){ // 构造发送的目标地址,指定目标 IP 和目标端口号 SocketAddress to = new InetSocketAddress(DST_HOST, PORT); while (true){ String req = "Hello Server!"; // 构造发送数据包,需要传入消息内容和目标地址结构 SocketAddress DatagramPacket message = new DatagramPacket(req.getBytes(), req.length(), to); // 发送消息 udpClient.send(message); System.out.println("Send UDP message:" + req + " to server:" + message.getSocketAddress().toString()); // 构造接收消息的数据包,需要传入 byte 数组 DatagramPacket inMessage = new DatagramPacket(inBuff, inBuff.length); // 接收消息 udpClient.receive(inMessage); System.out.println("Recv UDP message:" + new String(inMessage.getData(), 0, inMessage.getLength()) + " from server:" + inMessage.getSocketAddress().toString()); // 每隔 2 秒发送一次消息 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } }}

1. 部署前端

前端框架如 Vue 打包出来往往是静态的文件 index.html 加上一个 static 目录。static 目录下有 fonts、css、js、img等静态资源目录。前端的访问是从 index.html 开始的。假设服务器上打包出的前端代码放到/root/test-web目录下,对应部署前端的配置如下:...http{ server { # 监听8080端口 listen 8080; # 指定域名,不指定也可以 server_name www.xxx.com; # 浏览器交互调参,打开gzip压缩、缓存等等 gzip on; ... location / { root /root/test-web; # 也可以简单使用 index index.html try_files $uri $uri/ /index.html; } # vue 页面中向后台 java 服务发送请求 ... }}...

4.前后端分离模式开发规范

前后端分离模式逐渐成为主流,随之而来的问题也很突出。通常,后端服务开发完成,开发人员会写一份后端接口调用的说明文档。不同公司、不同项目甚至不同开发人员都有各自的喜好,因而开发好的后端服务,也是千奇百怪,前端开发人员为了使用使用后端服务,必须逐个对照说明文档才能知道调用方式。小型项目还好说,大型项目接口繁杂,如果仍然采用千奇百怪的接口调用方式,就会无形中增加开发难度。那么是否有一套接口开发规范,是否有一个见名知意的接口调用方式呢?答案是有的,这就是 RESTful 规范。后续内容,我们将正式开始学习 RESTful。

4. 接口的增强

Java 8 对接口做了进一步的增强。在接口中可以添加使用 default 关键字修饰的非抽象方法。还可以在接口中定义静态方法。增强后的接口看上去与抽象类的功能越来越类似了。关于默认方法和静态方法,我们在Java接口这一小节,已经结合实例详细介绍过了,忘记了的同学可以回去温习一下。

2.4 客户端凭证式

有的应用并没有明确的前端应用,比如控制台程序或者是服务接口,这种情况下就需要用到客户端凭证式获得凭证了。这种方式下,没有「人」的参与,只有认证服务对后台服务的认证。客户端凭证式

首页上一页1234567下一页尾页
直播
查看课程详情
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号