• Java网络篇_01 Socket网络编程



    Java与网络编程

    • 计算机网络是通过传输介质、通信设施和网络通信协议把分散在不同地点的计算机设备互连起来,实现资源共享和数据传输的系统。网络编程就是编写程序使联网的两个(或多个)设备(例如计算机)之间进行数据传输。
      Java语言对网络编程提供了良好的支持,通过其提供的接口,我们可以很方便地进行网络编程。
    • Java从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
    • Java提供的网络类库,可以实现无缝的网络连接,联网的底层细节被隐藏在Java的本机安装系统里,由JVM进行控制。并且Java实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。

    什么是socket?

    Socket的英文原义是“孔”或“插座”。在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

    Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

    Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

    Socket的原理

    Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。

    套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

    • 服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

    • 客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

    • 连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

    网络编程三要素

    • IP地址——确定对方主机
    • 端口号——区分应用程序
    • 协议TCP/UDP

    1、我们需要知道的是主机间通过网络进行通信是需要遵循网络通信协议,是通过IP地址准确定位主机,通过端口号准确定位主机上的应用,例如IP地址和端口号192.168.110.1:80

    2、如何实现网络中的主机互相通信?
    ①通信双方地址: IP和端口号
    ②一定的规则协议:tcp或者udp

    如何获取主机IP地址

    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    public class InetAddressDemo01 {
        public static void main(String[] args) throws UnknownHostException {
    //        获取给定主机名的IP地址,host表示指定主机
    //        获取本地主机IP
            InetAddress inetAddress = InetAddress.getByName("localhost");
    //        获取IP地址的主机名
            String hostName = inetAddress.getHostName();
            System.out.println(hostName); //localhost
            String hostAddress = inetAddress.getHostAddress();
            System.out.println(hostAddress); //127.0.0.1
    //        通过域名获取IP
            InetAddress baiduAddress = InetAddress.getByName("www.baidu.com");
            String baiduAddressHostAddress = baiduAddress.getHostAddress();
            System.out.println(baiduAddressHostAddress); //39.156.66.18
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    什么是UDP

    用户数据报协议(User Datagram Protocol)。
    UDP协议为应用程序提供了一种无需建立连接就可以发送封装的IP数据报的方法,俗称面向无连接;

    通俗易懂讲解UDP协议会把数据打包发送给目标地址,这个数据包能不能发送给目标地址就不管了。所以我们的udp协议它是不可靠协议、安全性低,容易丢包;但是速度非常快,因为它不需要类似于tcp协议的三次握手

    下面有代码和注释来介绍Java封装好的UDP协议的使用:

    在演示例子前,需要模拟一下发送端和接收端,我们打开C:\Windows\System32\drivers\etc的hosts文件(我这个是windows系统),然后给自己的主机地址添加两个域名,分别当作发送端和接收端。

    127.0.0.1 www.client.com
    127.0.0.1 www.server.com
    
    • 1
    • 2

    创建UDP发送者

    1. 创建发送端socket对象;
    2. 提供数据,并将数据封装到数据包中;
    3. 通过socket服务的发送功能,将数据包发出去;
    4. 释放资源。
    public class UdpClient {
        public static void main(String[] args) throws IOException {
    //        1. 创建发送端socket对象;
            DatagramSocket datagramSocket = new DatagramSocket();
    //        2. 提供数据,并将数据封装到数据包中;
            byte[] msg = "你好,你收到我的消息了吗?".getBytes();
            InetAddress serverAddress = InetAddress.getByName("www.server.com");
            int serverPort = 8080;
            //DatagramPacket对象的四个参数(发送的数据;数据的大小,以字节为单位;目标IP;目标端口号)
            DatagramPacket datagramPacket = new DatagramPacket(msg, msg.length, serverAddress, serverPort);
    //        3. 通过socket服务的发送功能,将数据包发出去;
            datagramSocket.send(datagramPacket);
            System.out.println("发送数据成功。。。");
    //        4. 释放资源。
            datagramSocket.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    最后我们需要知道,这段使用UDP协议的代码不会报错,因为它只要发出去了就行,无需关心目标是否可以收到。

    创建UDP接收者

    1. 创建接收端socket对象;
    2. 接收数据;
    3. 解析数据;
    4. 输出数据;
    5. 释放资源;
    public class UdpClient {
        public static void main(String[] args) throws IOException {
    //        1. 创建发送端socket对象;
            DatagramSocket datagramSocket = new DatagramSocket();
    //        2. 提供数据,并将数据封装到数据包中;
            byte[] msg = "你好,你收到我的消息了吗?".getBytes();
            InetAddress serverAddress = InetAddress.getByName("www.server.com");
            int serverPort = 8080;
            //DatagramPacket对象的四个参数(发送的数据;数据的大小,以字节为单位;目标IP;目标端口号)
            DatagramPacket datagramPacket = new DatagramPacket(msg, msg.length, serverAddress, serverPort);
    //        3. 通过socket服务的发送功能,将数据包发出去;
            datagramSocket.send(datagramPacket);
            System.out.println("发送数据成功。。。");
    //        4. 释放资源。
            datagramSocket.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    如果一直没有接收到数据,它会一直阻塞,知道接收到数据。
    在这里插入图片描述
    当服务端在等待数据时,客户端发送出数据,服务端这边就能接收到了。
    在这里插入图片描述

    小案例:UDP协议实现循环发送接收数据

    使用UDP协议,客户端可以一直发送数据给服务端,服务端可以一直接收客户端发送的数据。如果客户端输入-1就会直接退出程序。

    在这里插入图片描述
    在这里插入图片描述

    什么是TCP

    传输控制协议(Transmission Control Protocol)。

    UDP协议是直接将数据发送给服务器端,无需验证,无需确保服务器是否能够收到,所以UDP的传输效率高,但是它很有可能在传输过程中丢失数据,出现丢包的情况,所以说UDP是面向无连接的,不可靠的传输协议。
    而TCP协议需要先经历三次握手成功之后,确保两端正常通信,建立TCP连接,才将数据发送给服务端,当通信完成时还会解除连接(四次挥手)。所以TCP是面向连接的、可靠的传输协议。

    TCP的三次握手

    通俗来说,TCP的三次握手的目的是确认双方是否能够正常通信,并建立可靠的数据传输连接。
    在这里插入图片描述
    syn就是建立连接,ack是确认标识,fin是终止标识
    第一次握手:客户端会向服务端发送码syn=1,随机产生一个seq_num=x的数据包到服务器端(syn);
    第二次握手:服务端接收到客户端请求之后,确认ack=x+1,于是就向客户端发送syn(服务器生成的随机数y)+ack;
    第三次握手:客户端接收syn+ack,向服务端发送ack=y+1,此包发送完毕即可建立TCP连接。
    在这里插入图片描述

    创建TCP发送者

    1. 创建发送端Socket对象( 创建连接);
    2. 获取输出流对象;
    3. 发送数据;
    4. 释放资源。
    public class TcpClient {
        public static void main(String[] args) throws IOException {
    //        1. 创建发送端Socket对象( 创建连接);
            Socket socket = new Socket(InetAddress.getByName("www.server.com"),
                    8080);
    //        2. 获取输出流对象;
            OutputStream outputStream = socket.getOutputStream();
    //        3. 发送数据;
            String msg = "Hello,你在吗?";
            outputStream.write(msg.getBytes());
    //        4. 释放资源。
            outputStream.close();
            socket.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如果只有客户端,我们会发现有一个异常。
    这个异常是客户端连接不上目标端口,这是因为TCP协议必须得有连接目标的。HTTP协议就是基于TCP协议封装的。

    java.net.ConnectException: Connection refused: connect
    	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
    	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
    	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    	at java.net.Socket.connect(Socket.java:589)
    	at java.net.Socket.connect(Socket.java:538)
    	at java.net.Socket.(Socket.java:434)
    	at java.net.Socket.(Socket.java:244)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    创建TCP接收者

    1. 创建接收端Socket对象;
    2. 监听(阻塞:如果建立连接失败,程序会一直阻塞,不往下执行;
    3. 获取输入流对象;
    4. 获取数据;
    5. 释放资源。
    public class TcpServer {
        public static void main(String[] args) throws IOException {
    //        1. 创建接收端Socket对象;
            ServerSocket serverSocket = new ServerSocket(8080);
    //        2. 监听客户端发送的数据,如果客户端一直没有发送数据,服务器端会一直在该方法阻塞
            System.out.println("正在监听数据。。。");
            Socket acceptedSocket = serverSocket.accept();
    //        3. 获取输入流对象;
            InputStream inputStream = acceptedSocket.getInputStream();
    //        4. 获取数据;
            byte[] bytes = new byte[1024];
            int len = inputStream.read(bytes);
            System.out.println("接收到数据:" + new String(bytes,0,len));
    //        5. 释放资源。
            inputStream.close();
            serverSocket.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    先启动服务端,再运行客户端:
    在这里插入图片描述

    小案例:TCP协议实现循环发送接收数据

    使用tcp协议客户端可以一直发送数据给服务器端,服务器端可以一直接受到客户端发送的数据,并且客户端还可以收到服务端的响应消息。
    如果客户端输入666就会直接退出程序。

    public class TcpServer {
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("服务端启动成功。。。");
            while (true) {
                Socket socket = serverSocket.accept();
                InputStream inputStream = socket.getInputStream();
                byte[] bytes = new byte[1024];
                int len = inputStream.read(bytes);
                System.out.println("服务器接收到数据:" + new String(bytes, 0, len));
    //            回应数据给客户端
                OutputStream outputStream = socket.getOutputStream();
                String msg = "服务端已接收!";
                outputStream.write(msg.getBytes());
                inputStream.close();
                socket.close();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    public class TcpClient {
        public static void main(String[] args) throws IOException {
            Scanner sc = new Scanner(System.in);
            String ip = "www.server.com";
            int port = 8080;
            while (true) {
                System.out.println("请输入要发送的内容:");
                String context = sc.nextLine();
                if ("666".equals(context)) {
                    break;
                }
                Socket socket = new Socket(ip, port);
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write(context.getBytes());
    //            接收服务器端响应的数据
                byte[] bytes = new byte[1024];
                InputStream inputStream = socket.getInputStream();
                int len = inputStream.read(bytes);
                System.out.println("服务端响应的数据:" + new String(bytes, 0, len) + new Date());
                outputStream.close();
                socket.close();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述
    在这里插入图片描述
    优化案例为多线程处理
    当一个客户端请求服务端时,服务端可以直接处理。但是当有多个客户端同时请求时,那我们的服务端就会出现这样的情况:
    在这里插入图片描述
    在没有客户端请求时,服务端会阻塞在serverSocket.accept()这段代码上,并且服务端也是依赖这行代码接收请求,但是当服务器接收到一个请求后,代码会往下走处理这个请求,此时再来个请求,这段代码也无法提供服务了。

    这种情况如何解决呢?
    可以使用异步的方式,在Java中也就是多线程技术,通过创建一个子线程来处理请求,这样主线程就能空闲下来接收其他客户端的请求了。

    public class TcpServer {
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("服务端启动成功。。。");
            while (true) {
                Socket socket = serverSocket.accept();
                new Thread(() -> {
                    try {
                        InputStream inputStream = socket.getInputStream();
                        byte[] bytes = new byte[1024];
                        int len = inputStream.read(bytes);
                        System.out.println("服务器接收到数据:" + new String(bytes, 0, len));
    //            回应数据给客户端
                        OutputStream outputStream = socket.getOutputStream();
                        String msg = "服务端已接收!";
                        outputStream.write(msg.getBytes());
                        inputStream.close();
                        socket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    基于TCP协议封装HTTP协议,实现网络资源访问

    HTTP协议底层是基于TCP封装的。用户通过浏览器访问“ 协议名+IP+端口+资源位置 ”访问到服务器的资源。

    我们的浏览器就是一个现成的客户端,所以我们要把服务端实现一下。

    需要准备先一个文件,把文件路径放到代码中。

    public class HttpServer {
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(80);
            while (true) {
                Socket socket = serverSocket.accept();
                new Thread(() -> {
                    OutputStream outputStream = null;
                    FileInputStream fileInputStream = null;
                    try {
    //                    获取客户端浏览器请求信息
                        InputStream inputStream = socket.getInputStream();
                        byte[] reqBytes = new byte[1024];
                        int reqLen = inputStream.read(reqBytes);
                        String reqData = new String(reqBytes, 0, reqLen);
                        String resource = reqData.split(" ")[1];
    //                直接将静态资源给客户端
                        outputStream = socket.getOutputStream();
    //                读取本地磁盘中的静态资源 到内存中
                        File file = new File("C:/Users/Administrator/Desktop/" + resource);
                        fileInputStream = new FileInputStream(file);
                        byte[] bytes = new byte[1024];
                        int len = fileInputStream.read(bytes);
    //                在内存中直接将数据返回给客户端浏览器
                        outputStream.write(bytes, 0, len);
                    } catch (Exception e) {
    //                 java.io.FileNotFoundException   系统找不到指定的文件
                        try {
                            outputStream.write("404".getBytes());
                        } catch (IOException ioException) {
                            ioException.printStackTrace();
                        }
                    }
                    finally {
                        try {
                            if (outputStream != null) {
                                outputStream.close();
                            }
                            if (fileInputStream != null) {
                                fileInputStream.close();
                            }
                            if (socket != null) {
                                socket.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
    
                }).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    运行后打开浏览器访问
    输入一个存在的资源名:
    在这里插入图片描述
    输入一个不存在的资源名:
    在这里插入图片描述

  • 相关阅读:
    服务器数据恢复—OceanStor存储中NAS卷数据丢失如何恢复数据?
    【C语言学习笔记---学指针必刷它】
    测试的分类(3)
    经典面试题-平时用的测试框架是什么?pytest框架下怎么入参?
    lammps输出模拟结果的4种方法
    OpenCV----YOLOv5目标检测模型推理 (兼容YOLACT)
    乾元通多卡聚合路由设备海上通信应用解决方案
    python-(4-5)数据类型的应用(字符集、编码和运算符)
    kafka(一:分区数据不均衡(数据倾斜),分区分配策略)
    Jenkins详细安装配置部署--超详细
  • 原文地址:https://blog.csdn.net/qq_58099084/article/details/126336605