【Java SE进阶】Day11 网络编程、TCP应用程序

一、网络编程入门

1、软件架构

  • C/S:QQ、迅雷
  • B/S
  • 共同点:都离不开网络的支持
  • 网络编程:在一定的协议下,实现两台计算机通信

2、网络通信协议

  • 通信协议:需遵守的规则,只有遵守才能通信
    • 主要包括:传输格式、传输速率、传输步骤
  • TCP/IP协议: 
    • 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol)
    • 是Internet最基本、最广泛的协议
    • 内部包含了一系列处理数据通信的协议,并采用四层分层模型(下层为上层提供服务) 

 

 

  •  核心层:网络层,将数据分组,并将分组后的传输到计算机网络中

3、协议分类

  • UDP:用户数据报协议(User Datagram Protocol)
    • 无需连接,数据直接发送,传输快,不可靠,数据被限制在64kb内
    • 例子:QQ聊天、在线视频会议
  • TCP:传输控制协议 (Transmission Control Protocol)
    • 使用三次握手建立双端连接,可靠无差错的数据传输,数据安全
    • 例子:文件传输,网页浏览、看视频

 

 

 4、网络编程三要素

  • 组成:协议、IP地址、端口号(标识唯一设备)
  • IP地址:
    • IPV4:32位二进制,地址资源有限
    • IPV6:128位,8组16进制
    • 常用命令:ipconfig,特殊ip
  • 端口号(标识设备的多个进程)
    • 利用协议 + IP地址 + 端口号三元组,与其他进程交互
    • 0-65536,普通程序使用1024以上的
    • 常用端口号:
      • http/Nginx:80
      • https:443
      • Tomcat:8080
      • Oracle:1521
      • Redis:6379

二、TCP应用程序

1、概述

  • TCP通信实现两端(客户端(Client)与服务端(Server))的数据交互
  • 两个套接字类:Socket发请求/ServerSocket(构造传递端口)响应请求
  • 步骤:
    • 先启动服务器端,经过三次握手
    • 客户端请求服务器端
    • 建立连接,通过IO对象(字节流对象)通信
    • 服务器使用客户端的流与客户端发送数据和回写数据
  • 说明:
    • 与多个服务器端交互,可以使用accept获取指定对象:Socket s1=server.accept()

2、代码实现

  • 服务器端代码
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.创建服务器ServerSocket对象和系统要指定的端口号
        ServerSocket server=new ServerSocket(8888);
        //2.使用SeverSocket对象中的方法accept,获取到请求的客户端对象Socket
        Socket socket=server.accept();
        //3.使用Socket对象中的方法getInputStream读取网络字节输入流InputStream对象
        InputStream is=socket.getInputStream();
        //4.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
        byte[] bytes=new byte[1024];
        //读取一次就够了
        int len=is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //5.使用Socket对象中的方法getOutputStream()读取网络字节输出流OutputStream对象
        OutputStream os=socket.getOutputStream();
        //6.使用网络字节输出流OutputStream对象中的方法write,往客户端回写数据
        os.write("收到,谢谢".getBytes());
        //6.释放资源(socket)
        socket.close();
        server.close();
    }
}
  • 客户端代码
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1.创建客户端对象,构造方法绑定服务器端
        Socket socket=new Socket("127.0.0.1",8888);
        //只有客户端,抛出异常java.net.ConnectException
        //2.使用Socket对象中的方法getOutputStream()读取网络字节输出流OutputStream对象
        OutputStream os=socket.getOutputStream();
        //3.使用网络字节输出流OutputStream对象中的方法write,往服务器发送数据
        os.write("你好,服务器".getBytes());
        //4.使用Socket对象中的方法getInputStream读取网络字节输入流InputStream对象
        InputStream is=socket.getInputStream();
        //5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
        byte[] bytes=new byte[1024];
        //读取一次就够了
        int len=is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //6.释放资源(socket)
        socket.close();
    }
}

三、综合案例:文件上传下载 

1、原理 

 

 

  •  注意:
    • 与硬盘读写,自己创建字节流对象
    • C/S读写,使用socket创建字节流对象
    • IO流对象使用Socket套接字

2、客户端

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定读取的数据源【从指定路径中读出文件】
        FileInputStream fis=new FileInputStream("c:\\1.jpg");
        //2、创建一个客户端Socket对象,构造方法中绑定服务器的ip地址和端口号【io对象用于传送数据和接收数据】
        Socket socket=new Socket("127.0.0.1",8888);
        //3、使用Socket中的方法getOutputStream获取网络字节输出流对象OutputStream对象【获得网络输出流对象,上传】
        OutputStream os=socket.getOutputStream();
        //4、使用本地的字节输入流对象FileInputStream对象中的方法read,读取本地的文件【读取指定路径文件的内容】
        int len=0;
        byte[] bytes=new byte[1024];
        //循环读取文件
        while((len=fis.read(bytes))!=-1){
            //5、使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
            os.write(bytes,0,len);
        }
        //6、使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象【获得服务器端发送的数据】
        InputStream is=socket.getInputStream();
        //7、使用网络字节输入流InputStream对象中的read方法读取服务器回写的数据【读取并输出从服务器返回的数据】
        while((len=is.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }
        //8、释放资源(FileInputstream,Scoket)
        fis.close();
        socket.close();
    }
}

3、服务器端(先运行) 

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server=new ServerSocket(8888);
        //2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
        Socket socket=server.accept();
        //3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is=socket.getInputStream();
        //4、判断d:\\upload文件夹是否存在,不存在则创建
        File file=new File("d:\\upload");
        if (!file.exists()){
            file.mkdirs();
        }
        //5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream  fos=new FileOutputStream(file+"\\1.jpg");//不加\\文件会上传至D盘根目录,作为upload1.jpg文件
        //6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
        int len=0;
        byte[] bytes=new byte[1024];
        while((len=is.read(bytes))!=-1){
            //7、使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
            fos.write(bytes,0,len);
        }
        //8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
        //只调用一次,不创建对象
        //9、使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
        socket.getOutputStream().write("上传成功".getBytes());
        //10、释放资源(FileOutputStream,Socket,ServerSocket)
        socket.close();
        server.close();

    }
}

4、文件上传案例阻塞问题

从此输入流中读取一个数据字节。如果没有输入可用,则此方法将阻塞。
结束标志:读取到-1结束,不会读取到-1,也不会把结束标志写入服务器
服务器读取不到,就会进入阻塞状态,一直死循环等待结束标记

 

 

  • 解决:上传完文件,给服务器一个结束标记 
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定读取的数据源【从指定路径中读出文件】
        FileInputStream fis=new FileInputStream("c:\\1.jpg");
        //2、创建一个客户端Socket对象,构造方法中绑定服务器的ip地址和端口号【io对象用于传送数据和接收数据】
        Socket socket=new Socket("127.0.0.1",8888);
        //3、使用Socket中的方法getOutputStream获取网络字节输出流对象OutputStream对象【获得网络输出流对象,上传】
        OutputStream os=socket.getOutputStream();
        //4、使用本地的字节输入流对象FileInputStream对象中的方法read,读取本地的文件【读取指定路径文件的内容】
        int len=0;
        byte[] bytes=new byte[1024];
        //循环读取文件
        while((len=fis.read(bytes))!=-1){
            //5、使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
            os.write(bytes,0,len);
        }
        //6、使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象【获得服务器端发送的数据】
         /*
            写结束标记
            net中的方法 java.net.Socket
            void shutdownOutput()  禁用此套接字的输出流。
            禁用此套接字的输出流。对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列。
        * */
        socket.shutdownOutput();//告诉服务器,输出流已经结束
        InputStream is=socket.getInputStream();
        System.out.println("33333333333333333333333");

        //7、使用网络字节输入流InputStream对象中的read方法读取服务器回写的数据【读取并输出从服务器返回的数据】
        while((len=is.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }
        System.out.println("44444444444444444444444");
        //8、释放资源(FileInputstream,Scoket)
        fis.close();
        socket.close();
    }
}

5、文件上传案例优化

  • 文件命名:新建命名规则
  • 循环接收:accept方法放到死循环中,同时不关闭服务器端ServerSocket对象
  • 多线程提高效率:上传文件的操作放入run中,每上传一个文件,开启一个线程
  • 服务器端程序
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1、创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server=new ServerSocket(8888);
        //2、使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
        /*
            让服务器一直处于监听状态(死循环accept方法)
            有一个客户端上传文件,就保存一个文件
        * */
        while (true) {
            Socket socket=server.accept();
            /*
                使用多线程提高程序的效率
                只要有一个客户端上传文件,就开启一个线程,完成文件的上传
            * */
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //开启一个线程,完成文件的上传
                    try {
                        //3、使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
                        InputStream is = socket.getInputStream();
                        //4、判断d:\\upload文件夹是否存在,不存在则创建
                        File file = new File("d:\\upload");
                        if (!file.exists()) {
                            file.mkdirs();
                        }
                        //5、创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
                        /*
                            自定义一个文件的命名规则,防止同名的文件覆盖
                            规则:域名+毫秒值+随机数
                        * */
                        String fileName = "itcast" + System.currentTimeMillis() + new Random().nextInt() + ".jpg";
                        FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
                        //6、使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
                        int len = 0;
                        byte[] bytes = new byte[1024];
                        while ((len = is.read(bytes)) != -1) {
                            //7、使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
                            fos.write(bytes, 0, len);
                        }
                        //8、使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
                        //9、使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
                        socket.getOutputStream().write("上传成功".getBytes());
                        //10、释放资源(FileOutputStream,Socket,ServerSocket)
                        socket.close();
                    }
                    catch (IOException e){
                        e.printStackTrace();
                        System.out.println(e);
                    }
                }
            }).start();
        }
        //服务器不再关闭
        //server.close();
    }
}
  • 客户端程序
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1、创建一个本地字节输入流FileInputStream对象,构造方法中绑定读取的数据源【从指定路径中读出文件】
        FileInputStream fis=new FileInputStream("c:\\1.jpg");
        //2、创建一个客户端Socket对象,构造方法中绑定服务器的ip地址和端口号【io对象用于传送数据和接收数据】
        Socket socket=new Socket("127.0.0.1",8888);
        //3、使用Socket中的方法getOutputStream获取网络字节输出流对象OutputStream对象【获得网络输出流对象,上传】
        OutputStream os=socket.getOutputStream();
        //4、使用本地的字节输入流对象FileInputStream对象中的方法read,读取本地的文件【读取指定路径文件的内容】
        int len=0;
        byte[] bytes=new byte[1024];
        //循环读取文件
        while((len=fis.read(bytes))!=-1){
            //5、使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
            os.write(bytes,0,len);
        }
        //6、使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象【获得服务器端发送的数据】
         /*
            写结束标记
            net中的方法 java.net.Socket
            void shutdownOutput()  禁用此套接字的输出流。
            禁用此套接字的输出流。对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列。
        * */
        socket.shutdownOutput();//告诉服务器,输出流已经结束
        InputStream is=socket.getInputStream();
        //7、使用网络字节输入流InputStream对象中的read方法读取服务器回写的数据【读取并输出从服务器返回的数据】
        while((len=is.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }
        //8、释放资源(FileInputstream,Scoket)
        fis.close();
        socket.close();
    }
}

四、模拟B/S服务器

1、分析

只要访问该端口的指定地址,就访问页面头的路径,将html内容写回到客户端

  • 使用浏览器作为客户端

 

 

 

 

 

  •  目的:服务器给客户端回写一个信息,回写一个html文件,即需要读取该文件

2、服务器端

package com.liujinhui.Day1206Net.BSTCP;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
    创建BS版本TCP服务器
* */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建一个服务器ServerScoket,和系统要指定的端口号
        ServerSocket  server =new ServerSocket(8080);
        //使用accept方法,获取到请求的客户端对象(浏览器)
        Socket socket=server.accept();
        //使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is=socket.getInputStream();
        //使用网络字节输入流InputStream对象中的方法read读取客户端的请求信息
       /* byte[] bytes=new byte[1024];
        int len=0;
        while((len=is.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }*/
       //把is网络字节输入流对象,转换为字符缓冲输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        //读取客户端请求信息的第一行GET /MyJavaProject/web/index.html HTTP/1.1
        String line=br.readLine();
        System.out.println(line);
        //把读取的信息进行切割,只要中间的部分
        String[] arr = line.split(" ");
        //把路径前面的/去掉,进行截取
        String htmlPath = arr[1].substring(1);
        System.out.println(htmlPath);
        //根据此路径读取此文件
        //创建一个本地字节输入流,绑定读取的html路径
        //项目根目录不一样!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        FileInputStream fis=new FileInputStream("web/index.html");
        //使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
        OutputStream os=socket.getOutputStream();
        //写入HTTP协议响应头,固定写法【html中会讲】
        os.write("HTTP/1.1 200 0K\r\n".getBytes());
        os.write("Content-Type:text/html\r\n".getBytes());
        //必须要写入空行,否则浏览器不解析
        os.write("\r\n".getBytes());
        //一读一写复制文件,把服务器读取的html文件写到客户端
        int len=0;
        byte[] bytes=new byte[1024];
        while((len=fis.read(bytes))!=-1){
            os.write(bytes,0,len);
        }
        //释放资源
        fis.close();
        socket.close();
        server.close();


        //浏览器访问http://127.0.0.1:8080/MyJavaProject/web/index.html
        //打印一堆信息,客户端是IE的浏览器
    }
}
  • 优化:多线程一直保持监听,保证浏览器可以正常读取图片
package com.liujinhui.Day1206Net.BSTCP;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
    创建BS版本TCP服务器
* */
public class TCPServerThread {
    public static void main(String[] args) throws IOException {
        //创建一个服务器ServerScoket,和系统要指定的端口号
        ServerSocket  server =new ServerSocket(8080);
        /*
            浏览器解析服务器回写的html页面,页面中如果有图片,那么浏览器就会单独的开启一个线程,读取服务器的图片
            我们需要让服务器一直处于监听状态,客户端请求一次,服务器就回写一次
        * */
        while(true){
            //一直处于监听状态
            Socket socket=server.accept();
            new Thread(new Runnable() {
                @Override
                public void run() {
                   try{
                       //使用accept方法,获取到请求的客户端对象(浏览器)
                       //使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
                       InputStream is=socket.getInputStream();
                       //使用网络字节输入流InputStream对象中的方法read读取客户端的请求信息
                       /* byte[] bytes=new byte[1024];
                        int len=0;
                        while((len=is.read(bytes))!=-1){
                            System.out.println(new String(bytes,0,len));
                        }*/
                       //把is网络字节输入流对象,转换为字符缓冲输入流
                       BufferedReader br = new BufferedReader(new InputStreamReader(is));
                       //读取客户端请求信息的第一行GET /MyJavaProject/web/index.html HTTP/1.1
                       String line=br.readLine();
                       //打印请求的路径
                       System.out.println(line);
                       //把读取的信息进行切割,只要中间的部分
                       String[] arr = line.split(" ");
                       //把路径前面的/去掉,进行截取
                       String htmlPath = arr[1].substring(1);
                       //根据此路径读取此文件
                       //创建一个本地字节输入流,绑定读取的html路径
                       FileInputStream fis=new FileInputStream(htmlPath);
                       //使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
                       OutputStream os=socket.getOutputStream();
                       //写入HTTP协议响应头,固定写法【html中会讲】
                       os.write("HTTP/1.1 200 OK\r\n".getBytes());
                       os.write("Content-Type:text/html\r\n".getBytes());
                       //必须要写入空行,否则浏览器不解析
                       os.write("\r\n".getBytes());
                       //一读一写复制文件,把服务器读取的html文件写到客户端
                       int len=0;
                       byte[] bytes=new byte[1024];
                       while((len=fis.read(bytes))!=-1){
                           os.write(bytes,0,len);
                       }
                       //释放资源
                       fis.close();
                       socket.close();
                   }
                   catch(IOException e){
                       System.out.println(e);
                   }
                }
            }).start();
        }

        //server.close();
        //浏览器访问http://127.0.0.1:8080/web/index.html
        //打印一堆信息,客户端是IE的浏览器
    }
}

 

posted @ 2021-06-01 22:56  哥们要飞  阅读(118)  评论(0编辑  收藏  举报