服务器向客户端回写数据
服务端实现:
publicclassServerTCP { publicstaticvoidmain(String[] args) throwsIOException { System.out.println("服务端启动 , 等待连接 .... "); // 1.创建 ServerSocket对象,绑定端口,开始等待连接ServerSocketss=newServerSocket(6666); // 2.接收连接 accept 方法, 返回 socket 对象.Socketserver=ss.accept(); // 3.通过socket 获取输入流InputStreamis=server.getInputStream(); // 4.一次性读取数据// 4.1 创建字节数组byte[] b=newbyte[1024]; // 4.2 据读取到字节数组中.intlen=is.read(b);// 4.3 解析数组,打印字符串信息Stringmsg=newString(b, 0, len); System.out.println(msg); // =================回写数据=======================// 5. 通过 socket 获取输出流OutputStreamout=server.getOutputStream(); // 6. 回写数据out.write("我很好,谢谢你".getBytes()); // 7.关闭资源.out.close(); is.close(); server.close(); } }
客户端实现:
publicclassClientTCP { publicstaticvoidmain(String[] args) throwsException { System.out.println("客户端 发送数据"); // 1.创建 Socket ( ip , port ) , 确定连接到哪里.Socketclient=newSocket("localhost", 6666); // 2.通过Scoket,获取输出流对象 OutputStreamos=client.getOutputStream(); // 3.写出数据.os.write("你好么? tcp ,我来了".getBytes()); // ==============解析回写=========================// 4. 通过Scoket,获取 输入流对象InputStreamin=client.getInputStream(); // 5. 读取数据数据byte[] b=newbyte[100]; intlen=in.read(b); System.out.println(newString(b, 0, len)); // 6. 关闭资源 .in.close(); os.close(); client.close(); } }
第三章 综合案例
3.1 文件上传案例
文件上传分析图解
- 【客户端】输入流,从硬盘读取文件数据到程序中。
- 【客户端】输出流,写出文件数据到服务端。
- 【服务端】输入流,读取文件数据到服务端程序。
- 【服务端】输出流,写出文件数据到服务器硬盘中。
基本实现
服务端实现
publicclassFileUpload_Server { publicstaticvoidmain(String[] args) throwsIOException { System.out.println("服务器 启动..... "); // 1. 创建服务端ServerSocketServerSocketserverSocket=newServerSocket(6666); // 2. 建立连接 Socketaccept=serverSocket.accept(); // 3. 创建流对象// 3.1 获取输入流,读取文件数据BufferedInputStreambis=newBufferedInputStream(accept.getInputStream()); // 3.2 创建输出流,保存到本地 .BufferedOutputStreambos=newBufferedOutputStream(newFileOutputStream("copy.jpg")); // 4. 读写数据byte[] b=newbyte[1024*8]; intlen; while ((len=bis.read(b)) !=-1) { bos.write(b, 0, len); } //5. 关闭 资源bos.close(); bis.close(); accept.close(); System.out.println("文件上传已保存"); } }
客户端实现:
publicclassFileUPload_Client { publicstaticvoidmain(String[] args) throwsIOException { // 1.创建流对象// 1.1 创建输入流,读取本地文件 BufferedInputStreambis=newBufferedInputStream(newFileInputStream("test.jpg")); // 1.2 创建输出流,写到服务端 Socketsocket=newSocket("localhost", 6666); BufferedOutputStreambos=newBufferedOutputStream(socket.getOutputStream()); //2.写出数据. byte[] b=newbyte[1024*8 ]; intlen ; while (( len=bis.read(b))!=-1) { bos.write(b, 0, len); bos.flush(); } System.out.println("文件发送完毕"); // 3.释放资源bos.close(); socket.close(); bis.close(); System.out.println("文件上传完毕 "); } }
文件上传优化分析
- 文件名称写死的问题
服务端,保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,建议使用系统时间优化,保证文件名称唯一,代码如下:
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名称
BufferedOutputStream bos = new BufferedOutputStream(fis);
循环接收的问题
服务端,指保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断的接收不同用户的文件,代码如下:
// 每次接收新的连接,创建一个Socketwhile(true){ Socketaccept=serverSocket.accept(); ...... }
- 效率问题
服务端,在接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术优化,代码如下:
while(true){ Socketaccept=serverSocket.accept(); // accept 交给子线程处理.newThread(() -> { ...... InputStreambis=accept.getInputStream(); ...... }).start(); } 优化实现publicclassFileUpload_Server { publicstaticvoidmain(String[] args) throwsIOException { System.out.println("服务器 启动..... "); // 1. 创建服务端ServerSocketServerSocketserverSocket=newServerSocket(6666); // 2. 循环接收,建立连接while (true) { Socketaccept=serverSocket.accept(); /* 3. socket对象交给子线程处理,进行读写操作Runnable接口中,只有一个run方法,使用lambda表达式简化格式*/newThread(() -> { try ( //3.1 获取输入流对象BufferedInputStreambis=newBufferedInputStream(accept.getInputStream()); //3.2 创建输出流对象, 保存到本地 .FileOutputStreamfis=newFileOutputStream(System.currentTimeMillis() +".jpg"); BufferedOutputStreambos=newBufferedOutputStream(fis);) { // 3.3 读写数据byte[] b=newbyte[1024*8]; intlen; while ((len=bis.read(b)) !=-1) { bos.write(b, 0, len); } //4. 关闭 资源bos.close(); bis.close(); accept.close(); System.out.println("文件上传已保存"); } catch (IOExceptione) { e.printStackTrace(); } }).start(); } } }
信息回写分析图解
前四步与基本文件上传一致.
- 【服务端】获取输出流,回写数据。
- 【客户端】获取输入流,解析回写数据。
回写实现publicclassFileUpload_Server { publicstaticvoidmain(String[] args) throwsIOException { System.out.println("服务器 启动..... "); // 1. 创建服务端ServerSocketServerSocketserverSocket=newServerSocket(6666); // 2. 循环接收,建立连接while (true) { Socketaccept=serverSocket.accept(); /*3. socket对象交给子线程处理,进行读写操作Runnable接口中,只有一个run方法,使用lambda表达式简化格式*/newThread(() -> { try ( //3.1 获取输入流对象BufferedInputStreambis=newBufferedInputStream(accept.getInputStream()); //3.2 创建输出流对象, 保存到本地 .FileOutputStreamfis=newFileOutputStream(System.currentTimeMillis() +".jpg"); BufferedOutputStreambos=newBufferedOutputStream(fis); ) { // 3.3 读写数据byte[] b=newbyte[1024*8]; intlen; while ((len=bis.read(b)) !=-1) { bos.write(b, 0, len); } // 4.=======信息回写===========================System.out.println("back ........"); OutputStreamout=accept.getOutputStream(); out.write("上传成功".getBytes()); out.close(); //================================//5. 关闭 资源bos.close(); bis.close(); accept.close(); System.out.println("文件上传已保存"); } catch (IOExceptione) { e.printStackTrace(); } }).start(); } } }
客户端实现:
publicclassFileUpload_Client { publicstaticvoidmain(String[] args) throwsIOException { // 1.创建流对象// 1.1 创建输入流,读取本地文件BufferedInputStreambis=newBufferedInputStream(newFileInputStream("test.jpg")); // 1.2 创建输出流,写到服务端Socketsocket=newSocket("localhost", 6666); BufferedOutputStreambos=newBufferedOutputStream(socket.getOutputStream()); //2.写出数据.byte[] b=newbyte[1024*8 ]; intlen ; while (( len=bis.read(b))!=-1) { bos.write(b, 0, len); } // 关闭输出流,通知服务端,写出数据完毕socket.shutdownOutput(); System.out.println("文件发送完毕"); // 3. =====解析回写============InputStreamin=socket.getInputStream(); byte[] back=newbyte[20]; in.read(back); System.out.println(newString(back)); in.close(); // ============================// 4.释放资源socket.close(); bis.close(); } }
3.2 模拟B\S服务器(扩展知识点)
模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果。
案例分析
- 准备页面数据,web文件夹。
我们模拟服务器端,ServerSocket类监听端口,使用浏览器访问
ublicpublicstaticvoidmain(String[] args) throwsIOException { ServerSocketserver=newServerSocket(8000); Socketsocket=server.accept(); InputStreamin=socket.getInputStream(); byte[] bytes=newbyte[1024]; intlen=in.read(bytes); System.out.println(newString(bytes,0,len)); socket.close(); server.close(); }
服务器程序中字节输入流可以读取到浏览器发来的请求信息
GET/web/index.html HTTP/1.1是浏览器的请求消息。/web/index.html为浏览器想要请求的服务器端的资源,使用字符串切割方式获取到请求的资源。
//转换流,读取浏览器请求第一行BufferedReaderreadWb=newBufferedReader(newInputStreamReader(socket.getInputStream())); Stringrequst=readWb.readLine(); //取出请求资源的路径String[] strArr=requst.split(" "); //去掉web前面的/Stringpath=strArr[1].substring(1); System.out.println(path);
案例实现
服务端实现:
publicclassSerDemo { publicstaticvoidmain(String[] args) throwsIOException { System.out.println("服务端 启动 , 等待连接 .... "); // 创建ServerSocket 对象ServerSocketserver=newServerSocket(8888); Socketsocket=server.accept(); // 转换流读取浏览器的请求消息BufferedReaderreadWb=newBufferedReader(newInputStreamReader(socket.getInputStream())); Stringrequst=readWb.readLine(); // 取出请求资源的路径String[] strArr=requst.split(" "); // 去掉web前面的/Stringpath=strArr[1].substring(1); // 读取客户端请求的资源文件FileInputStreamfis=newFileInputStream(path); byte[] bytes=newbyte[1024]; intlen=0 ; // 字节输出流,将文件写会客户端OutputStreamout=socket.getOutputStream(); // 写入HTTP协议响应头,固定写法out.write("HTTP/1.1 200 OK\r\n".getBytes()); out.write("Content-Type:text/html\r\n".getBytes()); // 必须要写入空行,否则浏览器不解析out.write("\r\n".getBytes()); while((len=fis.read(bytes))!=-1){ out.write(bytes,0,len); } fis.close(); out.close(); readWb.close(); socket.close(); server.close(); } }
访问效果
- 火狐
发现浏览器中出现很多的叉子,说明浏览器没有读取到图片信息导致。
浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术。
publicclassServerDemo { publicstaticvoidmain(String[] args) throwsIOException { ServerSocketserver=newServerSocket(8888); while(true){ Socketsocket=server.accept(); newThread(newWeb(socket)).start(); } } staticclassWebimplementsRunnable{ privateSocketsocket; publicWeb(Socketsocket){ this.socket=socket; } publicvoidrun() { try{ //转换流,读取浏览器请求第一行BufferedReaderreadWb=newBufferedReader(newInputStreamReader(socket.getInputStream())); Stringrequst=readWb.readLine(); //取出请求资源的路径String[] strArr=requst.split(" "); System.out.println(Arrays.toString(strArr)); Stringpath=strArr[1].substring(1); System.out.println(path); FileInputStreamfis=newFileInputStream(path); System.out.println(fis); byte[] bytes=newbyte[1024]; intlen=0 ; //向浏览器 回写数据OutputStreamout=socket.getOutputStream(); out.write("HTTP/1.1 200 OK\r\n".getBytes()); out.write("Content-Type:text/html\r\n".getBytes()); out.write("\r\n".getBytes()); while((len=fis.read(bytes))!=-1){ out.write(bytes,0,len); } fis.close(); out.close(); readWb.close(); socket.close(); }catch(Exceptionex){ } } }