记录生活中的点点滴滴

0%

Java网络编程

网络编程的目的就是直接或间接地通过网络协议与其他计算机进行通信。在Java中包含网络编程地各种类,通过创建这些类的对象,调用相应的方法,就可以进行网络应用程序的编写。

在网络通信中主要有两种模式的通信方式:一种是客户机/服务器(Client/Server)模式,简称C/S模式;另一种是浏览器/服务器(Browser/Server)模式,简称B/S模式

尽管 TCP/IP 协议从名称看只包括 TCP 这个协议名,但是在 TCP/IP 协议的传输层同时存在 TCP(Transmission Control Protocol,传输控制协议)UDP(User Datagram Protocol,用户数据报协议)两个协议。

在网络通信中使用 TCP 的方式需要建立专门的虚拟连接,然后进行可靠的数据连接,如果数据发送失败,客户端会自动重发该数据。而使用 UDP 方式不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。

TCP 协议是一种以固定连线为基础的协议,它提供两台计算机之间可靠的数据传送。而 UDP 无连接通信协议,它不保证可靠数据的传输,但能够向若干目标发送数据以及接收来自若干源的数据。

对于一些重要的数据,一般使用 TCP 方式来进行数据传输,而大量的非核心数据则通过 UDP 方式进行传递。使用 TCP 方式传递的速度稍微慢一点,而且传输时产生的数据量会比 UDP 大一点。

在网络上很多应用程序都是采用客户端/服务器(C/S)的模式,实现网络通信必须将两台计算机连接起来建立一个双向的通信链路,这个双向通信链路的每一端称之为一个套接字(Socket)。

Java TCP通信:Java ServerSocket 类和 Socket 类

TCP 网络程序是指利用 Socket 编写的通信程序。利用 TCP 协议进行通信的两个应用程序是有主次之分的,一个是服务器程序,一个是客户端程序,两者的功能和编写方法不太一样。其中 ServerSocket 类表示 Socket 服务器端,Socket 类表示 Socket 客户端,两者之间的交互过程如下:

  1. 服务器端创建一个 ServerSocket (服务器端套接字),调用 accept() 方法等待客户端来连接。
  2. 客户端程序创建一个 Socket,请求与服务器建立连接。
  3. 服务器接收客户端的连接请求,同时创建一个新的 Socket 与客户端建立连接,服务器继续等待新的请求。

ServerSocket

ServerSocket 类是与 Socket 类相对应的用于表示通信双方中的服务器端,用于在服务器上开一个端口,被动的等待数据(使用accept() 方法)并建立连接进行数据交互。

常用构造方法:ServerSocket(int port):创建绑定到特定端口的服务器套接字。

调用 accept() 方法会返回一个和客户端 Socket 对象相连接的 Socket 对象,服务器端的 Socket 对象使用 getOutputStream() 方法获得的输出流将指定客户端 Socket 对象使用 getInputStream() 方法获得那个输入流。同样,服务器端的 Socket 对象使用的 getInputStream() 方法获得的输入流将指向客户端 Socket 对象使用的 getOutputStream() 方法获得的那个输出流。也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之同样如此。

使用 ServerSocket 类创建一个使用端口 8888 的服务器端套接字,如下:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器端Socket套接字创建成功");
while (true){
System.out.println("等待客户端连接...");
Socket accept = serverSocket.accept();
System.out.println("成功与客户端建立连接...");
}
}

如果没有客户端的连接请求,则 accept() 方法为空,所以不会输出“成功建立与客户端的连接”

Socket

Socket 类表示通信双方中的客户端,用于呼叫远端机器上的一个端口,主动向服务器端发送数据(当连接建立后也能接收数据)

常用构造方法:Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口。

一个简单TCP程序例子

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
public class Client {
public static void main(String[] args) {
//1.定义 Socket 对象、OutputStream 对象和一个 InputStream 对象并完成初始化
Socket socket = null;
InputStream in = null;
OutputStream out = null;
//定义服务器端的 IP 地址和端口号
String ip = "127.0.0.1";
int port = 5000;
try {
//2.建立与服务器端的连接并将数据发送到服务器端
socket = new Socket(ip,port);
out = socket.getOutputStream();//发送数据
out.write("我是客户端数据...".getBytes());
Thread.sleep(1000);

//3.从输出流中读取服务器的反馈信息并输出到控制台
byte[] bytes = new byte[1024];
in = socket.getInputStream();
int len = in.read(bytes);
System.out.println("服务器的反馈为:"+new String(bytes,0,len));
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
//4.关闭流及 Socket 对象
try {
in.close();
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}
}

2、创建一个类作为服务器端

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
public class Server {
public static void main(String[] args) {
//5.创建ServerSocket、Socket、OutputStream、InputStream以及端口号并初始化
ServerSocket serverSocket = null;
Socket socket = null;
InputStream in = null;
OutputStream out = null;
int port = 5000;
try {
//6.开启服务器并接收客户端发送的数据
serverSocket = new ServerSocket(port);//创建服务器套接字
System.out.println("服务器开启,准备连接...");
socket = serverSocket.accept();//获得连接
//结束客户端发送的内容
in = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = in.read(bytes);
System.out.println("客户端发送的内容是:"+new String(bytes,0,len));
//7.使用输出流返回信息给客户端
out = socket.getOutputStream();
out.write("我是服务器端...".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//8.关闭流对象、ServerSocket对象以及Socket对象
try {
in.close();
out.close();
serverSocket.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

3、运行服务器端的代码,结果如下:

​ 接着运行客户端的代码,等待1s后结果如下:

​ 此时再看看服务器端的结果:

传输对象数据

了解如何在服务器开始一个端口监听套接字,以及如何在客户端连接服务器,发送简单的数据后,下面将实现如何在客户端发送一个对象到服务器端,服务器如何解析对象中的数据。

1、我们先创建一个 Person 类,有 nameage 属性,把 getter/setter 方法什么的都写好。

2、创建服务器端类:

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
public class Server {
public static void main(String[] args) throws IOException {
int port = 8888;
ServerSocket serverSocket = new ServerSocket(port);
while (true){
Socket socket = serverSocket.accept();
invoke(socket);
}
}

private static void invoke(final Socket socket) {
new Thread(()->{
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
ois = new ObjectInputStream(socket.getInputStream());
Object obj = ois.readObject();
Person person = (Person)obj;
System.out.println(Thread.currentThread().getName()+"--"+person);
person.setName(person.getName()+"__new__");
person.setAge(person.getAge()+10);
oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(person);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
ois.close();
oos.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}

3、创建客户端类:

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
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Socket socket = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
String ip = "127.0.0.1";
int port = 8888;
try {
socket = new Socket(ip,port);
oos = new ObjectOutputStream(socket.getOutputStream());
Person person = new Person("高松", 20);
oos.writeObject(person);
oos.flush();
ois = new ObjectInputStream(socket.getInputStream());
Object obj = ois.readObject();
if(obj!=null){
person = (Person)obj;
System.out.println(person);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
ois.close();
oos.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

4、运行服务器端程序,然后再运行客户端程序,如下结果:

Java UDP通信:Java DatagramSocket类和DatagramPacket类

在 TCP/IP 协议的传输层除了一个 TCP 协议之外,还有一个 UDP 协议。UDP 协议是用户数据报协议的简称,也用于网络数据的传输。虽然 UDP 协议是一种不太可靠的协议,但有时在需要较快地接收数据并且可以忍受较小错误的情况下,UDP就会表现出更大的优势。

Java中使用 UDP 协议发送数据的步骤:

  • 使用 DatagramSocket() 创建一个数据包套接字。
  • 使用 DatagramPacket() 创建要发送的数据包。
  • 使用 DatagramSocket 类的 send() 方法发送数据包。

接收 UDP 数据包的步骤:

  • 使用 DatagramSocket 创建数据包套接字,并将其绑定到指定的端口。
  • 使用 DatagramPacket 创建字节数组来接收数据包。
  • 使用 DatagramPacket 类的 receive() 方法接收 UDP 包。

DatagramPacket类

java.net 包中的 DatagramPacket 类用来表示数据报包,数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。表 1 和表 2 简单介绍了 DatagramPacket 的构造方法和常用方法。

构造方法 说明
DatagramPacket(byte[] buf,int length) 构造 DatagramPacket,用来接收长度为 length 的数据包
DatagramPacket(byte[] buf,int offset, int length) 构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量
DatagramPacket(byte[] buf,int length, InetAddress address,int port) 构造 DatagramPacket,用来将长度为 length 的包发送到指定主机上的指定端口
DatagramPacket(byte[] buf,int length, SocketAddress address) 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口
DatagramPacket(byte[] buf,int offset, int length,InetAddress address,int port) 构造 DatagramPacket,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口。
DatagramPacket(byte[] buf,int offset, int length,SocketAddress address) 构造数据报包,用来将长度为 length、偏移量为 offset 的包发 送到指定主机上的指定端口。

常用方法:

方法 说明
InetAddress getAddress() 返回某台机器的 IP 地址,此数据报将要发往该机器或者从该机器接收。
byte[] getData() 返回数据缓冲区。
int getLength() 返回将要发送或者接收的数据的长度。
int getOffset() 返回将要发送或者接收的数据的偏移量。
int getPort() 返回某台远程主机的端口号,此数据报将要发往该主机或者从该主机接收。
getSocketAddress() 获取要将此包发送或者发出此数据报的远程主机的SocketAddress(通常为 IP地址+端口号)。
void setAddress(InetAddress addr) 设置要将此数据报发往的目的机器的IP地址。
void setData(byte[] buf) 为此包设置数据缓冲区。
void setData(byte[] buf,int offset, int length) 为此包设置数据缓冲区。
void setLength(int length) 为此包设置长度。
void setPort(int port) 设置要将此数据报发往的远程主机的端口号。
void setSocketAddress(SocketAddress address) 设置要将此数据报发往的远程主机的SocketAddress(通常为 IP地址+端口号)。

DatagramSocket 类

DatagramSocket 类用于表示发送和接收数据报包的套接字。数据报包套接字是包投递服务的发送或接收点。每个在数据报包套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。

构造方法 说明
DatagramSocket() 构造数据报包套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port) 创建数据报包套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int portJnetAddress addr) 创建数据报包套接字,将其绑定到指定的本地地址。
DatagramSocket(SocketAddress bindaddr) 创建数据报包套接字,将其绑定到指定的本地套接字地址。

常用方法:

方法 说明
void bind(SocketAddress addr) 将此 DatagramSocket 绑定到特定的地址和端口。
void close() 关闭此数据报包套接字。
void connect(InetAddress address,int port) 将套接字连接到此套接字的远程地址。
void connect(SocketAddress addr) 将此套接子连接到远程套接子地址(IP地址+端口号)。
void disconnect() 断开套接字的连接。
InetAddress getInetAddress() 返回此套接字连接的地址。
InetAddress getLocalAddress() 获取套接字绑定的本地地址。
int getLocalPort() 返回此套接字绑定的本地主机上的端口号。
int getPort() 返回此套接字的端口。

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
public class Client {
public static void main(String[] args) throws UnknownHostException {
//1.定义一个 DatagramSocket 对象和一个 DatagramPacket 对象并初始化,再定义一个 InetAddress 对象和一个端口号并初始化
DatagramSocket ds = null;
DatagramPacket dp = null;
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 6666;
try {
ds = new DatagramSocket();
for (int i = 0; i < 5; i++) {
byte[] bytes = ("我是客户端" + (i + 1)).getBytes();
dp = new DatagramPacket(bytes,bytes.length,address,port);
ds.send(dp);
Thread.sleep(1000);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}

2、创建服务器端类:

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
public class Server {
public static void main(String[] args) {
DatagramSocket ds = null;
DatagramPacket dp = null;
int port = 6666;
try {
ds = new DatagramSocket(port);
System.out.println("UDP服务已启动...");
byte[] bytes = new byte[1024];
while (!ds.isClosed()){
dp = new DatagramPacket(bytes,bytes.length);
try {
System.out.println(dp.getLength());
ds.receive(dp);
byte[] data = dp.getData();
int len = data.length;
System.out.println("UDP客户端发送的内容是:"+new String(data,0,len));
System.out.println("UDP客户端IP:"+dp.getAddress());
System.out.println("UDP客户端端口:"+dp.getPort());
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (SocketException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}

3、先启动服务器端,再启动客户端,可以看到服务器端结果如下:

URL?及URL类和URLConnection类

URL 概念

URL 是统一资源定位符(Uniform Resource Locator)的简称,它表示 Internet 上某一资源的地址。通过 URL 可以访问各种网络资源,比如常见的 WWW 及 FTP 站点。浏览器可以通过解析给定的 URL 在网络上查找相应的文件或其他资源。

URL的语法格式如下所示:

1
protocol://resourceName

协议名(protocol)指明获取资源所使用的传输协议,如HTTP、FTP 和 file 等,资源名(resourceName)则是资源的完整地址,包括主机名、端口号、文件名或文件内部的一个引用。例:

1
2
http://www.sun.com/    协议名://主机名
http://localhost:8080/Test/admin/login.jsp 协议名://机器名:端口号/文件名

URL 类

在 java.net 包中包含专门用来处理 URL 的类 URL,可以获得 URL 的相关信息,例如 URL 的协议名和主机名等。

构造方法 说明
public URL (String spec) 通过一个表示 URL 地址的字符串可以构造一个 URL 对象。
public URL(URL context,String spec) 使用基本地址和相对 URL 构造一个 URL 对象。
public URL(String protocol,String host,String file) 使用指定的协议、主机名和文件名创建一个 URL 对象。
public URL(String protocol,String host,int port,String file) 使用指定的协议、主机名、端口号和文件名创建一个 URL 对象。
方法 说明
public String getProtocol() 获取该 URL 的协议名
public String getHost() 获取该 URL 的主机名
public int getPort() 获取该 URL 的端口号,如果没有设置端口,返回 -1。
public String getFile() 获取该 URL 的文件名。
public String getRef() 获取该 URL 在文件中的相对位置。
public String getQuery() 获取该 URL 的查询信息。
public String getPath() 获取该 URL 的路径。
public String getAuthority() 获取该 URL 的权限信息。
public String getUserInfo() 获得使用者的信息。
public String getRef() 获得该 URL 的锚点。
1
2
3
4
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("https://www.gaosong.site:80/index.html");
System.out.println(url.getPath());
}

URLConnection 类

完成了 URL 的定义,接下来就可以获得 URL 的通信连接。在 java.net 包中,定义了专门的 URLConnection 类来表示与 URL 建立的通信连接,URLConnection 类的对象使用 URL 类的 openConnection() 方法获得。

方法 说明
void addRequestProperty(String key,String value) 添加由键值对指定的一般请求属性。key 指的是用于识别请求的关键字 (例如 accept),value 指的是与该键关联的值。
void connect() 打开到此 URL 所引用的资源的通信链接(如果尚未建立这样的链接)。
Object getConnection() 检索此 URL 链接的内容。
InputStream getInputStream() 返回从此打开的链接读取的输入流。
OutputStream getOutputStream() 返回写入到此链接的输出流。
URL getURL() 返回此 URLConnection 的 URL 字段的值。

我们举个栗子,建立一个到工商大学官网的连接,读取数据输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class URLConnectionDemo {
public static void main(String[] args) throws IOException {
URL url = new URL("http://www.zjsu.edu.cn");
URLConnection connection = url.openConnection();
InputStream in = connection.getInputStream();
byte[] bytes = new byte[1024];
int len;
while ((len=in.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
in.close();
}
}

输出结果(一部分):

基于ServerSocket建立聊天服务器

下面我们做一个案例,就是用 Socket 实现聊天服务器,可以实现客户端与客户端之间的互聊(win下cmd命令行tenlet连接服务器模拟客户端)。

下面是每个类具体作用:

  • Server:客户端测试
  • ServerThread:客户端真正的实现类,服务器端的侦听,负责获取连接成功的客户端
  • ChatSocket:客户端的通信,向客户端发送和输出信息,实现单独线程的通信
  • ChatManager:聊天管理,实现Socket入队列,发送数据

先看Server类:

1
2
3
4
5
public class Server {
public static void main(String[] args) {
new ServerThread().start();
}
}

ServerThread类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ServerThread extends Thread {
@Override
public void run(){
int port = 6666;
try {
//创建服务器socket
ServerSocket serverSocket = new ServerSocket(port);
while (true){
Socket socket = serverSocket.accept();
//消息提示框
JOptionPane.showMessageDialog(null, "有客户端连接到了本机6666端口哦");
//与客户端通信
ChatSocket cs = new ChatSocket(socket);
cs.start();
//添加socket到队列
ChatManager.getInstance().addChatPeople(cs);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

ChatSocket类:

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
/*
*与客户端通信,向客户端发送信息
*实现单独线程的通信处理
*/
public class ChatSocket extends Thread{
Socket socket;
public ChatSocket(Socket socket){
this.socket = socket;
}
//向客户端输出信息
public void out(String str){
try {
socket.getOutputStream().write(str.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
//向客户端发送消息
@Override
public void run() {
out("success\n");
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while ((line=br.readLine())!=null){
System.out.println(line);
ChatManager.getInstance().send(this,line);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

ChatManager类:

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
//添加socket到队列,发送数据
public class ChatManager {
//因为一个聊天系统只有一个聊天管理,所以需进行单例化private
private ChatManager() {}
private static final ChatManager cm = new ChatManager();
public static ChatManager getInstance() {
return cm;
}

//创建保存socket的队列
Vector<ChatSocket> vector=new Vector<>();

//添加聊天人
public void addChatPeople(ChatSocket cs) {
vector.add(cs);
}
//群发消息
public void send(ChatSocket cs,String str){
for (int i = 0; i < vector.size(); i++) {
ChatSocket chatSocket = vector.get(i);
if(cs!=chatSocket)
chatSocket.out(str);
}
}
}

启动服务器端Server,然后用cmd命令行 telent localhost 6666 创建一个客户端,结果如下:

点击确定之后,服务器端会出现 success

我们再通过cmd打开另外两个客户端,现在一共三个客户端,然后在其中一个客户端下输入 hello world!,我们会发现其他两个客户端还有服务器端都会打印出 hello world!

我们还可以多次尝试,都是可以的。