网络编程综合练习
一、聊天室(UDP)
需求
按照下面的要求实现程序:
- UDP发送数据:数据来自于键盘录入,直接输入的数据是886,发送数据结束
- UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
实现
-
发送端
package com.app.demo42_udp_test.test1; import java.io.IOException; import java.net.*; import java.util.Scanner; public class SendMessageDemo { public static void main(String[] args) throws IOException { /* 网络编程综合练习 一、聊天室 需求: UDP发送数据:数据来自于键盘录入,直接输入的数据是886,发送数据结束 */ // 1.创建DatagramSocket的对象 DatagramSocket ds = new DatagramSocket(); // 2.打包数据 Scanner sc = new Scanner(System.in); while (true) { System.out.println("请您输入想说的话:"); String str = sc.nextLine(); // 如果输入的是:886,则跳出循环(客户下线) if ("886".equals(str)) { break; } // 将输入的字符串转换成字节数组 byte[] bytes = str.getBytes(); // 目标主机 InetAddress address = InetAddress.getByName("127.0.0.1"); // 目标主机的接收端口 int port = 2023; // 打包 DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port); // 3.发送数据 ds.send(dp); } // 4.释放资源 ds.close(); } }
-
接收端
package com.app.demo42_udp_test.test1; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class ReceiveMessageDemo { public static void main(String[] args) throws IOException { /* 网络编程综合练习 一、聊天室 需求: UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收 */ // 1.创建DatagramSocket的对象,并指定接收端口 DatagramSocket ds = new DatagramSocket(2023); // 2.接收数据包 byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length); // 因为接收端不知道发送端什么时候停止发送,故采用死循环接收 while (true) { ds.receive(dp); // 3.解析数据包 byte[] data = dp.getData(); // 获取数据包 int len = dp.getLength(); // 获取数据包大小 // 获取数据包来源主机的ip String ip = dp.getAddress().getHostAddress(); // 获取数据包来源主机的主机名 String name = dp.getAddress().getHostName(); // 4.打印输出数据 System.out.println("ip为:" + ip + ", 主机名为:" + name + "的人发送了数据:" + new String(data, 0, len)); } } }
-
运行结果
二、多发多收(TCP)
需求
- 客户端:多次发送数据
- 服务端:多次接收数据,并打印输出
实现
-
客户端
package com.app.demo43_tcp_test.test1; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner; /* 客户端:多发 */ public class Client { public static void main(String[] args) throws IOException { /* 网络编程综合练习:多发多收(TCP) 需求: 客户端:多次发送数据 */ //1.创建Socket对象并连接服务端 Socket socket = new Socket("127.0.0.1", 10001); //2.发送数据 Scanner sc = new Scanner(System.in); //获取输出流对象 OutputStream os = socket.getOutputStream(); // 定义死循环,实现多发 while (true) { System.out.println("请输入您要发送的信息:"); String str = sc.nextLine(); // 如果输入的是:886,则停止发送 if ("886".equals(str)){ break; } //写出 os.write(str.getBytes()); } //3.释放资源(只需要关闭连接管道,流也会关闭) socket.close(); } }
-
服务端
package com.app.demo43_tcp_test.test1; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /* 服务端:多收 */ public class Server { public static void main(String[] args) throws IOException { /* 网络编程综合练习:多发多收(TCP) 需求: 服务端:多次接收数据,并打印输出 */ //1.创建ServerSocket对象绑定10001端口 ServerSocket ss = new ServerSocket(10001); //2.等待客户端来连接 Socket socket = ss.accept(); //3.接收数据并打印输出 //为了防止读取到的数据会出现中文乱码的问题,需要使用转换流 InputStreamReader isr = new InputStreamReader(socket.getInputStream()); int b; while ((b = isr.read()) != -1) { System.out.print((char)b); } //4.释放资源(只需要关闭连接管道,流也会关闭) socket.close(); // 关闭服务器 ss.close(); } }
-
运行结果
三、接收和反馈
需求
- 客户端:发送一条数据,接收服务端反馈的消息并打印
- 服务端:接收数据并打印,再给客户端反馈消息
实现
-
客户端
package com.app.demo43_tcp_test.test2; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; /* 客户端 */ public class Client { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 客户端:发送一条数据,接收服务端反馈的消息并打印 */ //1.创建Socket对象并连接服务端 Socket socket = new Socket("127.0.0.1", 10086); //2.发送数据 String str = "我喜欢你!"; OutputStream os = socket.getOutputStream(); os.write(str.getBytes()); //写出一个结束标记(重点) socket.shutdownOutput(); //3.接收服务端反馈的消息并打印输出 InputStream is = socket.getInputStream(); //为了防止读取到中文的时候会出现乱码问题 //因此需要使用转换流 InputStreamReader isr = new InputStreamReader(is); int b; while ((b = isr.read()) != -1) { System.out.print((char) b); } //4.释放资源 socket.close(); } }
-
服务端
package com.app.demo43_tcp_test.test2; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /* 服务端 */ public class Server { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 服务端:接收数据并打印,再给客户端反馈消息 */ //1.创建ServerSocket对象并绑定10086端口 ServerSocket ss = new ServerSocket(10086); //2.等待客户端连接 Socket socket = ss.accept(); //3.接收数据 InputStream is = socket.getInputStream(); //为了防止读取到中文的时候会出现乱码问题 //因此需要使用转换流 InputStreamReader isr = new InputStreamReader(is); int b; /* 细节: read()方法会从连接管道中读取数据 但是,需要有一个结束标记,此处的循环才会停止 否则,程序会一直停在read()方法这里,等待读取下面的数据 */ while ((b = isr.read()) != -1) { System.out.print((char) b); } //4.反馈消息给客户端(回写数据) String str = "我不喜欢你!!"; OutputStream os = socket.getOutputStream(); os.write(str.getBytes()); //5.释放资源 socket.close(); //关闭服务器 ss.close(); } }
-
运行结果
四、上传文件
需求
- 客户端:将本地文件上传到服务器。接收服务器的反馈
- 服务端:接收客户端上传的文件,上传完毕后给出反馈
实现
-
准备好要上传的文件
-
客户端
package com.app.demo43_tcp_test.test3; import java.io.*; import java.net.Socket; // 客户端 public class Client { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 客户端:将本地文件上传到服务器。接收服务器的反馈 */ //1.创建Socket对象连接服务端 Socket socket = new Socket("127.0.0.1", 10000); //2.读取本地文件中的数据,并写到服务器当中 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("comprehensive_exercises\\clientdir\\all.jpg")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] bytes = new byte[1024]; int len; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } //3.往服务器写出结束标记 socket.shutdownOutput(); //4.接收服务器的回写数据(反馈信息) BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); System.out.println(br.readLine()); //5.释放资源 socket.close(); } }
-
服务端
package com.app.demo43_tcp_test.test3; import java.io.*; import java.net.ServerSocket; import java.net.Socket; // 服务端 public class Server { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 服务端:接收客户端上传的文件,上传完毕后给出反馈 */ //1.创建ServerSocket对象绑定10000端口 ServerSocket ss = new ServerSocket(10000); //2.等待客户端来连接 Socket socket = ss.accept(); //3.读取数据并保存到本地文件中 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("comprehensive_exercises\\serverdir\\all.jpg")); int len; byte[] bytes = new byte[1024]; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } //4.回写数据(给客户端反馈信息) BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("上传成功!"); // 写一句话 bw.newLine(); // 写一个换行 bw.flush(); // 刷新数据(包含关闭流) //5.释放资源 socket.close(); ss.close(); } }
-
运行结果
五、上传文件(文件名重复问题)
需求
- 解决上一个业务文件名重复的问题
- 因为不管你重复运行多少次服务端和客户端,在serverdir文件夹中依然是一个文件,因为文件名重名会覆盖
实现
-
客户端(不需要改动)
package com.app.demo43_tcp_test.test3; import java.io.*; import java.net.Socket; // 客户端 public class Client { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 客户端:将本地文件上传到服务器。接收服务器的反馈 */ //1.创建Socket对象连接服务端 Socket socket = new Socket("127.0.0.1", 10000); //2.读取本地文件中的数据,并写到服务器当中 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("comprehensive_exercises\\clientdir\\all.jpg")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] bytes = new byte[1024]; int len; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } //3.往服务器写出结束标记 socket.shutdownOutput(); //4.接收服务器的回写数据(反馈信息) BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); System.out.println(br.readLine()); //5.释放资源 socket.close(); } }
-
服务端
package com.app.demo43_tcp_test.test3; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.UUID; // 服务端 public class Server { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 服务端:接收客户端上传的文件,上传完毕后给出反馈 */ //1.创建ServerSocket对象绑定10000端口 ServerSocket ss = new ServerSocket(10000); //2.等待客户端来连接 Socket socket = ss.accept(); //3.读取数据并保存到本地文件中 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); // 获取一个随机且唯一的文件名 // randomUUID(): 获取随机且唯一的文件名 // toString(): 将文件名转换成字符串 // replace("-", ""): 将文件名出现的所有 "-"(横杆) 都替换成 ""(0长度的字符串) String name = UUID.randomUUID().toString().replace("-", ""); // BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("comprehensive_exercises\\serverdir\\all.jpg")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("comprehensive_exercises\\serverdir\\" + name + ".jpg")); int len; byte[] bytes = new byte[1024]; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } //4.回写数据(给客户端反馈信息) BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("上传成功!"); // 写一句话 bw.newLine(); // 写一个换行 bw.flush(); // 刷新数据(包含关闭流) //5.释放资源 socket.close(); ss.close(); } }
-
运行结果
六、上传文件(多线程)
需求
- 服务器不停止,能接收很多用户上传的图片。
- 提示:可以用循环或者多线程(循环不合理,最优解法是用多线程)
实现
-
准备工作
-
客户端(不需要改动)
package com.app.demo43_tcp_test.test4; import java.io.*; import java.net.Socket; // 客户端 public class Client { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 客户端:将本地文件上传到服务器。接收服务器的反馈 */ //1.创建Socket对象连接服务端 Socket socket = new Socket("127.0.0.1", 10000); //2.读取本地文件中的数据,并写到服务器当中 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("comprehensive_exercises\\clientdir\\all.jpg")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] bytes = new byte[1024]; int len; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } //3.往服务器写出结束标记 socket.shutdownOutput(); //4.接收服务器的回写数据(反馈信息) BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); System.out.println(br.readLine()); //5.释放资源 socket.close(); } }
-
自定义服务端接收文件的线程类
package com.app.demo43_tcp_test.test4; import java.io.*; import java.net.Socket; import java.util.UUID; /* 自定义服务端接收客户端上传文件的线程类 第二种方式实现多线程 */ public class ServerRunnable implements Runnable{ Socket socket; public ServerRunnable(Socket socket) { this.socket = socket; } @Override public void run() { try { //3.读取数据并保存到本地文件中 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); // 获取一个随机且唯一的文件名 // randomUUID(): 获取随机且唯一的文件名 // toString(): 将文件名转换成字符串 // replace("-", ""): 将文件名出现的所有 "-"(横杆) 都替换成 ""(0长度的字符串) String name = UUID.randomUUID().toString().replace("-", ""); // BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("comprehensive_exercises\\serverdir\\all.jpg")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("comprehensive_exercises\\serverdir\\" + name + ".jpg")); int len; byte[] bytes = new byte[1024]; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } //4.回写数据(给客户端反馈信息) BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("上传成功!"); // 写一句话 bw.newLine(); // 写一个换行 bw.flush(); // 刷新数据(包含关闭流) } catch (IOException e) { throw new RuntimeException(e); } finally { //5.释放资源 if (socket != null) { try { socket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } }
-
服务端
package com.app.demo43_tcp_test.test4; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.UUID; // 服务端 public class Server { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 服务端:接收客户端上传的文件,上传完毕后给出反馈 */ //1.创建ServerSocket对象绑定10000端口 ServerSocket ss = new ServerSocket(10000); //2.使用循环+多线程来将每个客户端要上传的文件上传到服务端 while (true) { //3.等待客户端来连接 Socket socket = ss.accept(); // 开启一条线程:一个用户就对应服务端的一条线程 new Thread(new ServerRunnable(socket)).start(); } } }
-
我总共运行了服务端一次,客户端5次
七、上传文件(线程池优化)
需求
- 频繁创建线程并销毁实在是浪费系统资源,所以需要用线程池优化。
实现
-
准备工作
-
客户端(不改动)
package com.app.demo43_tcp_test.test5; import java.io.*; import java.net.Socket; // 客户端 public class Client { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 客户端:将本地文件上传到服务器。接收服务器的反馈 */ //1.创建Socket对象连接服务端 Socket socket = new Socket("127.0.0.1", 10000); //2.读取本地文件中的数据,并写到服务器当中 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("comprehensive_exercises\\clientdir\\all.jpg")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] bytes = new byte[1024]; int len; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } //3.往服务器写出结束标记 socket.shutdownOutput(); //4.接收服务器的回写数据(反馈信息) BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); System.out.println(br.readLine()); //5.释放资源 socket.close(); } }
-
自定义服务端接收文件的线程类(不改动)
package com.app.demo43_tcp_test.test5; import java.io.*; import java.net.Socket; import java.util.UUID; /* 自定义服务端接收客户端上传文件的线程类 第二种方式实现多线程 */ public class ServerRunnable implements Runnable{ Socket socket; public ServerRunnable(Socket socket) { this.socket = socket; } @Override public void run() { try { //3.读取数据并保存到本地文件中 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); // 获取一个随机且唯一的文件名 // randomUUID(): 获取随机且唯一的文件名 // toString(): 将文件名转换成字符串 // replace("-", ""): 将文件名出现的所有 "-"(横杆) 都替换成 ""(0长度的字符串) String name = UUID.randomUUID().toString().replace("-", ""); // BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("comprehensive_exercises\\serverdir\\all.jpg")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("comprehensive_exercises\\serverdir\\" + name + ".jpg")); int len; byte[] bytes = new byte[1024]; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } //4.回写数据(给客户端反馈信息) BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("上传成功!"); // 写一句话 bw.newLine(); // 写一个换行 bw.flush(); // 刷新数据(包含关闭流) } catch (IOException e) { throw new RuntimeException(e); } finally { //5.释放资源 if (socket != null) { try { socket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } }
-
服务端
package com.app.demo43_tcp_test.test5; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; // 服务端 public class Server { public static void main(String[] args) throws IOException { /* 网络编程综合练习:接收和反馈 需求: 服务端:接收客户端上传的文件,上传完毕后给出反馈 */ //1.创建ServerSocket对象绑定10000端口 ServerSocket ss = new ServerSocket(10000); //2.创建线程池对象 ThreadPoolExecutor pool = new ThreadPoolExecutor( 3,//核心线程数 16,//线程池总大小 60,//空闲时间 TimeUnit.SECONDS,//空闲时间(单位) new ArrayBlockingQueue<>(2),//队列 Executors.defaultThreadFactory(),//线程工厂,让线程池如何创建线程对象 new ThreadPoolExecutor.AbortPolicy()//阻塞队列 ); //2.使用循环+多线程来将每个客户端要上传的文件上传到服务端 while (true) { //3.等待客户端来连接 Socket socket = ss.accept(); // 开启一条线程:一个用户就对应服务端的一条线程 // new Thread(new ServerRunnable(socket)).start(); pool.submit(new ServerRunnable(socket)); } } }
-
运行结果:我总共运行1次服务端,3次客户端
八、BS(接收浏览器的消息并打印)
需求
核心思想:在BS架构中,客户端就是浏览器,服务端就是接收浏览器传输过来的数据;
当我们要回写数据的时候,就是把数据回写给浏览器。
- 客户端:是浏览器,所以不需要代码实现
- 服务端:接收数据并打印
实现
-
准备工作:先打开一个浏览器
-
客户端
package com.app.demo43_tcp_test.test6; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner; /* 客户端:多发 */ public class Client { public static void main(String[] args) throws IOException { /* 网络编程综合练习:多发多收(TCP) 需求: 客户端:多次发送数据 */ //1.创建Socket对象并连接服务端 Socket socket = new Socket("127.0.0.1", 10001); //2.发送数据 Scanner sc = new Scanner(System.in); //获取输出流对象 OutputStream os = socket.getOutputStream(); // 定义死循环,实现多发 while (true) { System.out.println("请输入您要发送的信息:"); String str = sc.nextLine(); // 如果输入的是:886,则停止发送 if ("886".equals(str)){ break; } //写出 os.write(str.getBytes()); } //3.释放资源(只需要关闭连接管道,流也会关闭) socket.close(); } }
-
服务端
package com.app.demo43_tcp_test.test6; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /* 服务端:多收 */ public class Server { public static void main(String[] args) throws IOException { /* 网络编程综合练习:多发多收(TCP) 需求: 服务端:多次接收数据,并打印输出 */ //1.创建ServerSocket对象绑定10001端口 ServerSocket ss = new ServerSocket(10001); //2.等待客户端来连接 Socket socket = ss.accept(); //3.接收数据并打印输出 //为了防止读取到的数据会出现中文乱码的问题,需要使用转换流 InputStreamReader isr = new InputStreamReader(socket.getInputStream()); int b; while ((b = isr.read()) != -1) { System.out.print((char)b); } //4.释放资源(只需要关闭连接管道,流也会关闭) socket.close(); // 关闭服务器 ss.close(); } }
-
运行服务端后
打开浏览器输入:127.0.0.1:端口号,然后回车
然后就可以在控制台看到