0%

文章字数:340,阅读全文大约需要1分钟

java,net提供了两个TCP通信的Socet类,分别代表客户端和服务端

构造方法

1
2
3
4
5
6
7
8
9
10
Socket(InetAddress address, int port);
Socket(InetAddress address, int port, boolean stream);
Socket(String host, int prot);
Socket(String host, int prot, boolean stream);
Socket(SocketImpl impl)
Socket(String host, int port, InetAddress localAddr, int localPort)
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
ServerSocket(int port);
ServerSocket(int port, int backlog);
ServerSocket(int port, int backlog, InetAddress bindAddr)

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class Client {
public static final int port = 8080;
public static final String host = "localhost";
public static void main(String[] args) {
System.out.println("Client Start...");
while (true) {
Socket socket = null;
try {
//创建一个流套接字并将其连接到指定主机上的指定端口号
socket = new Socket(host,port);

//读取服务器端数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//向服务器端发送数据
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入: \t");
String str = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(str);

String ret = input.readLine();
System.out.println("服务器端返回过来的是: " + ret);
// 如接收到 "OK" 则断开连接
if ("OK".equals(ret)) {
System.out.println("客户端将关闭连接");
Thread.sleep(500);
break;
}

out.close();
input.close();
} catch (Exception e) {
System.out.println("客户端异常:" + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
socket = null;
System.out.println("客户端 finally 异常:" + e.getMessage());
}
}
}
}
}
}

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
public static final int port = 8080;//监听的端口号

public static void main(String[] args) {
System.out.println("Server...\n");
Server server = new Server();
server.init();
}

public void init() {
try {
//创建一个ServerSocket,这里可以指定连接请求的队列长度
//new ServerSocket(port,3);意味着当队列中有3个连接请求是,如果Client再请求连接,就会被Server拒绝
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
//从请求队列中取出一个连接
Socket client = serverSocket.accept();
// 处理这次连接
new HandlerThread(client);
}
} catch (Exception e) {
System.out.println("服务器异常: " + e.getMessage());
}
}

private class HandlerThread implements Runnable {
private Socket socket;
public HandlerThread(Socket client) {
socket = client;
new Thread(this).start();
}

public void run() {
try {
// 读取客户端数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String clientInputStr = input.readLine();//这里要注意和客户端输出流的写方法对应,否则会抛 EOFException
// 处理客户端数据
System.out.println("客户端发过来的内容:" + clientInputStr);

// 向客户端回复信息
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入:\t");
// 发送键盘输入的一行
String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(s);

out.close();
input.close();
} catch (Exception e) {
System.out.println("服务器 run 异常: " + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
socket = null;
System.out.println("服务端 finally 异常:" + e.getMessage());
}
}
}
}
}
}

文章字数:611,阅读全文大约需要2分钟

正则规则

  1. 任意字符可以表示匹配自身
  2. []代表匹配其中之一,如[abc]。意思是a,b,c任意一个都能满足。
  3. [1-9]/[a-z]是简写,代表1-9个数字,a-z的字母。
  4. ^在括号外代表开头,如^a,代表a开头的。括号内代表非,如[^abc]代表除了abc的任意字符
  5. .表示任意字符
  6. \d数字
  7. \D非数字
  8. \s空字符,等于[\t\n\r\x\f]
  9. \S非空字符,等于[^\s]
  10. \w字母,数字,下划线,等于[a-zA-Z0-9_]
  11. \W非字母,数字,下划线
  12. ?表示前一个元素出现0次或1次
  13. +表示前一个元素出现1次以上
  14. *表示前一个元素出现任意次
  15. {n}表示前一个元素出现n次
  16. {n,m}表示前一个元素出现n-m次
  17. {n,}表示前一个元素出现n或n以上
  18. 正则的两个元素的排列就是需要匹配的顺序,比如\d\s就是数字后面必须跟着非数字
  19. a|b匹配a或者b
  20. (子表达式)将内部当成一个原子整体。比如([1-9][a-z])+,代表数字和小写字母的组合出现一次以上。

检测字符串是否匹配正则

  1. 基本用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 源字符串
    String str = "123";
    // 正则表达式
    String regex = "^[1-9]+";
    // 用正则创建Pattern对象,此对象就是正则表达式的对象,如果需要对此匹配,可以将此对象保存,避免重复创建。
    Pattern p = Pattern.compile(regex);
    // 检测字符串是否符合正则规则
    Matcher m = p.matcher(str);
    // 返回结果
    boolean res = m.matches();
  2. 使用Pattern类封装好的方法

    1
    2
    3
    String str = "123";
    String regex = "^[1-9]+";
    boolean res = Pattern.matches(regex, str);
  3. 使用String类封装好的方法

    1
    2
    3
    String str = "123";
    String regex = "^[1-9]+";
    boolean res = str.matches(regex);

正则替换

1
2
3
4
5
String str = "123abc";
String regex = "^[1-9]+";
System.out.println(str.replaceAll(regex, "&"));
System.out.println(str);
// 结果输出 &abc 和 123abc,代表替换结果不会对源字符串产生影响

正则切割

1
2
3
4
5
String str = "123a33b55h";
String regex = "[a-z]+";
String[] strs = str.split(regex);
// 输出
Arrays.stream(strs).forEach(System.out::println);

正则查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexMatches
{
public static void main( String[] args ){

// 按指定模式在字符串查找
String line = "This order was placed for QT3000! OK?";
String pattern = "(\\D*)(\\d+)(.*)";

// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);

// 现在创建 matcher 对象
Matcher m = r.matcher(line);
if (m.find( )) {
System.out.println("Found value: " + m.group(0) );
System.out.println("Found value: " + m.group(1) );
System.out.println("Found value: " + m.group(2) );
System.out.println("Found value: " + m.group(3) );
} else {
System.out.println("NO MATCH");
}
}
}

文章字数:1159,阅读全文大约需要4分钟

RMIJNDI是分布式处理的基础,RMI远程方法调用,负责分布式方法调用;JNDIjava命名和目录接口,用于更方便查找本地和远程对象

RMI概述

  • 是什么
  1. RMI(Remote Method Invocation) 远程方法调用,是一种计算机直接对象互相调用对方函数,启动对方进程的机制。
  2. 通过RMI,调用其它计算机上的方法用到的语法和规则和本地对象间调用一样
  • 怎么用
  1. 分布式计算,利用多个系统的组合算力,或者多端公用一个中央资源时用到(云计算时代,设备的算力可以向电一样统计和出售)
  2. 分布式对象编程,通过RMI远程调用可以忽略通讯方面的细节
  • 程序分类
  1. 服务器程序:创建多个远程对象,并使对象可以被引用。等待客户端调用
  2. 客户端程序:从服务端程序中的到一个或多个远程对象的引用
  3. 对等计算程序:双方互为对方的服务器和客户端

创建RMI程序

  • 大致步骤
  1. 定义远程接口
    利用远程接口调用方法,隐藏基类实施细节(接口实现java.rmi.Remote,方法抛出java.rmi.RemoteException
1
2
3
4
import  java.rmi.*;
public interface RmiSample extends Remote{
public int sum(int a,int b) throws RemoteException;
}
  1. 实现远程接口
  • 必须继承java.rmi.UnicastRemoteObject,并实现远程接口
  • 可以添加其它方法,但是客户端只能调用接口有的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import  java.rmi.*;
import java.rmi.server.*;
import com.itjob.RmiSample ;
/**
远程接口实现类,继承了UnicastRemoteObject并实现了RmiSample远程接口
*/
public class RmiSampleImpl extends UnicastRemoteObject implements RmiSample{
//覆盖默认构造函数并抛出RemoteException
public RmiSampleImpl() throws RemoteException{
super();
}
//所有远程实现方法必须抛出RemoteException
public int sum(int a,int b) throws RemoteException{
return a+b;
}
}
  1. 编写服务器类
  • 直接Registry实现(本地构建RMI服务器)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class RegistryService {
    public static void main(String[] args) {
    try {
    // 本地主机上的远程对象注册表Registry的实例,默认端口1099
    Registry registry = LocateRegistry.createRegistry(1099);
    // 创建一个远程对象
    HelloRegistryFacade hello = new HelloRegistryFacadeImpl();
    // 把远程对象注册到RMI注册服务器上,并命名为HelloRegistry
    registry.rebind("HelloRegistry", hello);
    System.out.println("======= 启动RMI服务成功! =======");
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    }
    }
  • 使用Naming注册到指定服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class NamingService {
public static void main(String[] args) {
try {
// 本地主机上的远程对象注册表Registry的实例
LocateRegistry.createRegistry(1100);
// 创建一个远程对象
HelloNamingFacade hello = new HelloNamingFacadeImpl();
// 把远程对象注册到RMI注册服务器上,并命名为Hello
//绑定的URL标准格式为:rmi://host:port/name
Naming.bind("rmi://localhost:1100/HelloNaming", hello);
System.out.println("======= 启动RMI服务成功! =======");
} catch (RemoteException | MalformedURLException | AlreadyBoundException e) {
e.printStackTrace();
}
}
}
  1. 编写使用远程服务的客户机类
  • 使用Registry获取指定端口的客户机
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class RegistryClient {
    public static void main(String[] args) {
    try {
    Registry registry = LocateRegistry.getRegistry(1099);
    HelloRegistryFacade hello = (HelloRegistryFacade) registry.lookup("HelloRegistry");
    String response = hello.helloWorld("ZhenJin");
    System.out.println("=======> " + response + " <=======");
    } catch (NotBoundException | RemoteException e) {
    e.printStackTrace();
    }
    }
    }

跟和干

  • stub客户端辅助对象
  • skeleton服务端辅助对象
    具体作用
  • 客户端对象调用服务端辅助对象上的方法
  • 服务端福追寻将客户端辅助对象发来的信息解包,找出需要被调用的方法所在对象
  • 调用对象方法,返回给服务端辅助对象
  • 服务端辅助对象将结果打包,发给客户端辅助对象
  • 客户端辅助对象将返回值解包,返回给客户对象

RMI和RPC

区别点 RMI RPC
适用语言 java 网络服务协议,和语言无关。通用
调用形式 通过客户端的Stub对象作为远程接口进行远程方法调用,每个远程方法有方法签名。如果方法签名没有匹配到远程接口(stub)就不能被调用 RPC通过网络服务协议先远程主机发送请求,发送classname.methodname(args)形式,远程主机就去搜索匹配的类和方法,找到后执行方法,把结果编码并用网络返回
返回类型 返回对象或者基本数据类型 结果是外部数据表示语言(External Data Representation, XDR)表示,抽象了类和数据结构的差异。

文章字数:478,阅读全文大约需要1分钟

开发流程环境

环境流程

UAT环境和灰度环境

Instrumentation接口

独立于应用程序之外的代理(agent)监控目标类

  1. 构造agent类
  2. 设置MANNIFEST.MF
  3. 打包成JAR
  4. 通过Attach API绑定到目标VM(SUN公司才有的API)
  5. 运行

btrace

开源的ASM + Java Attach API + Instrument开发的
image.png
image.png

  1. jps查看当前机器上面有哪些java程序
  2. btrace xxxx Test.java

查看运行时间,是否执行过

image.png

image.png

Arthas

阿里巴巴开源的Java诊断工具


文章字数:60,阅读全文大约需要1分钟

压缩过程无错误,压缩后的文件打开不了这个压缩文件格式未知或者数据已经被损坏

原因

ZipOutputStream这个流未关闭,代码上加上zipOutputStream.close()就可以了


文章字数:268,阅读全文大约需要1分钟

封装了两个小功能,传入文件输出流,压缩一个/多个文件到压缩文件。传入压缩文件地址和解压缩的文件夹,解压(文件夹会被覆盖)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* @author colin.cheng
* @date
*/

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class zipTest {

/**
* 压缩文件
*
* @param inputStream
* 读取的文件流
* @param outputStream
* 输出的压缩文件流
* @param fileName
* 压缩进去的文件名
* @throws Exception
*/
private static void zipFile(FileInputStream inputStream, ZipOutputStream outputStream, String fileName)
throws Exception {
// 新建一个压缩条目(相对路径)
outputStream.putNextEntry(new ZipEntry(fileName));
// 写入这个条目
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream, 512)) {
int index;
byte[] buffer = new byte[512];
while ((index = bufferedInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, index);
}
}
// 关闭条目,并等待下个条目
outputStream.closeEntry();
}

/**
* 创建文件父级目录
*
* @param file
*/
public static void createParentPath(File file) {
File parentFile = file.getParentFile();
if (null != parentFile && !parentFile.exists()) {
parentFile.mkdirs(); // 创建文件夹
createParentPath(parentFile); // 递归创建父级目录
}
}

/**
* 解压缩
*
* @param fromZip
* 目标压缩文件
* @param toPath
* 解压地址(存在则覆盖此目录)
* @throws Exception
*/
public static void decompressionFile(String fromZip, String toPath) throws Exception {
File baseFile = new File(toPath);
if (baseFile.exists()) {
baseFile.delete();
}
File zipFile = new File(fromZip);
ZipInputStream inputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile), 255));
ZipEntry entry;
int length;
byte[] buffer = new byte[255];
while ((entry = inputStream.getNextEntry()) != null) {
String filePath = baseFile.toPath().toString() + "\\" + entry.getName();
createParentPath(new File(filePath));
Files.createFile(Paths.get(filePath));
FileOutputStream outputStream = new FileOutputStream(Paths.get(filePath).toFile());
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
}

public static void main(String[] args) {
try {
// 压缩测试
// 压缩包名字
String zipPathStr =
"F:\\cache\\test.zip";
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(new File(zipPathStr)))) {
// 模拟压缩三份文件进去
for (int i = 0; i < 3; i++) {
String filePathStr =
"F:\\cache\\test.xls";
File file = new File(filePathStr);
try (FileInputStream fileInputStream = new FileInputStream(file)) {
// all/1test.xls all为指定的目录
zipFile(fileInputStream, zipOutputStream, "all/" + i + file.getName());
}
}
}

// 解压测试
decompressionFile(
"F:\\cache\\test.zip",
"F:\\cache\\test");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

文章字数:1511,阅读全文大约需要6分钟

ElasticSearch是一款使用java开发基于Lucene的开箱即用的全文搜索引擎。使用REST API操作接口。整理自阮一峰的教程

安装

  • Java8环境

  • 安装ElasticSearch-5.5.1

    1
    2
    3
    4
    5
    6
    # 下载压缩包到当前目录
    $ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.1.zip
    # 解压缩
    $ unzip elasticsearch-5.5.1.zip
    # 进入目录
    $ cd elasticsearch-5.5.1/
  • 中文分词插件ik

    1
    2
    3
    # 使用elastic插件工具下载安装插件
    ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip
    # 重启Elastic即可安装

开始使用

  • 启动

    1
    ./bin/elasticsearch
  • 解决max virtual memory areas vm.maxmapcount [65530] is too low

    1
    $ sudo sysctl -w vm.max_map_count=262144
  • 查看信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 默认9200端口运行
    $ curl localhost:9200

    {
    "name" : "atntrTf",
    "cluster_name" : "elasticsearch",
    "cluster_uuid" : "tf9250XhQ6ee4h7YI11anA",
    "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
    },
    "tagline" : "You Know, for Search"
    }
  • 允许非本机访问
    修改config/elasticsearch.yml去除network.host注释,修改值

    1
    2
    3
    4
    # 任何人都能访问(不推荐)
    network.host: 0.0.0.0
    # 指定ip
    network.host: 192.168.1.1

基本概念

  • cluster集群,多个Elastic实例组成一个集群cluster。实例可以运行在多个/同一个服务器上。
  • Node节点,一个Elastic实例就是一个节点。一组节点构成一个集群。
  • Index索引,查找数据的顶层单位。Elastic会索引所有字段,经过处理后写入一个反向索引Inverted IndexIndex可以理解成单个数据库。
    Index名字必须是小写
  • Document文档,Index里的单条记录成为Document文档。许多文档构成了Index。文档是json类型表示,可以理解为一个json对象。
    1
    2
    3
    4
    5
    {
    "title":"Elastic分布式全文搜索引擎",
    "keyword":"java,search",
    "body":"这是内容"
    }
  • 同一个文档(Doucment)最好结构(scheme)相同*
  • Type分组、类型,Document可以进行分组。根据虚拟逻辑进行分组,比如技术博问散文博文,用来过滤Documeent

    根据规划,Elastic6 版本之循序Index中包好一个Type, 7版本将移除type。

操作

  • 新建Index

    1
    2
    # 发送PUT请求创建Index
    curl -X PUT 'localhost:9200/weather'

    服务器返回

    1
    2
    3
    4
    5
    # acknowledged表示操作成功
    {
    "acknowledged":true,
    "shards_acknowledged":true
    }
  • 删除Index

    1
    2
    # 发送DELETE请求删除
    curl -X DELETE 'localhost:9200/weather'
  • 设置Index详细内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 创建叫accounts的Index
    curl -X PUT 'localhost:9200/accounts' -d '
    {
    "mappings":{
    // 有一个type叫person
    "TypePerson":{
    "properties":{
    //字段1 User
    "user":{
    "type":"text",//文本类型
    "analyzer":"ik_max_word",//字段分词器使用ik提供的文本最大数量分词。
    "search_analyzer":"ik_max_word"//搜索分词器
    },
    //字段2 Title
    "title":{
    ...同上
    }
    }
    }
    }
    }'
  • 新增记录(指定id)

    1
    2
    3
    4
    5
    6
    7
    # 向accounts的person分组插入(PUT)
    # 1为此记录的id,任意字符。
    $ curl -X PUT 'localhost:9200/accounts/person/1' -d '
    {
    "user": "张三",
    "title": "工程师"
    }'

    返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "_index":"accounts",
    "_type":"person",
    "_id":"1",
    "_version":1,
    "result":"created",
    "_shards":{"total":2,"successful":1,"failed":0},
    "created":true
    }
  • 新增记录(不指定id)

    1
    2
    3
    4
    5
    6
    7
    # 使用POST,服务器随机生成字符串形式的id
    $ curl -X POST 'localhost:9200/accounts/person' -d '
    {
    "user": "李四",
    "title": "工程师",
    "desc": "系统管理"
    }'

    如果没有创建Index直接插入会自动生成指定的Index。

  • 查看记录

    1
    2
    # 使用GET `Index/Type/Id`查看,pretty表示以易读格式返回。
    $ curl 'localhost:9200/accounts/person/1?pretty=true'

    返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "_index" : "accounts",
    "_type" : "person",
    "_id" : "1",
    "_version" : 1,
    "found" : true,//没找到 false
    "_source" : {
    "user" : "张三",
    "title" : "工程师",
    "desc" : "数据库管理"
    }
    }
  • 删除记录

    1
    2
    # delete id
    curl -X DELETE 'localhost:9200/accounts/person/1'
  • 更新记录

    1
    2
    3
    # 重新发送一次就可以了
    curl -X PUT 'localhost:9200/accounts/person/1' -d
    '...'

    返回

    1
    2
    3
    "_version" : 2,//版本+1
    "result" : "updated",//结果从创建变成修改
    "created" : false//created 变成 false
  • 查询所有数据

    1
    2
    # GET请求直接访问
    $ curl 'localhost:9200/accounts/person/_search'

    返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    {
    "took":2,
    "timed_out":false,
    "_shards":{"total":5,"successful":5,"failed":0},
    "hits":{
    "total":2,
    "max_score":1.0,
    "hits":[
    {
    "_index":"accounts",
    "_type":"person",
    "_id":"AV3qGfrC6jMbsbXb6k1p",
    "_score":1.0,
    "_source": {
    "user": "李四",
    "title": "工程师",
    "desc": "系统管理"
    }
    },
    {
    "_index":"accounts",
    "_type":"person",
    "_id":"1",
    "_score":1.0,
    "_source": {
    "user" : "张三",
    "title" : "工程师",
    "desc" : "数据库管理,软件开发"
    }
    }
    ]
    }
    }

    took操作耗时(毫秒)、timed_out是否超时、hits命中的记录
    hits字段含义

    1
    2
    3
    4
    total:返回记录数,本例是2条。
    max_score:最高的匹配程度,本例是1.0。
    hits:返回的记录组成的数组。
    hits下_score表示匹配的程序,按照这个字段排序的
  • 全文搜索

    1
    2
    3
    4
    5
    # 查询desc字段中包含软件这个词的
    $ curl 'localhost:9200/accounts/person/_search' -d '
    {
    "query" : { "match" : { "desc" : "软件" }}
    }'
  • 指定全文搜索结果条数

    1
    2
    3
    4
    5
    6
    # size 1 指定返回一条结果
    $ curl 'localhost:9200/accounts/person/_search' -d '
    {
    "query" : { "match" : { "desc" : "管理" }},
    "size": 1
    }'
  • 位移/跳过指定数量的结果

    1
    2
    3
    4
    5
    6
    $ curl 'localhost:9200/accounts/person/_search'  -d '
    {
    "query" : { "match" : { "desc" : "管理" }},
    "from": 1,//跳过1条
    "size": 1
    }'
  • 条件或、条件且

    1
    2
    3
    4
    5
    # 软件或是系统
    $ curl 'localhost:9200/accounts/person/_search' -d '
    {
    "query" : { "match" : { "desc" : "软件 系统" }}
    }'

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # desc 拥有软件关键词同时也要拥有系统关键词
    $ curl 'localhost:9200/accounts/person/_search' -d '
    {
    "query": {
    "bool": {
    "must": [
    { "match": { "desc": "软件" } },
    { "match": { "desc": "系统" } }
    ]
    }
    }
    }'

    后台运行

  • linux通用方法

    1
    2
    3
    4
    5
    6
    7
    #  nohup表示不依赖终端,可以在同一个终端继续其他事情
    # &不依赖于用户
    nohup bin/elasticsearch &
    # 查看日志
    tail -fn 200 nohup.out
    # 重定向输出信息 >/dev/null->将所有正确输出都保存到null文件中(抛弃) 2>&1 ->2即错误信息,重定向到1(正确信息中)
    nohup bin/elasticsearch >/dev/null 2>&1 &
  • Elastic参数

    1
    2
    # -d 代表后台运行
    bin/elasticsearch -d
  • 关闭后台程序

    1
    2
    3
    4
    # 查找进程
    ps -ef|grep elastic
    # 杀死进程
    kill 41496

模糊查询+分页

1
Page<User> findByUsernameContainingOrderByCreateTimeDesc(String username,Pageable pageable);

调用

1
2
Pageable page = PageRequest.of(0 , 2);
Page<User> list = userRepository.findByUsernameContainingOrderByCreateTimeDesc("丽",page);

优化

  1. Elastic数据存储在磁盘中,查询的时候会自动提取到Filesystem Cache中。增加Filesystem Cache(内存)的容量,以及预知会有大量访问的数据自己提前定时查询,使其写入内存。热数据冷数据分离,热数据不会被冷数据挤下去。
  2. join/nested/parent-child 这些操作尽量不要做
  3. 分页默认的性能很低,越往深层越慢。使用 Scroll API,滚动刷新(不能跳页)
  4. 配合其他数据库使用,es查询会查询所有数据,但是大部分可能没用。可以将需要检索的字段和id存入ES,再用id在mysql/HBase中取出其他的。

文章字数:356,阅读全文大约需要1分钟

一、依赖

主要是两个包,发起连接的httpclient以及定义http请求数据类型的包httpmime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependencies>
<!-- httpmime -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.13</version>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
</dependencies>

二、封装工具类

把常用的功能封装成工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package org.colin;

import org.apache.http.HttpEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.stream.Collectors;

/**
* httpClient常用功能封装
*/
public class HttpClientUtil {


/**
* 将http请求结果的内容读取成字符串
*
* @param response
* @return
* @throws IOException
*/
public static String parseRespToStr(CloseableHttpResponse response) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
response.getEntity().writeTo(outputStream);
return outputStream.toString();
}

/**
* 创建formData的httpPost请求
* content-type 对应 multipart/form-data
*
* @param url post地址
* @param headers 请求头内容
* @param params 表单内容 map,value支持 String/File/Collection<File>
* @return
* @throws IOException
*/
public static HttpPost createFormDataHttpPost(String url, Map<String, String> headers, Map<String, Object> params) throws IOException {
HttpPost httpPost = new HttpPost(url);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
if(params != null) {
params.forEach((k,v)->{
if(v instanceof File) {
builder.addBinaryBody(k ,(File) v);
} else if(v instanceof Collection) {
for (Object obj : (Collection)v) {
builder.addBinaryBody(k, (File)obj);
}
} else {
builder.addTextBody(k, v.toString());
}
});
}
httpPost.setEntity(builder.build());
if(headers != null) {
headers.forEach(httpPost::addHeader);
}
return httpPost;
}

/**
* 创建纯字符串的form表单,并进行url编码
* content-type 对应 application/x-www-form-urlencoded
*
* @param url
* @param headers
* @param params
* @return
* @throws IOException
*/
public static HttpPost createFormUrlencodedHttpPost(String url, Map<String, String> headers, Map<String, String> params) throws IOException {
HttpPost httpPost = new HttpPost(url);
List<BasicNameValuePair> nameValuePairs;
if(params != null) {
nameValuePairs = params.entrySet().stream().map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
} else {
nameValuePairs = new LinkedList<>();
}
HttpEntity entity = new UrlEncodedFormEntity(nameValuePairs);
httpPost.setEntity(entity);
if(headers != null) {
headers.forEach(httpPost::addHeader);
}
return httpPost;
}

/**
* 创建json格式的httpPost请求
* content-type 对应 application/json
*
* @param url
* @param headers
* @param jsonStr
* @return
* @throws UnsupportedEncodingException
*/
public static HttpPost createJsonHttpPost(String url, Map<String, String> headers, String jsonStr) throws UnsupportedEncodingException {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(jsonStr));
if(headers != null) {
headers.forEach(httpPost::addHeader);
}
return httpPost;
}

/**
* 创建httpGet请求
*
* @param url
* @param headers
* @return
* @throws IOException
*/
public static HttpGet createHttpGet(String url, Map<String, String> headers) throws IOException {
HttpGet httpGet = new HttpGet(url);
if(headers != null) {
headers.forEach(httpGet::addHeader);
}
return httpGet;
}

public static HttpClientBuilder getHttpClientBuilder() {
return HttpClientBuilder.create();
}

public static HttpClientBuilder getHttpsClientBuilder() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
return HttpClientBuilder.create().setSSLContext(ctx);
}
}

三、测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.colin;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public class Test {
public static void main( String[] args ) throws UnsupportedEncodingException {
HttpClientUtil app = new HttpClientUtil();
try {
String url = "http://127.0.0.1:8098/test";
// 复用一个httpClient
CloseableHttpClient client = HttpClientBuilder.create().build();
// 1. get请求测试
HttpGet get = app.createHttpGet(url + "?name=111&age=333", null);

// 2. post请求测试 application/x-www-form-urlencoded
Map<String, String> param = new HashMap<>();
param.put("name", "321");
param.put("age", "10");
HttpPost postStr = app.createFormUrlencodedHttpPost(url, param, param);

// 3. post请求测试,多文件上传 multipart/form-data
Map<String, Object> params = new HashMap<>();
params.put("name", "123");
params.put("age", "10");
LinkedList<Object> list = new LinkedList<>();
list.add(new File("E:\\321.png"));
list.add(new File("E:\\123.png"));
params.put("photo", list);
HttpPost postFormData1 = app.createFormDataHttpPost(url, null, params);

// 4. post请求测试,单文件上传 multipart/form-data
params = new HashMap<>();
params.put("name", "123");
params.put("age", "10");
params.put("photo", new File("E:\\321.png"));
HttpPost postFormData2 = app.createFormDataHttpPost(url, null, params);

// 5. post请求测试,json格式 application/json
HashMap<String, String> head = new HashMap<>();
HttpPost postJson = app.createJsonHttpPost("http://127.0.0.1:8098/jsonTest", head, "{'name':'123', 'age':'333'}");

// 发起请求
CloseableHttpResponse response;
response = client.execute(get);
System.out.println("-------get---------");
System.out.println(HttpClientUtil.parseRespToStr(response));

response = client.execute(postStr);
System.out.println("---------post str----------");
System.out.println(HttpClientUtil.parseRespToStr(response));

response = client.execute(postFormData1);
System.out.println("---------post formData1----------");
System.out.println(HttpClientUtil.parseRespToStr(response));

response = client.execute(postFormData2);
System.out.println("---------post formData2----------");
System.out.println(HttpClientUtil.parseRespToStr(response));

response = client.execute(postJson);
System.out.println("---------post json----------");
System.out.println(HttpClientUtil.parseRespToStr(response));
} catch (Exception e) {
e.printStackTrace();
}
}
}

四、服务器接受的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@GetMapping("/test")
@ResponseBody
String getTest(String name, String age){
System.out.println("name = " + name);
System.out.println("age = " + age);
return "getOk";
}

// 兼容接收多个文件,或者一个
@PostMapping("/test")
@ResponseBody
String postTest(String name, String age, @RequestParam(value = "photo", required = false) MultipartFile[] photo) {
System.out.println("name = " + name);
System.out.println("age = " + age);
if(photo != null) {
for (MultipartFile file : photo) {
System.out.println("photo.getSize() = " + file.getSize());
System.out.println("photo.getName() = " + file.getOriginalFilename());
}
}
return "postOk";
}

@PostMapping("/jsonTest")
@ResponseBody
String jsonTest(@RequestBody String json) {
System.out.println("json = " + json);
return "postOk";
}

文章字数:809,阅读全文大约需要3分钟

整理自原文

环境

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.9.Final</version>
</dependency>

如果使用的是SpringBoot,这个依赖是自带的。

校验Controller入参的对象

  1. 添加校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data // get set
@AllArgsConstructor // 创建有所有参数的构造方法
@NoArgsConstructor // 创建无参构造方法
public class Account {
private String id;
@NotNull
@Length(max = 20)
private String userName;
@NotNull
@Pattern(regexp = "[A-Z][a-z][0-9]")
private String passWord;
@DateTimeFormat(pattern = "yyy-MM-dd") // String转换成时间
private Date createTime;
private String alias;
@Max(10)
@Min(1)
private Integer level;
private Integer vip;
}
  1. 使用
1
2
3
4
5
6
// 需要加 @Valid 才会校验,错误直接抛出异常
@PostMapping("/saveAccount")
public Object saveAccount(@RequestBody @Valid Account account){
accountService.saveAccount(account);
return "保存成功";
}

Controller方法上直接校验

在类上需要添加注解@Validated

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping("/validation")
@RestController
@Validated
public class ValidationController {
/**如果只有少数对象,直接把参数写到Controller层,然后在Controller层进行验证就可以了。*/
@RequestMapping(value = "/demo3", method = RequestMethod.GET)
public void demo3(@Range(min = 1, max = 9, message = "年级只能从1-9")
@RequestParam(name = "grade", required = true)
int grade,
@Min(value = 1, message = "班级最小只能1")
@Max(value = 99, message = "班级最大只能99")
@RequestParam(name = "classroom", required = true)
int classroom) {
System.out.println(grade + "," + classroom);
}
}

使用工具类校验

  1. 工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Set;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;

    import lombok.Data;
    import org.hibernate.validator.HibernateValidator;
    public class ValidationUtil {
    /**
    * 开启快速结束模式 failFast (true)
    */
    private static Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
    /**
    * 校验对象
    *
    * @param t bean
    * @param groups 校验组
    * @return ValidResult
    */
    public static <T> ValidResult validateBean(T t,Class<?>...groups) {
    ValidResult result = new ValidationUtil().new ValidResult();
    Set<ConstraintViolation<T>> violationSet = validator.validate(t,groups);
    boolean hasError = violationSet != null && violationSet.size() > 0;
    result.setHasErrors(hasError);
    if (hasError) {
    for (ConstraintViolation<T> violation : violationSet) {
    result.addError(violation.getPropertyPath().toString(), violation.getMessage());
    }
    }
    return result;
    }
    /**
    * 校验bean的某一个属性
    *
    * @param obj bean
    * @param propertyName 属性名称
    * @return ValidResult
    */
    public static <T> ValidResult validateProperty(T obj, String propertyName) {
    ValidResult result = new ValidationUtil().new ValidResult();
    Set<ConstraintViolation<T>> violationSet = validator.validateProperty(obj, propertyName);
    boolean hasError = violationSet != null && violationSet.size() > 0;
    result.setHasErrors(hasError);
    if (hasError) {
    for (ConstraintViolation<T> violation : violationSet) {
    result.addError(propertyName, violation.getMessage());
    }
    }
    return result;
    }
    /**
    * 校验结果类
    */
    @Data
    public class ValidResult {

    /**
    * 是否有错误
    */
    private boolean hasErrors;

    /**
    * 错误信息
    */
    private List<ErrorMessage> errors;

    public ValidResult() {
    this.errors = new ArrayList<>();
    }
    public boolean hasErrors() {
    return hasErrors;
    }

    public void setHasErrors(boolean hasErrors) {
    this.hasErrors = hasErrors;
    }

    /**
    * 获取所有验证信息
    * @return 集合形式
    */
    public List<ErrorMessage> getAllErrors() {
    return errors;
    }
    /**
    * 获取所有验证信息
    * @return 字符串形式
    */
    public String getErrors(){
    StringBuilder sb = new StringBuilder();
    for (ErrorMessage error : errors) {
    sb.append(error.getPropertyPath()).append(":").append(error.getMessage()).append(" ");
    }
    return sb.toString();
    }

    public void addError(String propertyName, String message) {
    this.errors.add(new ErrorMessage(propertyName, message));
    }
    }

    @Data
    public class ErrorMessage {

    private String propertyPath;
    private String message;

    public ErrorMessage() {
    }

    public ErrorMessage(String propertyPath, String message) {
    this.propertyPath = propertyPath;
    this.message = message;
    }
    }
    }
  2. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void test5() throws IOException {
    Account account = new Account();
    account.setAlias("kalakala");
    account.setUserName("wokalakala");
    account.setPassWord("密码");
    ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(account);
    if(validResult.hasErrors()){
    String errors = validResult.getErrors();
    System.out.println(errors);
    }
    }

自定义规则

  1. 注解上必须有 @Constraint(validatedBy = {**.class}) 注解标注,validateBy 的值就是校验逻辑的实现类,实现类必须实现接口ConstraintValidator
  2. 自定义注解 必须包含 message ,groups,payload 属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import org.apache.commons.lang3.time.DateUtils;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.text.ParseException;
import java.util.Date;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Created by hu on 2018/3/12.
*/
@Target({FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {DateValidator.DateValidatorInner.class})
public @interface DateValidator {

/**
* 必须的属性
* 显示 校验信息
* 利用 {} 获取 属性值,参考了官方的message编写方式
*@see org.hibernate.validator 静态资源包里面 message 编写方式
*/
String message() default "日期格式不匹配{dateFormat}";

/**
* 必须的属性
* 用于分组校验
*/
Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

/**
* 非必须
*/
String dateFormat() default "yyyy-MM-dd HH:mm:ss";

/**
* 必须实现 ConstraintValidator接口
*/
class DateValidatorInner implements ConstraintValidator<DateValidator, String> {
private String dateFormat;

@Override
public void initialize(DateValidator constraintAnnotation) {
this.dateFormat = constraintAnnotation.dateFormat();

}

/**
* 校验逻辑的实现
* @param value 需要校验的 值
* @return 布尔值结果
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if("".equals(value)){
return true;
}
try {
Date date = DateUtils.parseDate(value, dateFormat);
return date != null;
} catch (ParseException e) {
return false;
}
}
}
}

使用

1
2
@DateValidator(dateFormat = "yyyy-MM-dd")
private String day;

分组校验

同一个对象在不同业务下验证规则可能不一样

  1. 声明规则时指定分组(不写默认是Default.class分组)
    1
    2
    @DateValidator(dateFormat = "yyyy-MM-dd",groups = {AccountService.class})
    private String birthday;
  2. 验证规则时加入分组
    1
    2
    3
    4
    5
    ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(account, AccountService.class);
    if(validResult.hasErrors()){
    String errors = validResult.getErrors();
    System.out.println(errors);
    }

其它常用规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@AssertFalse @AssertTrue  检验boolean类型的值

@DecimalMax @DecimalMin 限定被标注的属性的值的大小

@Digits(intege=,fraction=) 限定被标注的属性的整数位数和小数位数

@Future检验给定的日期是否比现在晚

@Past 校验给定的日期是否比现在早

@Max检查被标注的属性的值是否小于等于给定的值

@Min检查被标注的属性的值是否大于等于给定的值

@NotNull检验被标注的值不为空

@Null 检验被标注的值为空

@Pattern(regex=,flag=) 检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配

@Size(min=,max=) 检查被标注元素的长度

@Valid递归的对关联的对象进行校验

配合使用的全局异常处理

1
2
3
4
5
6
7
8
9
10
11
@RestControllerAdvice
public class ExceptionControllerAdvice {

@ExceptionHandler(MethodArgumentNotValidException.class)
public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回
return objectError.getDefaultMessage();
}
}

不抛异常,手动判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;

@PostMapping("/addUser")
public String addUser(@RequestBody @Valid User user, BindingResult bindingResult) {
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
for (ObjectError error : bindingResult.getAllErrors()) {
return error.getDefaultMessage();
}
return userService.addUser(user);
}
}

文章字数:659,阅读全文大约需要2分钟

一个基于socket的Netty客户端和服务端相互通讯

服务端

服务类,用于创建Netty服务绑定端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package io.greatcolin.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/** Netty服务器类,用于接收请求
* @author colin.cheng
* @version V1.0
* @date Created In 13:50 2019/8/14
*/
public class NettyServer {

/**
* 启动服务
* @param port 启动时绑定的端口
*/
public void bind(int port){
//Reactor线程组,一个用来处理连接,一个用来处理网络读写
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try{
//启动NIO服务端的辅助启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//绑定线程组
serverBootstrap.group(bossGroup,workGroup)
//指定通道类型(服务端是NioServerSocketChannel)
.channel(NioServerSocketChannel.class)
//设置通道的处理器
.option(ChannelOption.SO_BACKLOG,1024)
//子通道
.childHandler(new ChildChannelHandler());
//绑定并监听端口
ChannelFuture future = serverBootstrap.bind(port).sync();
System.out.println(Thread.currentThread().getName()+",启动成功,等待请求中");
//future.channel()获取程序的channel,等待结束(closeFuthre),阻塞(sync)
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//退出,释放资源
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}


private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
//ChannelPipeline是一个链式的处理请求的流程
arg0.pipeline().addLast(new NettyServerHandler());
}
}
}

处理请求的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package io.greatcolin.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
* @author colin.cheng
* @version V1.0
* @date Created In 14:31 2019/8/14
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

/**
* 接收客户端消息,自动触发
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

//转换msg成Netty的ByteBuf对象,类似ByteBuffer(缓冲区)
ByteBuf buf = (ByteBuf) msg;
//创建缓冲区内信息大小的byte类型数组
byte[] reg = new byte[buf.readableBytes()];
//读取缓冲区的信息并转换成字符串
buf.readBytes(reg);
String mess = new String(reg,"UTF-8");
System.out.println("mess = " + mess);
//回复消息
String respMess = "收到";
ByteBuf respByteBuf = Unpooled.copiedBuffer(respMess.getBytes());
ctx.write(respByteBuf);
}

/**
*当Channel上的一个读操作完成时被调用( channelRead是进行拆包和粘包之后的请求,channelReadComplete则是具体的每一次发送请求)
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//读完后刷新缓冲区,发送
ctx.flush();
}

/**
* 异常发生
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//发送异常关闭context,释放相关资源
ctx.close();
}
}

客户端

建立连接请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package io.greatcolin.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**客户端,发起连接请求
* @author colin.cheng
* @version V1.0
* @date Created In 15:19 2019/8/14
*/
public class NettyClient {

/**
* 连接目标服务器
* @param host
* @param port
*/
public void connect(String host,int port){
//NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChileHandler());
ChannelFuture channelFuture = bootstrap.connect(host,port).sync();
System.out.println(Thread.currentThread().getName()+",发起连接请求");
//等待客户端链路关闭
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//释放资源
group.shutdownGracefully();
}
}

private class ChileHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHanlder());
}
}
}

具体处理事件的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package io.greatcolin.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**处理请求
* @author colin.cheng
* @version V1.0
* @date Created In 16:32 2019/8/14
*/
public class NettyClientHanlder extends ChannelInboundHandlerAdapter {

/**
* 连接成功之后执行
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String sendMess = "我是客户端:"+Thread.currentThread().getName();
byte[] sendMesByte = sendMess.getBytes("UTF-8");
ByteBuf sendByteBuf = Unpooled.buffer(sendMesByte.length);
sendByteBuf.writeBytes(sendMesByte);
ctx.writeAndFlush(sendByteBuf);
}

/**
* 接收返回的消息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String mess = new String(req,"UTF-8");
System.out.println(Thread.currentThread().getName()+"接收到返回的消息:"+mess);
ctx.close();
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package io.greatcolin;

import io.greatcolin.client.NettyClient;
import io.greatcolin.server.NettyServer;

import java.util.concurrent.TimeUnit;

/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
//开启服务,服务会阻塞,所以使用一个线程开启
new Thread(new Runnable() {
@Override
public void run() {
NettyServer server = new NettyServer();
server.bind(7777);
}
}).start();

NettyClient client = new NettyClient();
for (int i=0;i<3;i++){
App.sleep();
client.connect("127.0.0.1",7777);
System.out.println("------");
}
}

//休眠3秒
public static void sleep(){
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
Thread-0,启动成功,等待请求中
main,发起连接请求
mess = 我是客户端:nioEventLoopGroup-4-1
nioEventLoopGroup-4-1接收到返回的消息:收到
------
main,发起连接请求
mess = 我是客户端:nioEventLoopGroup-5-1
nioEventLoopGroup-5-1接收到返回的消息:收到
------
main,发起连接请求
mess = 我是客户端:nioEventLoopGroup-6-1
nioEventLoopGroup-6-1接收到返回的消息:收到
------