美文网首页
08-JAVA网络01

08-JAVA网络01

作者: XAbo | 来源:发表于2021-01-10 00:00 被阅读0次

    一、网络模型

    IP是外网寻址使用,MAC是内外寻址使用。
    参考:网络编程总结

    TCP/IP模型 image.png 网络协议
    网络协议关系图

    二、TCP介绍

    传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。通信两端严格区分客户端和服务端。

    2.1 TCP报文格式

    TCP报文格式
    • 源端口号以及目的端口号:各占2个字节,端口是传输层和应用层的服务接口,用于寻找发送端和接收端的进程,一般来讲,通过端口号和IP地址,可以唯一确定一个TCP连接,在网络编程中,通常被称为一个socket接口。
    • 序号:Seq序号,占4个字节、32位。用来标识从TCP发送端向TCP接收端发送的数据字节流。发起方发送数据时对此进行标记。
    • 确认序号:Ack序号,占4个字节、32位。包含发送确认的一端所期望收到的下一个序号。只有ACK标记位为1时,确认序号字段才有效,因此,确认序号应该是上次已经成功收到数据字节序号加1,即Ack=Seq + 1。
    • 数据偏移:占4个字节,用于指出TCP首部长度,若不存在选项,则这个值为20字节,数据偏移的最大值为60字节。
    • 保留字段占6位,暂时可忽略,值全为0。
    • 标志位,6个
    URG(紧急):为1时表明紧急指针字段有效
    ACK(确认):为1时表明确认号字段有效
    PSH(推送):为1时接收方应尽快将这个报文段交给应用层
    RST(复位):为1时表明TCP连接出现故障必须重建连接
    SYN(同步):在连接建立时用来同步序号
    FIN(终止):为1时表明发送端数据发送完毕要求释放连接
    
    • 接收窗口:占2个字节,用于流量控制和拥塞控制,表示当前接收缓冲区的大小。在计算机网络中,通常是用接收方的接收能力的大小来控制发送方的数据发送量。TCP连接的一端根据缓冲区大小确定自己的接收窗口值,告诉对方,使对方可以确定发送数据的字节数。
    • 校验和:占2个字节,范围包括首部和数据两部分。
    • 选项是可选的,默认情况是不选。

    2.2 TCP三次握手

    三次握手
    1. 第一次握手(客户端发送请求):客户机发送连接请求报文段到服务器,并进入SYN_SENT状态,等待服务器确认。发送连接请求报文段内容:SYN=1,seq=x;SYN=1意思是一个TCP的SYN标志位置为1的包,指明客户端打算连接的服务器的端口;seq=x表示客户端初始序号x,保存在包头的序列号(Sequence Number)字段里。
    2. 第二次握手(服务端回传确认):服务器收到客户端连接请求报文,如果同意建立连接,向客户机发回确认报文段(ACK)应答,并为该TCP连接分配TCP缓存和变量。服务器发回确认报文段内容:SYN=1,ACK=1,seq=y,ack=x+1;SYN标志位和ACK标志位均为1,同时将确认序号(Acknowledgement Number)设置为客户的ISN加1,即x+1;seq=y为服务端初始序号y。
    3. 第三次握手(客户端回传确认):客户机收到服务器的确认报文段后,向服务器给出确认报文段(ACK),并且也要给该连接分配缓存和变量。此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。客户端发回确认报文段内容:ACK=1,seq=x+1,ack=y+1;ACK=1为确认报文段;seq=x+1为客户端序号加1;ack=y+1,为服务器发来的ACK的初始序号字段+1。
      注意:握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。

    三、HTTP介绍

    详见:HTTP 官方协议内容
    超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)的发展是由蒂姆·伯纳斯-李于1989年在欧洲核子研究组织(CERN)所发起。HTTP的标准制定由万维网协会(World Wide Web Consortium,W3C)和互联网工程任务组(Internet Engineering Task Force,IETF)进行协调,最终发布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定义了HTTP协议中现今广泛使用的一个版本——HTTP 1.1。

    2014年12月,互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工作小组将HTTP/2标准提议递交至IESG进行讨论,于2015年2月17日被批准。 HTTP/2标准于2015年5月以RFC 7540正式发表,取代HTTP 1.1成为HTTP的实现标准。

    HTTP是基于B/S架构进行通信的,而HTTP的服务器端实现程序有httpd、nginx等,其客户端的实现程序主要是Web浏览器。

    HTTP请求报文 HTTP响应报文 一次完整的HTTP请求

    四、JAVA基于BIO的网络通信

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,其实就是一个门面模式。TCP用主机的IP地址加上主机上的端口号作为TCP连结的端点,这种端点就叫做套接字:Socket。

    JAVA网络编程是面向传输层进行的;传输层以下的JAVA控制不了。
    JAVA Socket是【应用层】和【传输层】传输数据的桥梁。

    应用层:主要是实现HTTP等协议的应用,如浏览器、Tomcat。
    传输层:主要是实现TCP等协议的应该,如操作系统。

    JAVA网络编程三要素:

    • 网络层的:【1】IP+【2】端口,获取到这些内容,JAVA就与网络层建立了关系,算是“联网”并且确保找到对方指定的。
    • 传输层的:【3】UDP / TCP协议,只有遵守该层指定的规则,才能“稳定”上网。
    • 客户端:java.net.Socket。
    • 服务端:java.net.ServerSocket。

    4.1 基础通信

    Java实现TCP

    客户端:

    public class TCPClient {
        public static void main(String[] args) throws IOException {
            //1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
            Socket socket = new Socket("127.0.0.1",8888);
            //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();
        }
    }
    

    服务端:

    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.使用网络字节输入流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());
            //7.释放资源(Socket,ServerSocket)
            socket.close();
            server.close();
        }
    }
    

    4.2 文件上传

    对应B/S应用,客户端就是浏览器;服务器多一步:需要在http协议下获取浏览器的文件流。

    文件上传

    客户端:

    public class TCPClient {
        public static void main(String[] args) throws IOException {
            //1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
            FileInputStream fis = new FileInputStream("c:\\1.jpg");
            //2.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
            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);
            }
    
            /*
                解决:上传完文件,给服务器写一个结束标记
                void shutdownOutput() 禁用此套接字的输出流。
                对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。
             */
            socket.shutdownOutput();
    
            //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,Socket)
            fis.close();
            socket.close();
        }
    }
    
    

    服务端:

    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();
                           }
    
                        /*
                            自定义一个文件的命名规则:防止同名的文件被覆盖
                            规则:域名+毫秒值+随机数
                         */
                           String fileName = "itcast"+System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
    
                           //5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
                           //FileOutputStream fos = new FileOutputStream(file+"\\1.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)
                           fos.close();
                           socket.close();
                       }catch (IOException e){
                           System.out.println(e);
                       }
                    }
                }).start();
            }
            //服务器就不用关闭
            //server.close();
        }
    }
    
    

    4.3 模拟Tomcat

    模拟tomcat

    客户端:浏览器

    public class Browser {
        public static void main(String[] args) throws Exception {
            Socket socket = null;
            InputStream is = null;
            OutputStream os = null;
            try {
                socket = new Socket("www.zzsi.com", 80);
                is = socket.getInputStream();
                os = socket.getOutputStream();
                //发起HTTP请求  注意这里必须制定请求方式 地址 注意空格
                StringBuffer sb = new StringBuffer("GET http://www.zzsi.com/ HTTP/1.1\r\n");
                // 以下为请求头
                sb.append("Host: www.zzsi.com\r\n");
                sb.append("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0\r\n");
                sb.append("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n");
                sb.append("Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
                // 注意这里不要使用压缩 否则返回乱码
                sb.append("Accept-Encoding: \r\n");
                sb.append("Connection: keep-alive\r\n");
                sb.append("Upgrade-Insecure-Requests: 1\r\n");
                // 注意这里要换行结束请求头
                sb.append("\r\n");
                System.out.println(sb.toString());
                os.write(sb.toString().getBytes());
                byte[] bytes = new  byte[2048];
                int len = 0;
                while ((len = is.read(bytes))!=-1) {
                    System.out.println(new String(bytes,"UTF-8"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (null != is) {
                    is.close();
                }
                if (null != os) {
                    os.close();
                }
                if (null != socket) {
                    socket.close();
                }
            }
        }
    }
    
    

    服务端:静态服务器

    
    /*
        创建BS版本TCP服务器
     */
    public class TCPServerThread {
        public static void main(String[] args) throws IOException {
            //创建一个服务器ServerSocket,和系统要指定的端口号
            ServerSocket server = new ServerSocket(8080);
    
            /*
                浏览器解析服务器回写的html页面,页面中如果有图片,那么浏览器就会单独的开启一个线程,读取服务器的图片
                我们就的让服务器一直处于监听状态,客户端请求一次,服务器就回写一次
             */
            while(true){
                //使用accept方法获取到请求的客户端对象(浏览器)
                Socket socket = server.accept();
    
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //使用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 /11_Net/web/index.html HTTP/1.1
                            String line = br.readLine();
                            System.out.println(line);
                            //把读取的信息进行切割,只要中间部分 /11_Net/web/index.html
                            String[] arr = line.split(" ");
                            //把路径前边的/去掉,进行截取 11_Net/web/index.html
                            String htmlpath = arr[1].substring(1);
    
                            //创建一个本地字节输入流,构造方法中绑定要读取的html路径
                            FileInputStream fis = new FileInputStream(htmlpath);
                            //使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
                            OutputStream os = socket.getOutputStream();
    
                            // 写入HTTP协议响应头,固定写法
                            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){
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
            //server.close();
        }
    }
    
    

    服务端:动态服务器
    待完善

    相关文章

      网友评论

          本文标题:08-JAVA网络01

          本文链接:https://www.haomeiwen.com/subject/doxhaktx.html