最新Java基础系列课程--Day15-网络编程(一)https://developer.aliyun.com/article/1423553
四、UDP通信代码(多发多收)
刚才的案例,我们只能客户端发一次,服务端接收一次就结束了。下面我们想把这个代码改进一下,
需求:实现客户端不断的发数据,而服务端能不断的接收数据,客户端发送exit时客户端程序退出。
4.1 客户端程序
/** * 目标:完成UDP通信快速入门:实现客户端反复的发。 */ public class Client { public static void main(String[] args) throws Exception { // 1、创建客户端对象(发韭菜出去的人) DatagramSocket socket = new DatagramSocket(); // 2、创建数据包对象封装要发出去的数据(创建一个韭菜盘子) /* public DatagramPacket(byte buf[], int length, InetAddress address, int port) 参数一:封装要发出去的数据。 参数二:发送出去的数据大小(字节个数) 参数三:服务端的IP地址(找到服务端主机) 参数四:服务端程序的端口。 */ Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 一旦发现用户输入的exit命令,就退出客户端 if("exit".equals(msg)){ System.out.println("欢迎下次光临!退出成功!"); socket.close(); // 释放资源 break; // 跳出死循环 } byte[] bytes = msg.getBytes(); DatagramPacket packet = new DatagramPacket(bytes, bytes.length , InetAddress.getLocalHost(), 6666); // 3、开始正式发送这个数据包的数据出去了 socket.send(packet); } } }
4.2 服务端程序
/** * 目标:完成UDP通信快速入门-服务端反复的收 */ public class Server { public static void main(String[] args) throws Exception { System.out.println("----服务端启动----"); // 1、创建一个服务端对象(创建一个接韭菜的人) 注册端口 DatagramSocket socket = new DatagramSocket(6666); // 2、创建一个数据包对象,用于接收数据的(创建一个韭菜盘子) byte[] buffer = new byte[1024 * 64]; // 64KB. DatagramPacket packet = new DatagramPacket(buffer, buffer.length); while (true) { // 3、开始正式使用数据包来接收客户端发来的数据 socket.receive(packet); // 4、从字节数组中,把接收到的数据直接打印出来 // 接收多少就倒出多少 // 获取本次数据包接收了多少数据。 int len = packet.getLength(); String rs = new String(buffer, 0 , len); System.out.println(rs); System.out.println(packet.getAddress().getHostAddress()); System.out.println(packet.getPort()); System.out.println("--------------------------------------"); } } }
五、TCP通信(一发一收)
学习完UDP通信的代码编写之后,接下来我们学习TCP通信的代码如何编写。Java提供了一个java.net.Socket类来完成TCP通信。
我们先讲一下Socket完成TCP通信的流程,再讲代码怎么编写就很好理解了。如下图所示
- 当创建Socket对象时,就会在客户端和服务端创建一个数据通信的管道,在客户端和服务端两边都会有一个Socket对象来访问这个通信管道。
- 现在假设客户端要发送一个“在一起”给服务端,客户端这边先需要通过Socket对象获取到一个字节输出流,通过字节输出流写数据到服务端
- 然后服务端这边通过Socket对象可以获取字节输入流,通过字节输入流就可以读取客户端写过来的数据,并对数据进行处理。
- 服务端处理完数据之后,假设需要把“没感觉”发给客户端端,那么服务端这边再通过Socket获取到一个字节输出流,将数据写给客户端
- 客户端这边再获取输入流,通过字节输入流来读取服务端写过来的数据。
5.1 TCP客户端
下面我们写一个客户端,用来往服务端发数据。由于原始的字节流不是很好用,这里根据我的经验,我原始的OutputStream包装为DataOutputStream是比较好用的。
/** * 目标:完成TCP通信快速入门-客户端开发:实现1发1收。 */ public class Client { public static void main(String[] args) throws Exception { // 1、创建Socket对象,并同时请求与服务端程序的连接。 Socket socket = new Socket("127.0.0.1", 8888); // 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。 OutputStream os = socket.getOutputStream(); // 3、把低级的字节输出流包装成数据输出流 DataOutputStream dos = new DataOutputStream(os); // 4、开始写数据出去了 dos.writeUTF("在一起,好吗?"); dos.close(); socket.close(); // 释放连接资源 } }
5.2 TCP服务端
上面我们只是写了TCP客户端,还没有服务端,接下来我们把服务端写一下。这里的服务端用来接收客户端发过来的数据。
/** * 目标:完成TCP通信快速入门-服务端开发:实现1发1收。 */ public class Server { public static void main(String[] args) throws Exception { System.out.println("-----服务端启动成功-------"); // 1、创建ServerSocket的对象,同时为服务端注册端口。 ServerSocket serverSocket = new ServerSocket(8888); // 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求 Socket socket = serverSocket.accept(); // 3、从socket通信管道中得到一个字节输入流。 InputStream is = socket.getInputStream(); // 4、把原始的字节输入流包装成数据输入流 DataInputStream dis = new DataInputStream(is); // 5、使用数据输入流读取客户端发送过来的消息 String rs = dis.readUTF(); System.out.println(rs); // 其实我们也可以获取客户端的IP地址 System.out.println(socket.getRemoteSocketAddress()); dis.close(); socket.close(); } }
六、TCP通信(多发多收)
到目前为止,我们已经完成了客户端发送消息、服务端接收消息,但是客户端只能发一次,服务端只能接收一次。现在我想要客户端能过一直发消息,服务端能够一直接收消息。
下面我们把客户端代码改写一下,采用键盘录入的方式发消息,为了让客户端能够一直发,我们只需要将发送消息的代码套一层循环就可以了,当用户输入exit时,客户端退出循环并结束客户端。
6.1 TCP客户端
/** * 目标:完成TCP通信快速入门-客户端开发:实现客户端可以反复的发消息出去 */ public class Client { public static void main(String[] args) throws Exception { // 1、创建Socket对象,并同时请求与服务端程序的连接。 Socket socket = new Socket("127.0.0.1", 8888); // 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。 OutputStream os = socket.getOutputStream(); // 3、把低级的字节输出流包装成数据输出流 DataOutputStream dos = new DataOutputStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 一旦用户输入了exit,就退出客户端程序 if("exit".equals(msg)){ System.out.println("欢迎您下次光临!退出成功!"); dos.close(); socket.close(); break; } // 4、开始写数据出去了 dos.writeUTF(msg); dos.flush(); } } }
6.2 TCP服务端
为了让服务端能够一直接收客户端发过来的消息,服务端代码也得改写一下。我们只需要将读取数据的代码加一个循环就可以了。
但是需要我们注意的时,如果客户端Socket退出之后,就表示连接客户端与服务端的数据通道被关闭了,这时服务端就会出现异常。服务端可以通过出异常来判断客户端下线了,所以可以用try…catch把读取客户端数据的代码套一起来,catch捕获到异常后,打印客户端下线。
/** * 目标:完成TCP通信快速入门-服务端开发:实现服务端反复发消息 */ public class Server { public static void main(String[] args) throws Exception { System.out.println("-----服务端启动成功-------"); // 1、创建ServerSocket的对象,同时为服务端注册端口。 ServerSocket serverSocket = new ServerSocket(8888); // 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求 Socket socket = serverSocket.accept(); // 3、从socket通信管道中得到一个字节输入流。 InputStream is = socket.getInputStream(); // 4、把原始的字节输入流包装成数据输入流 DataInputStream dis = new DataInputStream(is); while (true) { try { // 5、使用数据输入流读取客户端发送过来的消息 String rs = dis.readUTF(); System.out.println(rs); } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "离线了!"); dis.close(); socket.close(); break; } } } }
七、TCP通信(多线程改进)
上一个案例中我们写的服务端程序只能和一个客户端通信,如果有多个客户端连接服务端,此时服务端是不支持的。
为了让服务端能够支持多个客户端通信,就需要用到多线程技术。具体的实现思路如下图所示:每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。按照这样的设计,服务端就可以支持多个客户端连接了。
按照上面的思路,改写服务端代码。
7.1 多线程改进
首先,我们需要写一个服务端的读取数据的线程类,代码如下
public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); DataInputStream dis = new DataInputStream(is); while (true){ try { String msg = dis.readUTF(); System.out.println(msg); } catch (Exception e) { System.out.println("有人下线了:" + socket.getRemoteSocketAddress()); dis.close(); socket.close(); break; } } } catch (Exception e) { e.printStackTrace(); } } }
接下来,再改写服务端的主程序代码,如下:
/** * 目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通信。 */ public class Server { public static void main(String[] args) throws Exception { System.out.println("-----服务端启动成功-------"); // 1、创建ServerSocket的对象,同时为服务端注册端口。 ServerSocket serverSocket = new ServerSocket(8888); while (true) { // 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求 Socket socket = serverSocket.accept(); System.out.println("有人上线了:" + socket.getRemoteSocketAddress()); // 3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。 new ServerReaderThread(socket).start(); } } }
7.2 案例拓展(群聊)
接着前面的案例,下面我们案例再次拓展一下,这个并不需要同学们必须掌握,主要是为了锻炼同学们的编程能力、和编程思维。
我们想把刚才的案例,改进成全能够实现群聊的效果,就是一个客户端发的消息,其他的每一个客户端都可以收到。
刚才我们写的多个客户端可以往服务端发现消息,但是客户端和客户端是不能直接通信的。想要试下全群聊的效果,我们还是必须要有服务端在中间做中转。 具体实现方案如下图所示:
我们可以在服务端创建一个存储Socket的集合,每当一个客户端连接服务端,就可以把客户端Socket存储起来;当一个客户端给服务端发消息时,再遍历集合通过每个Socket将消息再转发给其他客户端。
最新Java基础系列课程--Day15-网络编程(三)https://developer.aliyun.com/article/1423555