[javaWeb]Socket网络编程
网络编程:写一个应用程序,让这个程序可以使用网络通信。这里就需要调用传输层提供的 api。
Socket套接字
传输层提供协议,主要是两个: UDP和TCP
提供了两套不同的 api,这api也叫做socket api。
UDP和 TCP 特点对比:
- UDP: 无连接,不可靠传输,面向数据报,全双工
- TCP : 有连接,可靠传输,面向字节流,全双工
有连接,无连接:客户端和服务器之间,彼此之间使用内存空间保存对端的信息,双方都保存这个信息此时“连接”就出现了。
可靠传输,不可靠传输:不是说,A 给 B 发的消息 100% 能到(这个要求太难了)。A 尽可能的把消息传给 B.并且在传输失败的时候,A 能感知到,**或者在传输成功的时候,也能知道自己传输成了。**可靠传输效率低,不可靠传输效率高。
面向字节流,面向数据报:
- TCP 和 文件操作类似,都是“流”式的(由于这里传输的单位是字节,称为字节流)
- UDP 是面向数据报,读写的基本单位是一个 UDP 数据报(包含了一系列的数据/属性)
全双工,半双工:
- 全双工:一个通道,可以双向通信
- 半双工:一个通道,只能单向通信
UDP数据报套接字编程
DatagramSocket API
DatagramSocket API 是一个 Socket 对象。操作系统,使用文件这样的概念,来管理一些软硬件资源,操作系统也是使用文件的方式来管理网卡的。表示网卡的这类文件,称为 Socket 文件。Java 中的 socket 对象,就对应这系统里的 socket 文件。
构造方法:
方法签名 | 方法说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口**(一般用 于服务端)** |
客户端使用哪个端口,系统自动分配。但是服务器使用哪个端口,是手动指定的。对于服务器来说,需要有一个固定的端口号,方便其他客户端找到。一个客户端的主机,上面运行的程序很多,天知道你手动指定的端口是不是被别的程序占用了。让系统自动分配一个端口是更明智的选择。服务器是完全在程序猿手里控制的。程序猿可以把服务器上的多个程序安排好,让他们使用不同的端口。
普通方法:
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻 塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
DatagramPacket API
表示了一个 UDP 数据报,代表了系统中设定的 UDP 数据报的二进制结构。
构造方法:
方法签名 | 方法说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数 length) |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造一个DatagramPacket以用来发送数据报,发送的数据为字节 数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号 |
普通方法:
方法签名 | 方法说明 |
---|---|
SocketAddress getSocketAddress() | SocketAddress对象包含了IP地址和端口号 |
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据。udp 数据报载荷部分(完整的应用层数据报) |
示例一:回显服务器(echo server).
回显服务器(echo server):客户端发啥,服务器返回啥.
// UDP 的 回显服务器.
// 客户端发的请求是啥, 服务器返回的响应就是啥.
public class UdpEchoServer {private DatagramSocket socket = null;// 参数是服务器要绑定的端口public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}// 使用这个方法启动服务器.public void start() throws IOException {System.out.println("服务器启动!");while (true) {// 反复的, 长期的执行针对客户端请求处理的逻辑.// 一个服务器, 运行过程中, 要做的事情, 主要是三个核心环节.// 1. 读取请求, 并解析DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);// 这样的转字符串的前提是, 后续客户端发的数据就是一个文本的字符串.String request = new String(requestPacket.getData(), 0, requestPacket.getLength());// 2. 根据请求, 计算出响应String response = process(request);// 3. 把响应写回给客户端// 此时需要告知网卡, 要发的内容是啥, 要发给谁.DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);// 记录日志, 方便观察程序执行效果.System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(),request, response);}}// 根据请求计算响应. 由于是回显程序, 响应内容和请求完全一样.public String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer server = new UdpEchoServer(9090);server.start();}
}
客户端代码:
public class UdpEchoClient {private DatagramSocket socket = null;private String serverIp;private int serverPort;// 服务器的 ip 和 服务器的端口.public UdpEchoClient(String ip, int port) throws SocketException {serverIp = ip;serverPort = port;// 这个 new 操作, 就不再指定端口了. 让系统自动分配一个空闲端口.socket = new DatagramSocket();}// 让这个客户端反复的从控制台读取用户输入的内容. 把这个内容构造成 UDP 请求, 发给服务器. 再读取服务器返回的 UDP 响应// 最终再显示在客户端的屏幕上.public void start() throws IOException {Scanner scanner = new Scanner(System.in);System.out.println("客户端启动!");while (true) {// 1. 从控制台读取用户输入的内容System.out.print("-> "); // 命令提示符, 提示用户要输入字符串.String request = scanner.next();// 2. 构造请求对象, 并发给服务器.DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,InetAddress.getByName(serverIp), serverPort);socket.send(requestPacket);// 3. 读取服务器的响应, 并解析出响应内容.DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);String response = new String(responsePacket.getData(), 0, responsePacket.getLength());// 4. 显示到屏幕上.System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);client.start();}
}
可以启动多个客户端,服务器也是可以应对的。IDEA 上启动多个程序,需要稍微设置一下:
其他人的电脑不能访问我这个程序。原因是:网络环境的现状,NAT 机制是主流。NAT 机制下,就把 IP 地址分成了外网 IP 和内网 IP.(内网IP 不能直接访问)。
示例二:翻译服务器
翻译服务器:请求是一些英文单词,响应则是对应的中文翻译。代码很多和回显服务器相同,所以继承UdpEchoServer 来实现代码的复用。
public class UdpDictServer extends UdpEchoServer {private Map<String, String> dict = new HashMap<>();public UdpDictServer(int port) throws SocketException {super(port);dict.put("cat", "小猫");dict.put("dog", "小狗");dict.put("hello", "你好");// 可以在这里继续添加千千万万个单词. 使每个单词都有一个对应的翻译.}// 是要复用之前的代码, 但是又要做出调整.@Overridepublic String process(String request) {// 把请求对应单词的翻译, 给返回回去.return dict.getOrDefault(request, "该词没有查询到!");}public static void main(String[] args) throws IOException {UdpDictServer server = new UdpDictServer(9090);// start 不需要重新再写一遍了. 直接就复用了之前的 start !server.start();}
}
TCP流套接字编程
TCP 分量要比 UDP 更重用的,用的更多的协议。
TCP 提供的 api 也是主要有两个类
- ServerSoeket:给服务器使用的 socket
- Socket: 既会给服务器使用,也会给客户端使用
字节流,一个字节一个字节进行传输的。一个 tcp 数据报,就是一个 字节数组 byte[]。
ServerSocket API
ServerSocket 构造方法:
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
ServerSocket 方法:
方法签 名 | 方法说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
Socket API
Socket 构造方法:
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接 |
Socket 方法:
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
ServerSocket的accept()方法负责产生连接,产生连接的类型是Socket。Socket负责后续的连接后的通信。 ServerSocket的accept()是在服务器中使用,当服务器执行到 accept 的时候, 此时客户端可能还没来呢。accept 就会阻塞到有客户端连接成功为止。
accept()把内核中的连接获取到应用程序中了,这个过程类似于,"生产者消费者模型”。
TCP版本的回显服务器
客户端代码,要做的事情:
- 读取请求并解析.
- 根据请求计算响应
- 把响应写回给客户端.
public class TcpEchoServer {private ServerSocket serverSocket = null;// 这个操作就会绑定端口号public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}// 启动服务器public void start() throws IOException {System.out.println("服务器启动");while (true){Socket clientSocket = serverSocket.accept();processConnection(clientSocket);}}// 通过这个方法来处理一个连接的逻辑.private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d]客户端上线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());// 接下来就可以读取请求, 根据请求计算响应, 返回响应三步走了.// Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写工作try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){// 一次连接中, 可能会涉及到多次请求/响应while (true){//1. 读取请求并解析. 为了读取方便, 直接使用 Scanner.Scanner sc = new Scanner(inputStream);if(!sc.hasNext()){//读取完毕,客户端下线System.out.printf("[%s:%d]客户端下线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());break;}// 这个代码暗含一个约定, 客户端发过来的请求, 得是文本数据, 同时, 还得带有空白符作为分割. (比如换行这种)String request = sc.next();// 2. 根据请求计算响应String response = process(request);// 3. 把响应写回给客户端. 把 OutputStream 使用 PrinterWriter 包裹一下, 方便进行发数据.PrintWriter writer = new PrintWriter(outputStream);// 使用 PrintWriter 的 println 方法, 把响应返回给客户端.// 此处用 println, 而不是 print 就是为了在结尾加上 \n . 方便客户端读取响应, 使用 scanner.next 读取.writer.println(response);// 这里还需要加一个 "刷新缓冲区" 操作.writer.flush();// 日志, 打印当前的请求详情.System.out.printf("[%s:%d] req: %s,resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);}} catch (IOException e) {e.printStackTrace();}finally {clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}
客户端代码,要做的事情:
1.从控制台读取用户的输入
2.把输入的内容构造成请求并发送给服务器
3.从服务器读取响应
4.把响应显示到控制台上
public class TcpEchoClient {private Socket socket = null;// 要和服务器通信, 就需要先知道, 服务器所在的位置.public TcpEchoClient(String serverIp, int serverPort) throws IOException {// 这个 new 操作完成之后, 就完成了 tcp 连接的建立.socket = new Socket(serverIp, serverPort);}public void start() {System.out.println("客户端启动");Scanner scannerConsole = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {while (true) {// 1. 从控制台输入字符串.System.out.print("-> ");String request = scannerConsole.next();// 2. 把请求发送给服务器PrintWriter printWriter = new PrintWriter(outputStream);// 使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了printWriter.println(request);// 不要忘记 flush, 确保数据是真的发送出去了!!printWriter.flush();// 3. 从服务器读取响应.Scanner scannerNetwork = new Scanner(inputStream);String response = scannerNetwork.next();// 4. 把响应打印出来System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);client.start();}
}
多线程版本的会先服务器
当前代码还存在一个很大的问题。多个客户端访问会出现bug。当第一个客户端连接好了之后,第二个客户端,不能正确被处理服务器。看不到客户端上线,同时客户端发来的请求也无法被处理。当第一个客户端退出之后,之前第二个客户端发的请求,就能正确响应了。
上面的单线程代码中,serverSocket.accept(),建立一个连接,但是在进入processConnection()后,连接不断开,下一个连接就没办法连接上。主线程一个线程,又要建立连接,又要进行通信,只有通信结束连接断开之后,才会有下一个连接。要想解决这个问题我们就需要用多线程来解决,主线程负责建立连接,新建立的线程负责连接后的通信,各司其职就能解决这个问题。
public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}// 启动服务器public void start() throws IOException {System.out.println("服务器启动");while (true){Socket clientSocket = serverSocket.accept();Thread t = new Thread(()->{processConnection(clientSocket);});}}
经过上述改进,只要服务器系统资源足够,有几个客户端都是可以的了。
TCP 程序的时候,涉及到两种写法
-
一个连接中只传输一次请求和响应.短连接
-
一个连接中,可以传输多次请求和响应.长连接
上面代码属于长连接。其实如果我们刚才的代码,不写成这个样子。比如要求每个客户端只能发一次请求,发完就断开。上述情况能得到一定的缓解,但是还是有类似的问题的。处理多个消息,自然就会延长 processConnection 的执行时间,后续的连接也得等到上个连接断开后才能继续连接。
现在是有一个连接,就有一个新的线程。如果很多客户端,频繁的来连接/断开,服务器就涉及到频繁创建/释放线程了。我们使用线程池会比较好的解决这个问题。
// 此处不应该创建固定线程数目的线程池.private ExecutorService service = Executors.newCachedThreadPool();// 启动服务器public void start() throws IOException {System.out.println("服务器启动");while (true){Socket clientSocket = serverSocket.accept();// 使用线程池, 来解决上述问题service.submit(new Runnable() {@Overridepublic void run() {processConnection(clientSocket);}});}}
虽然使用线程池,避免了频繁创建销毁线程。但是毕竟是每个客户端对应一个线程。如果服务器对应的客户端很多,服务器就需要创建出大量的线程.对于服务器的开销是很大的。当客户端进一步增加,线程数目进一步增加系统的负担就越来越重,响应速度也会大打折扣。
< C10K问题:同一时刻,有 10k 个客户端(1w 个客户端),通过前面的一些技术手段,硬件设备,是可以处理好.(线程池之类的)
随着互联网的发展,客户端越来越多,请求也越来越多,进而引发了C10M问题。
C10M: 同一时刻,有 1kw 的客户端并发请求。引入了很多技术手段.其中一个非常 有效/必要 的手段,IO 多路复用/IO 多路转接.
IO 多路复用:一个线程完成多个IO操作。 不是说使用了这一个方法就能解决C10M问题,但是解决 高并发(C10M) 重要手之一。
解决高并发,就是四个字:
- 开源: 引入更多的硬件资源 (多核CPU)
- 节流: 提高单位硬件资源能够处理的请求数。同样的请求数,消耗的硬件资源更少。
IO 多路复用,就是节流的方式。同样的请求,消耗的硬件资源更少了。本质上就是减少线程的数量。操作系统提供的另一组网络编程的 API 可以和 tcp udp api 配合使用的。可以去搜索 Java NIO 一些关键词来学习IO多路复用。
相关文章:

[javaWeb]Socket网络编程
网络编程:写一个应用程序,让这个程序可以使用网络通信。这里就需要调用传输层提供的 api。 Socket套接字 传输层提供协议,主要是两个: UDP和TCP 提供了两套不同的 api,这api也叫做socket api。 UDP和 TCP 特点对比: UDP: 无连…...
<MySon car=“宝马“ :money=“money“></MySon>有没有冒号
为什么car"宝马"没有: 但是 :money"money"就有: <script setup> import {ref} from vue import MySon from /components/MySon.vueconst money ref(100) </script><template><h3>father</h3><My…...
netty(三):NIO——多线程优化
NIO多线程优化 使用Boss线程来处理accepct事件使用Worker线程来处理读写事件,可以创建多个worker线程 package com.review;import lombok.extern.slf4j.Slf4j;import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.*; impor…...

Linux操作系统--linux概述
1.Linux概述 Linux,全称GNU/Linux,是一种免费使用和自由传播的类UNIX操作系统(OS)。简单的说就是一种操作系统。在日常中常见的操作系统有一下三种: 2.linux起源和背景 (1).linux的诞生 linux操作系统是由李纳斯托瓦兹…...

数组中出现次数超过一半的数字
⭐️ 题目描述 🌟 OJ链接:数组中出现次数超过一半的数字 思路: 采用投票计数的方式,我们可以把每个数字都看成一次投票并且计数,那么最后剩下来的就是数组中数字出现次数最多的那一个。比如 { 1,2,3,2,2,2,5,4,2 } &a…...

网络优化工程师,你真的了解吗?
一、5G网络优化工程师到底是什么? 5G,就是我们通常所说的第五代移动通信标准,属于目前最热门的新技术趋势。随着2019年5G技术进入正式的商用阶段,拥有广阔的发展前景,备受瞩目。“5G工程师”这个词是一个概念词&#x…...
git 的常用命令
git是一个版本管理器,是程序员必备工具之一,其主分为三个区: 工作区: 暂存区: 仓库: 通过保持软件版本,分支,合并,等多种版本操作,使软件能在自己想要的版本…...
linux如何拷贝文件,删除多余的一级目录,用*号代替所有文件
加上*,代表目录下的所有文件 mv /home/user/dir1/dir1/* /home/user/dir1/可以使用mv命令的通配符来去掉一层目录。 例如,假设有一个名为/home/user/dir1/dir2/file.txt的文件,要将它移动到/home/user/dir2/目录下并去掉dir1目录࿰…...

springboot使用properties
一、方式1: 1.1.配置类: package cn.zyq.stater.config;import cn.zyq.stater.bean.User4; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework…...
Android中获取手机SIM卡的各种信息
通过以下工具类方法可以获取到手机SIM的各种信息数据!!! package com.utils; import android.telephony.TelephonyManager; import com.baidu.platform.comapi.map.E; import org.json.JSONArray; import org.json.JSONObject; import java.…...
matlab 根据索引提取点云
目录 一、语法二、说明三、名称-值对应参数1、输入参数2、输出参数四、代码示例五、结果展示六、参考链接一、语法 ptCloudOut = select(ptCloud,indices) ptCloudOut = select(ptCloud,row,column...
蓝芯、四川邦辰面试(部分)
蓝芯 HTTP请求经过MQ异步处理后,怎样返回结果呢?grpc比起spring cloud的优缺点? 四川邦辰 SkyWalking的埋点具体是怎么操作的?newBing: SkyWalking支持两种埋点方式:自动埋点和手动埋点。自动埋点是指通过SkyWalking…...
openCV实战-系列教程13:文档扫描OCR识别下(图像轮廓/模版匹配)项目实战、源码解读
🧡💛💚💙💜OpenCV实战系列总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 上篇内容: openCV实战-系列教程11:文档扫描OCR识别上&am…...

SpringBootWeb案例 Part 4
3. 修改员工 需求:修改员工信息 在进行修改员工信息的时候,我们首先先要根据员工的ID查询员工的信息用于页面回显展示,然后用户修改员工数据之后,点击保存按钮,就可以将修改的数据提交到服务端,保存到数据…...

什么是ChatGPT水印,ChatGPT生成的内容如何不被检测出来,原理什么?
太长不看版 1. 什么是ChatGPT水印? ChatGPT水印是AI以伪随机方式生成的独特tokens序列。该序列用来作为水印,以区分AI生成内容和人类原创内容。 2. 如何规避ChatGPT水印? 一种规避方法是使用其他AI模型改写ChatGPT生成的文本。这会破坏水…...
Android 6.0 Settings中添加虚拟键开关
添加系统默认键值 b/frameworks/base/packages/SettingsProvider/res/values/defaults.xml-212,4 212,7 <!-- Default for Settings.Secure.NFC_PAYMENT_COMPONENT --><string name"def_nfc_payment_component"></string><!--mh.modify 2019060…...

Yolov8小目标检测(12):动态稀疏注意力BiFormer | CVPR 2023
💡💡💡本文改进:动态稀疏注意力,cvpr2023。 BiFormer | 亲测在红外弱小目标检测涨点,map@0.5 从0.755提升至0.758 💡💡💡Yolo小目标检测,独家首发创新(原创),适用于Yolov5、Yolov7、Yolov8等各个Yolo系列,专栏文章提供每一步步骤和源码,带你轻松实现小…...

C# VS调试技巧
一.按照条件调试步骤 ①在需要代码执行的行打断点 ②触发此断点,让代码执行到此处 ③鼠标滑至在断点处,点击设置 ④设置断点条件,如下图所示 二、多线程调试技巧 ①在需要代码执行的行打断点 ②触发此断点,让代码执行到此处…...

VS的调试技巧
Visual Studiohttps://visualstudio.microsoft.com/zh-hans/vs/ 目录 1、什么是调试? 2、debug和release 3、调试 3.1、环境 3.2、 快捷键 3.2.1、F10和F11 3.2.2、ctrlF5 3.2.3、F5与F9 3.2.3.1、条件断点 3.3、监视和内存观察 3.3.1、监视 3.3.2、内存 …...
lucene国内镜像 极速下载
文章目录 国内镜像汇总-极速下载【JavaPub版】 lucene国内镜像 https://mirrors.cloud.tencent.com/apache/lucene/ 国内镜像汇总-极速下载【JavaPub版】...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...