UDP简介

TCP的所有操作都必须建立可靠的连接,这样肯定会消耗大量的系统资源,为了减少这种开销,在网络中又提供了另一种传输协议—UDP协议,DUP连接时一种不可靠的连接UDP协议广泛的使用与各种聊天工具中。
使用DUP发送的消息,接收方不一定会接收到,所有的信息使用数据包的形式发送出去,所以这就要求客户端要始终等待服务器发送的消息才能进行接收,在Java中使用DatagramSocket类和DatagramPacket类完成UDP程序的开发。

UDP程序实现

在UDP开发中使用DatagramPacket类包装一条要发送的信息,之后使用DatagramSocket类用于完成信息的发送操作,这两个类的常用方法如下表所示。

序号 DatagramPacket类常用方法 描述
1 DatagramPacket(byte[] buf, int length) 构造 DatagramPacket(数据报包)对象,指定数据报包的长度为length
2 DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造数据报包对象,指定数据的长度,目标地址,目标端口
3 byte[] getData() 返回接收的数据
4 int getLength() 返回将要发送或接收到的数据的长度。
序号 DatagramSocket类的常用方法 描述
1 DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。
2 void send(DatagramPacket p) 从此套接字发送数据报包。
3 void receive(DatagramPacket p) 从此套接字接收数据报包。

实现UDP程序,首先应该从客户端编写,在客户端指定要接收数据的端口和取得数据。
实例:UDP客户端—UDPClient

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
package my.net.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPClient
{
public static void main(String args[]) throws Exception
{
//定义用户接收数据报的引用
DatagramSocket ds = null;
// 开辟空间,以接收数据
byte[] buf = new byte[1024];
// 声明DatagramPacket对象
DatagramPacket dp = null;
//监听本机的6666端口
ds = new DatagramSocket(6666);
// 所有的信息使用buf保存
dp = new DatagramPacket(buf, 1024);
//接收数据到数据报包中
System.out.println("等待接收信息中...");
ds.receive(dp);
String receiveStr= new String(dp.getData(), 0, dp.getLength());
String str=receiveStr + "from "
+ dp.getAddress().getHostAddress() + ":" + dp.getPort();
System.out.println("接收到信息:");
System.out.println(str); // 输出内容
}
}

程序运行结果:
UDP客户端运行结果
程序运行之后,客户端已经打开了监听端口,等待服务器端向客户端发送信息。现在来编写服务端向客户端接收信息。
实例:DUP发送的服务端程序—UDPServer

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
package my.net.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPServer
{
public static void main(String args[]) throws Exception
{
// 定义发送数据报的对象
DatagramSocket ds = null;
// 声明DatagramPacket对象
DatagramPacket dp = null;
// 服务端在8888端口上发送信息
ds = new DatagramSocket(8888);
String str = "来着服务器的消息";
byte[] tosend=str.getBytes();
//向客户端发送消息
dp = new DatagramPacket(tosend, tosend.length,
InetAddress.getByName("localhost"), 6666);
System.out.println("服务器正在发送信息...");
ds.send(dp);
System.out.println("发送完毕...");
ds.close();
}
}

运行结果:
DUP客户端的运行结果
在服务器端运行完成后,客户端可以接收到来自服务端的消息,此时的客户端的运行结果:
客户端接收到的消息

Java TCP程序设计

Java中使用Socket(即套接字)完成TCP程序的开发,使用此类可以方便的建立可靠的,双向的,持续的,点对点的通信连接。
Socket的程序开发中,服务器段使用SeverSocket等待客户端的连接,对于Java的网络编程,每一个客户端都使用一个Socket对象表示,如下图所示。
Socket与SeverSocket

SeverSocket类与Socket类

SeverSocket类主要用于在服务器端程序的开发上,用于接收客户端的连接请求。SeverSocket类的常用方法如下表所示。

序号 方法 描述
1 ServerSocket(int port) 创建SeverSocket实例,并制定监听窗口
2 Socket accept() 侦听并接受到此套接字的连接。等待客户端的连接,此方法之前一直阻塞
3 InetAddress getInetAddress() 返回服务器的IP地址
4 boolean isClosed() 返回 ServerSocket 的关闭状态。
5 void close() 关闭此套接字。

在服务器端每次运行时都要使用accept()方法等待客户端连接,此方法发执行之后服务器段将进入到阻塞状态,直到客户端连接之后程序才能向下继续执行。此方法的返回值是Socket,每一个Socket都表示一个客户端对象,Socket的常用方法如下表所示。

序号 方法 描述
1 Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号。
2 InputStream getInputStream() 返回此套接字的输入流。
3 OutputStream getOutputStream() 返回此套接字的输出流。
4 boolean isClosed() 判断此套接字是否被关闭
5 void close() 关闭此套接字。

  在客户端,程序可以通过Socket类的getInputStream()方法取得服务器的输出信息,在服务端可以通过getOutputStream()方法取得客户端的输出信息,如下图所示:
服务器和客户端的连接
在网络编程中需要使用输入及输出流的形式完成信息的传递,所以在开发时需要导入java.io包。

第一个TCP程序

  下面通过ServerSocket类及Socket类完成 一个服务器的程序开发,此服务向客户端输出”hello world!”的字符串信息。

服务端代码

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
package my.net.tcp;

import java.net.*;
import java.io.*;
public class HelloServer
{
public static void main(String args[]) throws Exception
{

ServerSocket server = null; // 定义ServerSocket类
Socket client = null; // 表示客 户端
PrintStream out = null; // 打印流输出最方便
server = new ServerSocket(8888); // 服务器在8888端口上监听
System.out.println("服务器运行,等待客户端连接。");
client = server.accept(); // 得到连接,程序进入到阻塞状态
String str = "hello world"; // 表示要输出的信息
//在服务端可以通过Socket类的getOutputStream()得到对客户端的输出流
out = new PrintStream(client.getOutputStream());
//向客户端输出信息
out.println(str);
client.close();
server.close();
}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package my.net.tcp;

import java.net.*;
import java.io.*;
public class HelloClient
{
public static void main(String args[]) throws Exception
{
//表示客 户端
Socket client = null;
client = new Socket("localhost", 8888);
BufferedReader buf = null;
//客户端 可以通过getInputStream()方法获取服务器的输出
buf = new BufferedReader(
new InputStreamReader(client.getInputStream()));
String str = buf.readLine();
System.out.println("接收到服务器端输出内容:" + str);
buf.close();
client.close();
}
}

编码问题

上面的代码在eclipse中运行控制台输出可能有问题,所以到命令行中去编译和运行。因为用的编码是UTF-8编码的,所以编译的时候要指定编码。
如果不指定编码的话,会造成无法编译:

1
HelloServer.java:10: 错误: 编码GBK的不可映射字符

编码GBK的不可映射字符

CMD 中编译运行服务端

编译服务端:

1
javac -encoding UTF-8 -d . HelloServer.java

运行服务端:

1
java my.net.tcp.HelloServer

此时的运行结果:
这里写图片描述
可以看到服务器正在等待客户端的连接,如果没有收到客户端的连接,服务器端的将一直等待客户端的连接。

CMD 中编译运行客户端

编译客户端:

1
javac -encoding utf-8 -d . HelloClient.java

运行客户端:

1
java my.net.tcp.HelloClient

客户端连接上了之后,服务端程序才能接着往下运行,此时,服务端和客户端的运行结果如下。
服务端运行结果:
客户端运行结果
客户端运行结果:
客户端运行结果

从上面的程序的运行结果可以发现,服务器程序一执行到accept()方法后,程序将进入阻塞状态,直到客户端连接上之后,服务器才会继续往下执行。

使用telnet命令连接服务器端进行验证

服务器端程序建立完成之后,因为这里使用的是TCP通信协议连接的,所以可以直接使用telnet即可取得服务器的输出信息。
步骤:
在命令行下输入telnet即可进入telnet

1
telnet

成功进入telnet
重新打开一个cmd窗口,进入到服务端所在的目录,然后启动客户端:

1
java my.net.tcp.HelloServer

客户端输出:

服务端输出,等待Telnet的连接
然后切换到telnet命令行窗口,输入:

1
open localhost 8888

telnet运行结果:
telnet连接服务器
可以看到telnet成功连接到服务器端,此时telnet为客户端,服务器端向telnet客户端输出”hello world”
服务端运行结果:
服务端运行结果
可以看到telnet连接到服务端之后,服务器停止监听,继续往下运行。打印一条信息后服务端后停止运行。

telnet运行不正正常的情况: 没有开启telnet服务

如果还没有开启telnet服务的话,是无法启动telnet的。输入telnet显示如下的错误信息:
没有启动telnet的情况
解决方法是启动telnet即可

如何启动telnet

首先进入控制面板:
进入控制面板
然后在控制面板的收缩框中输入:启用或关闭Windows功能,然后点击启用或关闭Windows功能进入
点击启用或关闭Windows功能进入
然后下拉滚动条,找到Telnet客户端,在前面打钩,然后点确定即可启动Telnet客户端。
这里写图片描述

TCP实例:Echo程序

Echo程序是一个网络编程通信交互的一个经典案例,称为回应程序,即客户端输入什么内容,服务器端就会在这些内容前面加上”Echo:“,然后把这些信息发回给客户端,下面实现这样的一个程序。
在上面的代码中,服务器端每次执行完毕后服务器都会退出,这是因为服务器端指定接收一个客户端的连接,主要是由于accpet()方法只能使用一次。下面的程序中将通过循环的方式使用accpt(),这样每一个客户端执行完毕后,服务器端都可以重新等待用户连接。

实例:EchoServer

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
package my.net.tcp;

import java.net.*;
import java.io.*;
public class EchoServer
{
public static void main(String args[]) throws Exception
{
//定义服务器引用
ServerSocket server = null;
//定义客户端引用
Socket client = null;
BufferedReader buf = null;
PrintStream out = null;
//创建服务器,监听6666端口
server = new ServerSocket(6666);
//
boolean f = true; // 定义个标记位
while (f)
{
System.out.println("服务器运行,等待客户端连接。");
//等待客户端连接
client = server.accept();
//连接成功之后
//生成对客户端的输出流
out = new PrintStream(client.getOutputStream());
//获取客户端对本服务器的输入流
buf = new BufferedReader(
new InputStreamReader(client.getInputStream()));
boolean flag = true; // 标志位,表示可以一直接收并回应信息
while (flag)
{
//从客户端读入一条消息
String str = buf.readLine();
//如果有读到消息,或者没有读到
if (str == null || "".equals(str))
{ // 表示没有内容
flag = false; // 退出循环
} else
{
if ("exit".equals(str))
{ // 如果输入的内容为exit表示结束
flag = false;
} else
{
out.println("ECHO : " + str); // 回应信息
}
}
}
//关闭客户端
client.close();
}
//关闭服务端
server.close();
}
}

程序运行结果:
启动服务器
可以看到服务器运行之后,和之前的一样,要等待客户端的连接。下面是客户端程序的代码。

实例:EchoClient

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
package my.net.tcp;

import java.net.*;
import java.io.*;
public class EchoClient
{
public static void main(String args[]) throws Exception
{
//定义客户端引用
Socket client = null;
//创建客户端监听本机的6666端口
client = new Socket("localhost", 6666);

BufferedReader buf = null;

PrintStream out = null;
BufferedReader input = null;
//获取键盘输入流
input = new BufferedReader(new InputStreamReader(System.in));
//获取服务器对本客户端的输入流
buf = new BufferedReader(
new InputStreamReader(client.getInputStream()));
//获取对服务器的输出流
out = new PrintStream(client.getOutputStream());

boolean flag = true; // 定义标志位
while (flag)
{
System.out.print("输入信息:");
//接收键盘输入
String str = input.readLine();
//把键盘输入的字符串,打印到输出流,也就是打印到服务器端
out.println(str);
if ("exit".equals(str))
{
flag = false;
} else
{
//接收服务端的输入流
String echo = buf.readLine(); // 接收返回结果
System.out.println(echo); // 输出回应信息
}
}
//关闭服务器对本客户端的输入流
buf.close();
//关闭客户端
client.close();
}

在客户端EchoClient.java目录下,重新打开一个cmd窗口,然后编译运行客户端。
运行结果:
EchoClient运行结果
从程序运行的结果中可以发现,所有的输入信息最终都会通过回显的方式发回给客户端,并且在前面加上了”ECHO:“的信息。另外在本程序用,当一个客户端结束之后服务器端并并不会退出,而是继续等待下一个用户连接,下一个用户连接上了之后,服务器端会继续执行。
客户端退出后服务器不会退出,等待下一个客户端的连接
但是在本程序中有一个问题,就是现在的服务器端每次只能有一个用户能连接,属于单线程的处理机制,如果现在有其他客户端要连接到客户端,由于现在已经有一个客户端连接到服务器端了,其他客户端是无法连接的,要等待服务器出现空闲才可以连接。
此时服务器端只能连接一个客户端,其他客户端无法得到响应

为了能保证服务器可以同时连接多个客户端,可以加入多线程机制,即每一个客户端连接之后都启动一个线程,这样一个服务器就可以同时支持多个客户端的连接。
使用多线程机制,实现多个客户端同时连接

在服务器上应用多线程

对于服务器端来说,如果要加入多线程机制,则应该在每个用户连接之后启动一个新的线程提供服务。下面先建立一个EchoThread类,EchoThread类是专门用于提供服务的多线程操作,这里的多线程使用Runnable接口的方式实现。
把EchoSever.java中的accpet()之后的服务代码打包成线程,这样客户端连接上之后服务器就启动一个线程为该客户端提供服务,然后等待下一个客户端的连接。这样就能接收多个客户端的连接,并同时为多个客户端提供服务。
具体的做法是,把EchoSever.java中的accpet()之后的提供服务的代码,放到EchoThread.java的run()方法中。

实例:EchoThread

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
package my.net.tcp;

import java.net.*;
import java.io.*;
public class EchoThread implements Runnable
{
//客户端引用
private Socket client = null;
//构造函数
public EchoThread(Socket client)
{
this.client = client;
}
//线程执行体
public void run()
{
//表示来着客户端的输入
BufferedReader clientIn = null;
//对客户端的输出
PrintStream outToClient = null;
try
{
//获取对客户端的输出流
outToClient = new PrintStream(client.getOutputStream());
//获取来自客户端的输入流
clientIn = new BufferedReader(
new InputStreamReader(client.getInputStream()));
boolean flag = true; // 标志位,表示可以一直接收并回应信息
while (flag)
{
//读取来自客户端的输入
String str = clientIn.readLine();
if (str == null || "".equals(str))
{ // 表示没有内容
flag = false; // 退出循环
} else
{
if ("exit".equals(str))
{ // 如果输入的内容为bye表示结束
flag = false;
} else
{
//输出到客户端
outToClient.println("ECHO : " + str); // 回应信息
}
}
}
client.close();
} catch (Exception e)
{
}

}
}

EchoThread.java的主要功能就是接受一个客户端的Socket,并通过循环的方式接受客户端的输入信息,然后向客户端回送刚才输入的信息。
下面创建EchoThreadServer类,使用上面的EchoThread来为客户端提供服务。

实例:EchoThreadServer类

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
package my.net.tcp;

import java.net.*;
public class EchoThreadServer
{
public static void main(String args[]) throws Exception
{
//定义服务器的引用
ServerSocket server = null;
//客户端的引用
Socket client = null;
//建立服务器,监听本地6666端口
server = new ServerSocket(6666);
boolean f = true;
while (f)
{
System.out.println("服务器运行,等待客户端连接。");
//取得连接,客户端没连接之前先等待连接。
client = server.accept();
//一个客户端连接之后,为该客户端启动一个服务线程进行服务
new Thread(new EchoThread(client)).start();
}
server.close();
}
}

还是对比上面的EchoServer.java,我们发现把EchoServer.java的接受连接的部分提供服务功能部分分割出来成两个类,即可得到接受连接部分EchoThreadServer.java,和提供服务功能部分:EchoThread.java

运行服务端EchoThreadServer

运行服务端

然后运行客户端:EchoClient

运行客户端
此时服务器端程序会创建一个服务进程来提供服务,然后进入下一个循环,等待下一个客户端的连接
等待下一个客户端的连接
再打开一个cmd窗口,然后启动另一个客户端:
多个窗口同时连接到服务器端
可以看到此时已经成功连接上两个客户端了,这两个客户端都正常运行,且服务器端已经准备等待第三个客户端的连接。这样,在服务端,每一个连接到服务器的客户端Socket都会以一个线程的方式运行,无论有多少个客户端连接都可以同时完成操作。

使用回调函数

如果想知道到底有多少个客户端正在和服务器进行通讯,可以通过回调函数来实现.

回调函数

假设A线程创建了B线程,B线程运行过程中调用了A线程的方法,则该方法就叫回调方法.

如何通过客户端关闭服务器

EchoThreadServer

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
package tcp;
import java.net.*;
public class EchoThreadServer
{
// 静态变量,可以更改该标记以便停止服务端
private static boolean isServerAlive = true;
private static int clientNum = 0;
public static void main(String args[]) throws Exception
{
// 定义服务器的引用
ServerSocket server = null;
// 客户端的引用
Socket client = null;
// 建立服务器,监听本地6666端口
server = new ServerSocket(6666);
while (isServerAlive)
{
System.out.println("等待客户端连接...");
// 取得连接,客户端没连接之前先等待连接。
client = server.accept();
System.out.println(" 客户端连接成功,当前客户端数量:" + clientNum);
if (isServerAlive)
{
// 一个客户端连接之后,为该客户端启动一个服务线程进行服务
new Thread(new EchoThread(client)).start();
}
}
server.close();
client.close();
System.out.println("服务端已经停止...");
}
public static void addClientNum()
{
clientNum = clientNum + 1;
// 注意了,下面的赋值方法错误
// 后++:先取值,再增加,这将达不到增加的效果
// clientNum = clientNum ++;
}
public static void minusClientNum()
{
clientNum = clientNum - 1;
}
public static int getClientNum()
{
return clientNum;
}
public static void shutdownServer()
{
isServerAlive = false;
}
public static boolean isServerAlive()
{
return isServerAlive;
}
}

EchoThreadServer这个类中的,minusClientNum,getClientNum,shutdownServer这三个方法都是给子线程调用的方法,也就是所谓的回调方法.

EchoThread

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
package tcp;
import java.net.*;
import java.io.*;
public class EchoThread implements Runnable
{
// 客户端引用
private Socket client = null;
int clienId;
// 构造函数
public EchoThread(Socket client)
{
this.client = client;
// 设置服务线程编号
this.clienId = EchoThreadServer.getClientNum();
// 服务线程数量加1
EchoThreadServer.addClientNum();
}
// 线程执行体
public void run()
{
// 表示来着客户端的输入
BufferedReader inByclient = null;
// 对客户端的输出
PrintStream outToClient = null;
try
{
// 获取对客户端的输出流
outToClient = new PrintStream(client.getOutputStream());
// 获取来自客户端的输入流
inByclient = new BufferedReader(
new InputStreamReader(client.getInputStream()));
boolean isClientAlive = true; // 标志位,表示可以一直接收并回应信息
while (isClientAlive)
{
// 读取来自客户端的输入
String str = inByclient.readLine();
// 表示客户端要退出
if ("exit".equals(str))
{
// 结束循环不再接收客户端的输入.
isClientAlive = false;
// 服务线程数目减1
EchoThreadServer.minusClientNum();
}
// 关闭服务端
else if ("shutdownServer".equals(str))
{
EchoThreadServer.shutdownServer();
System.out.println("关闭服务器");
break;
} else
{
// 输出到客户端
outToClient.println(
"服务线程 " + this.clienId + " ECHO to client: " + str); // 回应信息
}
}
} catch (Exception e)
{
}
}
}

EchoThread的一个线程启动时,将会调用主线程的addClientNum方法对主线程中的计数器加1,该线程结束时调用minusClientNum方法对主线程中的计数器减1.
当服务线程收到shutdownServer这个字符串的时候,将调用主线程的shutdownServer方法关闭服务器的主线程,这样服务器就不能响应新的客户端连接了.等到之前连接过的客户端都结束后客户端将会真正结束。

EchoClient

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
package tcp;
import java.net.*;
import java.io.*;
public class EchoClient
{
public static void main(String args[]) throws Exception
{
// 定义客户端引用
Socket client = null;
// 创建客户端监听本机的6666端口
client = new Socket("localhost", 6666);
BufferedReader inputByServer = null;
PrintStream outToServer = null;
BufferedReader input = null;
// 获取键盘输入流
input = new BufferedReader(new InputStreamReader(System.in));
// 获取服务器对本客户端的输入流
inputByServer = new BufferedReader(
new InputStreamReader(client.getInputStream()));
// 获取对服务器的输出流
outToServer = new PrintStream(client.getOutputStream());
boolean flag = true; // 定义标志位
// 如果服务器没死
while (flag && EchoThreadServer.isServerAlive())
{
System.out.print("输入信息:");
// 接收键盘输入
String str = input.readLine();
// 把键盘输入的字符串,打印到输出流,也就是打印到服务器端
outToServer.println(str);
// 客户端退出或者关闭服务器
if ("exit".equals(str))
{
flag = false;
} else if ("shutdownServer".equals(str))
{
flag = false;
// 无效连接,用来抵消服务端的监听连接请求,
// 不然服务端会一直阻塞造成无法关闭
new Socket("localhost", 6666);
} else
{
// 接收服务端的输入流
String echo = inputByServer.readLine(); // 接收返回结果
System.out.println(echo); // 输出回应信息
}
}
// 关闭服务器对本客户端的输入流
inputByServer.close();
// 关闭客户端
client.close();
}
}

运行效果

打开两个客户端,并各自发送两个字符串,显示效果如下图:
这里有一张图片
关闭一个客户端,然后再打开,则此时服务线程的数量先减一后加一,服务线程(等于客户端数量)数量不变.
这里有一张图片
现在输入shutdownServer关闭服务器,效果如下:
这里有一张图片
这只对新的客户端有影响,对已经连接的客户端并没有影响.
这里有一张图片
当所有连接过的客户端都结束后,服务端没有任何线程在运行,此时服务器才真正的结束:
这里有一张图片

Java中如果要完成URL的编码和解码操作,可以使用URLEncoderURLDecoder两个类,URLEncoder可以为传递的内容进行编码,而URLDecoder可以为传递的内容进行解码。这两个类常用的方法如下。

序号 URLEncoder类常用方法 描述
1 public static String encode(String s, String enc) 使用指定的编码机制将字符串转换为 application/x-www-form-urlencoded 格式。
序号 URLDecoder类常用方法 描述
1 public static String decode(String s, String enc) 使用指定的编码机制对 application/x-www-form-urlencoded 字符串解码。
实例:编码和解码操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package my.net.encoder_decoder;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

public class Encode_Decode
{
public static void main(String[] args) throws UnsupportedEncodingException
{
String urlStr = "file:///D:/dev/workspace/Net/%E4%B8%8A%E4%B8%80%E7%AF%87%E5%8D%9A%E5%AE%A2.html";
String urlDecoder = URLDecoder.decode(urlStr, "utf-8");
// 把解码的好的字符串在编程成utf-8编码
String urlEncoderutf8 = URLEncoder.encode(urlDecoder, "utf-8");
System.out.println("原字符串 :"+urlStr);
System.out.println("解码后的字符串 :"+urlDecoder);
System.out.println("重新编码为utf-8:"+urlEncoderutf8);
System.out.println("再次解码 :"+URLDecoder.decode(urlEncoderutf8, "utf8"));
System.out.println();
}
}

运行结果:

1
2
3
4
5
原字符串       :file:///D:/dev/workspace/Net/%E4%B8%8A%E4%B8%80%E7%AF%87%E5%8D%9A%E5%AE%A2.html
解码后的字符串 :file:///D:/dev/workspace/Net/上一篇博客.html
重新编码为utf-8:file%3A%2F%2F%2FD%3A%2Fdev%2Fworkspace%2FNet%2F%E4%B8%8A%E4%B8%80%E7%AF%87%E5%8D%9A%E5%AE%A2.html
再次解码 :file:///D:/dev/workspace/Net/上一篇博客.html

中文转为Unicode字符

1
2
3
4
5
6
7
8
9
10
11
String str = "中文";
try
{
String encodeStr = URLEncoder.encode(str, "utf-16be");
encodeStr = encodeStr.replaceAll("%(.{2})%(.{2})", "\\\\u$1$2");
System.out.println(encodeStr);
} catch (UnsupportedEncodingException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}

运行结果:

1
\u4E2D\u6587

URL

URL(Uniform Resource Locator)统一资源定位符,可以直接使用此类找到互联网上的资源(如一个简单的网页),URL类的常用方法如下表所示。

序号 方法 描述
1 public URL(String spec) throws MalformedURLException 根据 String 表示形式的地址创建 URL 对象。
2 public URL(String protocol,String host,int port,String file) throws MalformedURLException 创建URL对象,并指定协议protocol,主机host,端口名称port,资源文件名file
3 public URLConnection openConnection() throws IOException 返回一个 URLConnection 对象,它表示 URL 所引用的远程对象的连接。
4 public final InputStream openStream() throws IOException 打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream
实例:读取的上一篇博客URL地址,下载该博客的源码。
地址:https://blog.csdn.net/qq_21808961/article/details/80848662
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
package java1.net.url;

import java.net.URL;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Scanner;
public class URLDemo
{
public static void main(String args[]) throws Exception
{
// 保存原来的标准输出流,这步很重要,不然就恢复不回来
PrintStream out = System.out;
// 新建打印流
PrintStream ps = new PrintStream("上一篇博客.html");
// 输出重定向
System.out.println("开始下载博客...");
System.setOut(ps);
URL url = new URL(
"https://blog.csdn.net/qq_21808961/article/details/80848662");
InputStream input = url.openStream(); // 打开输入流
Scanner scan = new Scanner(input); // 实例化Scanner类
scan.useDelimiter("\n"); // 设置读取分隔符
while (scan.hasNext())
{
System.out.println(scan.next());
}
//恢复标准输出流
System.setOut(out);
System.out.println("...博客下载完毕");
}
}

控制台输出:

1
2
开始下载博客...
...博客下载完毕

下载的路基:当前工程路径下
下载的路径
打开下载的html文件
打开下载的文件源码
  以上程序运行时,使用URL找到上一篇博客的页面资源,并使用输出重定向把页面中的内容下载下来到本地中。
在以上程序中下载下来的内容全部是页面的HTML代码,HTML(HyperText Mark-up Language),超文本标记语言,是构成网页文档的语言。

URLConnection

URLConnection是封装访问远程网络资源一般方法的类,通过它可以建立与远程服务器的连接,检查远程资源的一些属性,URLConnection的常用方法如下表所示。

序号 方法 描述
1 public int getContentLength() 取得内容的长度
2 public String getContentType() 取得内容的类型
3 public InputStream getInputStream() 获取连接的输入流
  URLConnection对象可以同拖URL类的openConnection()方法取得,下面通过URLConnection对象去的一个URL的基本信息。
实例:取得URL的基本信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package java1.net.url;

import java.net.URL;
import java.net.URLConnection;
public class URLConnectionDemo
{
public static void main(String args[]) throws Exception
{
URL url = new URL("file:///D:/dev/workspace/Net/%E4%B8%8A%E4%B8%80%E7%AF%87%E5%8D%9A%E5%AE%A2.html");
URLConnection urlCon = url.openConnection(); // 建立连接
System.out.println("内容大小:" + urlCon.getContentLength());
System.out.println("内容类型:" + urlCon.getContentType());
}
}

运行结果:

1
2
内容大小:57283
内容类型:text/html

上面连接的是上面我们下载到本地的html文件:上一篇博客.html。如果是使用网络上的其他路径获取的长度可能不正确(urlCon.getContentLength()返回-1),所以为了演示方便就以本地的html文件作为例子。
可以在该上一篇博客.html文件上右键选择属性查看该文件的大小:
上一篇博客.html文件大小
可以看到我们获取的长度是对的。

网络可以使不同物理位置上的计算机达到资源共享和通信的目的,在Java中也提供了专门的网络开发程序包—-java.net,以方便开发者进行网络程序的开发。
Java的网络编程提供了两种通信协议:TCP(传输控制协议),UDP(数据报协议)。
关于TCP和UDP
TCP和UDP都属于传输层协议,TCP(Transmission Control Protocol)是可靠的传输协议,传输前会采用三次握手的方式建立连接,以保证传输的可靠性。
而UDP(User Datagram Protocol)协议是不可靠的传输协议,即发送出去的数据不一定接收得到,网上的聊天工具一般使用UDP协议。

IP(Internet Protocal)与InetAddress

IP地址简介

互联网上的每一台计算机都有一个唯一表示自己的标记,这个标记就是IP地址。
IP地址使用32位长度的二进制数据表示,一般在实际中看到的大部分IP地址都是以点分十进制形式表示的,如192.168.1.1

IP地址格式

IP地址=网络地址+主机地址

  • 网络号:用于识别主机所在的网络。
  • 主机号:用于识别该网络中的主机。
    IP地址分为5类,A类地址保留给政府机构,B类地址分配给中等规模的公司,C类地址分配给任何需要的人,D类地址用户组播,E类地址用于实验,各类地址可以容纳的地址数目不同,这5类地址的范围如下表所示。
    序号 地址分类 地址范围
    1 A类地址 1.0.0.1~126.255.255.254
    2 B类地址 128.0.0.1~191.255.255.254
    3 C类地址 192.0.0.1~233.255.255.254
    4 D类地址 224.0.0.1~239.255.255.254
    5 E类地址 240.0.0.1~255.255.255.254
    在上面的地址分类表中可以发现没哟127.X.X.X的表示,这是因为127.X.X.X是保留地址,用做循环测试,在开发测试中经常使用127.0.0.1表示本机的IP地址。
    ####IPv4,IPv6
    IPv4(Internet Protocal version 4)是互联网协议的第4个版本,也是使用最广泛的版本,但是IPv4已经无法满足当今互联网上的主机数量,所以在此基础之上有产生的新的版本IPv6,使用IPv6可以比IPv4容纳更多的主机。

    InetAddress

    InetAddress类主要表示IP地址,这个类有两个子类:Inet4Address,Inet6Address,一个用于表示IPv4,另一个用于表示IPv6。InetAddress类的常用方法如下表所示。
    序号 描述
    1 public static InetAddress getByName(String host)throws UnknownHostException 在给定主机名的情况下确定主机的 IP 地址(InetAddress对象)。
    2 public static InetAddress getLocalHost() throws UnknownHostException 返回本地主机的IP地址(InetAddress对象)
    3 public String getHostName() 获取此 IP 地址(InetAddress对象)的主机名。
    4 public boolean isReachable(int timeout) throws IOException 测试是否可以达到该地址。
    实例: 测试InetAddress类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package java1.net.ip;

import java.net.InetAddress;
public class InetAddressDemo
{
public static void main(String args[]) throws Exception
{
InetAddress locAdd = null;
InetAddress csdn = null;
locAdd = InetAddress.getLocalHost();// 得到本机
//https://www.csdn.net/---注意要把"https://"去掉
csdn = InetAddress.getByName("blog.csdn.net");
System.out.println("本机的 IP地址:" + locAdd.getHostAddress());
System.out.println(" CSDN博客地址:" + csdn.getHostAddress());
System.out.println(" 本机是否可达:" + locAdd.isReachable(5000));
System.out.println("CSDN博客是否可达:" + csdn.isReachable(5000));
}
}

运行结果:

1
2
3
4
5
本机的   IP地址:192.168.45.9
CSDN博客地址:47.95.47.253
本机是否可达:true
CSDN博客是否可达:true

eclipse HTML代码格式化

如下图所示:
这里有一张图片

  • Line width:表示代码显示的宽度
  • Split multiple attributes each on a new line:表示html标签的属性各自放在一行,这个没必要.
  • Align final bracket in multi-line element tags:表示对齐多行元素的结束符
  • Clear all blank lines:删除空行
  • Indent using spaces:使用空格对齐,而不是tab

Word如何插入目录

插入标题

首先选中一段文本:
这里有一张图片
然后点击开始,点击段落区域右下角的小箭头,然后点击缩进和间距选项卡,常规,然后在大纲级别下拉框中选择标题的级别,我这里选择2级:
这里有一张图片
此时就可以插入标题了:
这里有一张图片

更新目录

点击引用,然后在最左边的目录框中,选择点击更新目录即可:
这里有一张图片

取消标题

取消标题和插入标题类似,在开始>段落>缩进和间距>常规>大纲级别选择正文文本即可:
这里有一张图片
这里有一张图片

sfc /scannow命令说明

sfc的全称是SystemFileChecker,指代系统文件检查程序用于扫描和检查是否存在已损坏或被取代的系统档案文件,并进行正确文件的修复替换

1
2
3
4
5
6
7
8
9
C:\Windows\system32>sfc /scannow
开始系统扫描。此过程将需要一些时间。
开始系统扫描的验证阶段。
验证 100% 已完成。
Windows 资源保护找到了损坏文件并成功修复了它们。
对于联机修复,位于 windir\Logs\CBS\CBS.logCBS 日志文件中
有详细信息。例如 C:\Windows\Logs\CBS\CBS.log。对于脱机修复,
/OFFLOGFILE 标记提供的日志文件中有详细信息。
C:\Windows\system32>

参考资料

https://blog.csdn.net/zhangyihui2016/article/details/47376093

PreparedStatement接口方法2

接口PreparedStatement位于java.sql
所有超级接口:
Statement, Wrapper
所有已知子接口:
CallableStatement

执行SQL语句的方法

方法 描述
boolean execute() 在此 PreparedStatement 对象中执行 SQL 语句,该语句可以是任何种类的 SQL 语句。
ResultSet executeQuery() 在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的 ResultSet 对象。
int executeUpdate() 在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言(Data Manipulation LanguageDML)语句,比如 INSERTUPDATE DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。

其他方法

方法 描述
void addBatch() 将一组参数添加到此 PreparedStatement 对象的批处理命令中。
void clearParameters() 立即清除当前参数值。

getXXX方法

方法 描述
ResultSetMetaData getMetaData() 获取包含有关 ResultSet 对象列信息的 ResultSetMetaData 对象,ResultSet 对象将在执行此 PreparedStatement 对象时返回。
ParameterMetaData getParameterMetaData() 获取此 PreparedStatement 对象的参数的编号、类型和属性。

setXXX方法

setObject方法

方法 描述
void setObject(int parameterIndex, Object x) 使用给定对象设置指定参数的值。
void setObject(int parameterIndex, Object x, int targetSqlType) 使用给定对象设置指定参数的值。
void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) 使用给定对象设置指定参数的值。

设置数组的方法

方法 描述
void setArray(int parameterIndex, Array x) 将指定参数设置为给定 java.sql.Array 对象。

设置输入流的方法

方法 描述
void setAsciiStream(int parameterIndex, InputStream x) 将指定参数设置为给定输入流。
void setAsciiStream(int parameterIndex, InputStream x, int length) 将指定参数设置为给定输入流,该输入流将具有给定字节数。
void setAsciiStream(int parameterIndex, InputStream x, long length) 将指定参数设置为给定输入流,该输入流将具有指定字节数。
void setBinaryStream(int parameterIndex,InputStream x) 将指定参数设置为给定输入流。
void setBinaryStream(int parameterIndex, InputStream x, int length) 将指定参数设置为给定输入流,该输入流将具有给定字节数。
void setBinaryStream(int parameterIndex, InputStream x, long length) 将指定参数设置为给定输入流,该输入流将具有指定字节数。

设置大数字对象的方法

方法 描述
void setBigDecimal(int parameterIndex, BigDecimal x) 将指定参数设置为给定 java.math.BigDecimal 值。
void setBlob(int parameterIndex, Blob x) 将指定参数设置为给定 java.sql.Blob 对象。
void setBlob(int parameterIndex, InputStream inputStream) 将指定参数设置为 InputStream 对象。
void setBlob(int parameterIndex, InputStream inputStream, long length) 将指定参数设置为 InputStream 对象。
void setClob(int parameterIndex, Clob x) 将指定参数设置为给定 java.sql.Clob 对象。
void setClob(int parameterIndex, Reader reader) 将指定参数设置为 Reader 对象。
void setClob(int parameterIndex, Reader reader, long length) 将指定参数设置为 Reader 对象。
void setNClob(int parameterIndex, NClob value) 将指定参数设置为 java.sql.NClob 对象。
void setNClob(int parameterIndex, Reader reader) 将指定参数设置为 Reader 对象。
void setNClob(int parameterIndex, Reader reader, long length) 将指定参数设置为 Reader 对象。

Set基本数据类型方法

方法 描述
void setByte(int parameterIndex, byte x) 将指定参数设置为给定 Java byte 值。
void setBytes(int parameterIndex, byte[] x) 将指定参数设置为给定 Java byte 数组。
void setShort(int parameterIndex, short x) 将指定参数设置为给定 Java short 值。
void setInt(int parameterIndex, int x) 将指定参数设置为给定 Java int 值。
void setLong(int parameterIndex, long x) 将指定参数设置为给定 Java long 值。
void setFloat(int parameterIndex, float x) 将指定参数设置为给定 Java REAL 值。
void setDouble(int parameterIndex, double x) 将指定参数设置为给定 Java double 值。
void setBoolean(int parameterIndex, boolean x) 将指定参数设置为给定 Java boolean 值。

设定为Reader对象的方法

方法 描述
void setCharacterStream(int parameterIndex, Reader reader) 将指定参数设置为给定 Reader 对象。
void setCharacterStream(int parameterIndex, Reader reader, int length) 将给定参数设置为给定 Reader 对象,该对象具有给定字符数长度。
void setCharacterStream(int parameterIndex, Reader reader, long length) 将指定参数设置为给定 Reader 对象,该对象具有给定字符数长度。
void setNCharacterStream(int parameterIndex, Reader value) 将指定参数设置为 Reader 对象。
void setNCharacterStream(int parameterIndex, Reader value, long length) 将指定参数设置为 Reader 对象。

set时间方法

方法 描述
void setDate(int parameterIndex, Date x) 使用运行应用程序的虚拟机的默认时区将指定参数设置为给定 java.sql.Date 值。
void setDate(int parameterIndex, Date x, Calendar cal) 使用给定的 Calendar 对象将指定参数设置为给定 java.sql.Date 值。
void setTime(int parameterIndex, Time x) 将指定参数设置为给定 java.sql.Time 值。
void setTime(int parameterIndex, Time x, Calendar cal) 使用给定的 Calendar 对象将指定参数设置为给定 java.sql.Time 值。
void setTimestamp(int parameterIndex, Timestamp x) 将指定参数设置为给定 java.sql.Timestamp 值。
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) 使用给定的 Calendar 对象将指定参数设置为给定 java.sql.Timestamp 值。

setString方法

方法 描述
void setString(int parameterIndex, String x) 将指定参数设置为给定 Java String 值。
void setNString(int parameterIndex, String value) 将指定参数设置为给定 String 对象。

setNull方法

方法 描述
void setNull(int parameterIndex, int sqlType) 将指定参数设置为 SQL NULL
void setNull(int parameterIndex, int sqlType, String typeName) 将指定参数设置为 SQL NULL
void setRef(int parameterIndex, Ref x) 将指定参数设置为给定 REF(<structured-type>) 值。

其他set方法

方法 描述
void setRowId(int parameterIndex, RowId x) 将指定参数设置为给定 java.sql.RowId 对象。
void setSQLXML(int parameterIndex, SQLXML xmlObject) 将指定参数设置为给定 java.sql.SQLXML 对象。
void setURL(int parameterIndex, URL x) 将指定参数设置为给定 java.net.URL 值。

从接口 java.sql.Statement 继承的方法

addBatch, cancel, clearBatch, clearWarnings, close, execute, execute, execute, execute, executeBatch, executeQuery, executeUpdate, executeUpdate, executeUpdate, executeUpdate, getConnection, getFetchDirection, getFetchSize, getGeneratedKeys, getMaxFieldSize, getMaxRows, getMoreResults, getMoreResults, getQueryTimeout, getResultSet, getResultSetConcurrency, getResultSetHoldability, getResultSetType, getUpdateCount, getWarnings, isClosed, isPoolable, setCursorName, setEscapeProcessing, setFetchDirection, setFetchSize, setMaxFieldSize, setMaxRows, setPoolable, setQueryTimeout

从接口 java.sql.Wrapper 继承的方法

isWrapperFor, unwrap

eclipse自身设置的原因

指定JRE路径

eclipse启动的时候,它总是会搜索让其运行的jre,往往就是这个搜索过程让eclipse启动变慢了,解决方案是,在eclipse的配置文件eclipse.ini(该文件位于eclipse安装目录下)中加入-vm配置,直接指定jre的位置,减少这个搜索过程.
这里有一张图片

关闭用不到的插件

这里有一张图片
我这里用到Git,所以我保留的Git integration for Eclipse-UI这个插件,
用到了maven,所以我保留了m2e Marketplace这个插件
还用到的Spring,所以我保留了Spring相关的插件,
至于其他插件,我全部取消勾选.
到这里应该就启动快了,我的是这样,至于其他的,就先不折腾了。

被Windows Defender拦截

Windows Defender不扫描eclipse的安装目录

上面是eclipse中的配置,但是我发现,配置之后没有效果.我打开任务管理器发现,每次启动eclipse的时候,都会被Windows Defender拦截,如下图所示:
这里有一张图片
Windows Defender会先扫描eclipse的安装目录,确认安全后再让eclipse运行.扫描是很耗时的,这就造成eclipse启动十分缓慢.

解决方案

打开windows defender,然后点击病毒和威胁防护,然后点击"病毒和威胁防护"设置
这里有一张图片
然后向下拖动滚动条,找到排除项,点击添加或删除排除项
这里有一张图片
然后点击添加排除项,添加eclipse的安装目录即可。
这里有一张图片

参考资料

如何让Windows Defender不扫描指定排除文件夹](如何让WindowsDefender不扫描指定排除文件夹)