Java网络编程

网络编程概述

应用程序可以与网络上其他设备中的应用程序进行数据交互。 网络编程的解决方案都是在java.net包下, 通信的基本架构主要有两种形式:一种是CS架构(Client 客户端/Server服务端)、一种是BS架构(Brower 浏览器/Server服务端)。

CS架构的特点:

CS架构需要用户在自己的电脑或者手机上安装客户端软件,然后由客户端软件通过网络连接服务器程序,由服务器把数据发给客户端,客户端就可以在页面上看到各种数据了。

Java网络编程_第1张图片 BS架构的特点:

BS架构不需要开发客户端软件,用户只需要通过浏览器输入网址就可以直接从服务器获取数据,并由服务器将数据返回给浏览器,用户在页面上就可以看到各种数据了。

Java网络编程_第2张图片

这两种结构不管是CS、还是BS都是需要用到网络编程的相关技术。我们学习Java的程序员,以后从事的工作方向主要还是BS架构的。  

网络编程三要素

分别是IP地址、端口号、通信协议

Java网络编程_第3张图片

  1. IP地址:表示设备在网络中的地址,是网络中设备的唯一标识

  2. 端口号:应用程序在设备中唯一的标识

  3. 协议:连接和数据在网络中传输的规则。

IP地址

IP(Ineternet Protocol)全称互联网协议地址,是分配给网络设备的唯一表示

本机的IP地址,可以在命令行窗口,输入ipconfig命令查看,如下图所示

Java网络编程_第4张图片

IPV6

IPV6采用128位二进制数据来表示(16个字节),号称可以为地球上的每一粒沙子编一个IP地址,

IPV6比较长,为了方便阅读,每16位编成一组,每组采用十六进制数据表示,然后用冒号隔开(称为冒分十六进制表示法),如下图所示

Java网络编程_第5张图片

InetAddress类

Java中也有一个类用来表IP地址,这个类是InetAddress类。我们在开发网络通信程序的时候,可能有时候会获取本机的IP地址,以及测试与其他地址是否连通。

Java网络编程_第6张图片

public class InetAddressTest {
    public static void main(String[] args) throws Exception {
        // 1、获取本机IP地址对象的
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());

        // 2、获取指定IP或者域名的IP地址对象。
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

        // ping www.baidu.com
        System.out.println(ip2.isReachable(6000));
    }
}

端口号

端口号:指的是计算机设备上运行的应用程序的标识,被规定为一个16位的二进制数据,范围(0~65535)

端口号分为一下几类(了解一下)

  • 周知端口:0~1023,被预先定义的知名应用程序占用(如:HTTP占用80,FTP占用21)

  • 注册端口:1024~49151,分配给用户经常或者某些应用程序

  • 动态端口:49152~65536,之所以称为动态端口,是因为它一般不固定分配给某进程,而是动态分配的

协议

网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。

为了让世界上各种上网设备能够互联互通,肯定需要有一个组织出来,指定一个规则,大家都遵守这个规则,才能进行数据通信。

Java网络编程_第7张图片

UDP协议特点(User Datagram Protocol)用户数据包网络传输协议

Java网络编程_第8张图片

TPC协议特点

Java网络编程_第9张图片

三次握手如下图所示:目的是确认通信双方,手法消息都是正常没问题的

Java网络编程_第10张图片

四次挥手如下图所示:目的是确保双方数据的收发已经完成,没有数据丢失

Java网络编程_第11张图片

UDP通信

UDP是面向无连接的、不需要确认双方是否存在,所以它是不可靠的协议。Java提供了一个类叫DatagramSocket来完成基于UDP协议的收发数据。使用DatagramSocket收发数据时,数据要以数据包的形式体现,一个数据包限制在64KB以内 。

一发一收

import java.io.IOException;
import java.net.*;

public class Client {
    public static void main(String[] args) throws IOException {
        //创建DatagramSocket对象
        DatagramSocket ds= new DatagramSocket();
        String meg = "我是客户端";
        byte[] bytes = meg.getBytes();
        //创建数据包对象并且封装要发送的数据;
        DatagramPacket packet = new DatagramPacket(
                bytes,
                bytes.length,
                InetAddress.getLocalHost(),
                6666
        );
        ds.send(packet);//开始正式发送这个数据;
        System.out.println("客户端数据发送完毕~~");
        ds.close();//释放资源;
    }
}
public class Server {
    public static void main(String[] args) throws IOException {
        System.out.println("开启服务端");
        //创建一个服务端接收对象注册端口;
        DatagramSocket socket = new DatagramSocket(6666);
        byte[] bytes = new byte[1024*64];//可接收最大数据64k
        //创建接收数据的DatagramPacket对象
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
        socket.receive(packet);
        //从字节数组中把接收到的数据打印出来
        int length = packet.getLength();
        String s = new String(bytes, 0, length);
        System.out.println(s);

    }
}

UDP通信代码(多发多收)

public class Client {
    public static void main(String[] args) throws IOException {
        //创建DatagramSocket对象
        DatagramSocket ds= new DatagramSocket();
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.println("请讲");
            String meg = scanner.next();
            if(meg.contains("886")){
                System.out.println("欢迎下次光临~~~");
                ds.close();
                break;//跳出死循环;
            }
            byte[] bytes = meg.getBytes();
            //创建数据包对象并且封装要发送的数据;
            DatagramPacket packet = new DatagramPacket(
                    bytes,
                    bytes.length,
                    InetAddress.getLocalHost(),
                    6666
            );
            ds.send(packet);//开始正式发送这个数据;
            System.out.println("客户端数据发送完毕~~");
            ds.close();//释放资源;
        }

    }
}
public class Server {
    public static void main(String[] args) throws IOException {
        System.out.println("开启服务端");
        //创建一个服务端接收对象注册端口;
        DatagramSocket socket = new DatagramSocket(6666);
        byte[] bytes = new byte[1024*64];//可接收最大数据64k
        //创建接收数据的DatagramPacket对象
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
        while (true) {
            socket.receive(packet);
            //从字节数组中把接收到的数据打印出来
            int length = packet.getLength();
            String s = new String(bytes, 0, length);
            System.out.println(s);
        }

    }
}

TCP通信

一发一收

  1. 当创建Socket对象时,就会在客户端和服务端创建一个数据通信的管道,在客户端和服务端两边都会有一个Socket对象来访问这个通信管道。

  2. 现在假设客户端要发送一个“在一起”给服务端,客户端这边先需要通过Socket对象获取到一个字节输出流,通过字节输出流写数据到服务端

  3. 然后服务端这边通过Socket对象可以获取字节输入流,通过字节输入流就可以读取客户端写过来的数据,并对数据进行处理。

  4. 服务端处理完数据之后,假设需要把“没感觉”发给客户端端,那么服务端这边再通过Socket获取到一个字节输出流,将数据写给客户端

  5. 客户端这边再获取输入流,通过字节输入流来读取服务端写过来的数据。

public class Client {
    public static void main(String[] args) throws IOException {
        //创建socket对象;并请求与服务端的连接;
        Socket socket = new Socket("localhost",6666);
        //从socket通信中得到一个字节输出流,用来发数据给服务端;
        OutputStream outputStream = socket.getOutputStream();
        //把低级的字节输出流包装成数据输出流
        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
        //开始写数据出去;
        dataOutputStream.writeUTF("我是客户端");

        System.out.println(socket.getRemoteSocketAddress());
        //释放资源
        dataOutputStream.close();
        socket.close();

    }
}
public class Server {
    public static void main(String[] args) throws IOException {
        System.out.println("启动服务端~~~");
        //1、创建ServerSocket的对象,同时为服务端注册端口。
        ServerSocket ss = new ServerSocket(6666);
        //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
        Socket socket = ss.accept();
        //3、从socket通信管道中得到一个字节输入流。
        InputStream inputStream = socket.getInputStream();
        //4、把原始的字节输入流包装成数据输入流
        DataInputStream dataInputStream = new DataInputStream(inputStream);
        // 5、使用数据输入流读取客户端发送过来的消息
        String s = dataInputStream.readUTF();
        System.out.println(s);
        //释放资源;
        dataInputStream.close();
        ss.close();


    }
}

多发多收

如果有多个客户端连接服务端,此时服务端是不支持的。

为了让服务端能够支持多个客户端通信,就需要用到多线程技术。具体的实现思路如下图所示:每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。按照这样的设计,服务端就可以支持多个客户端连接了。

public class Client {
    public static void main(String[] args) throws IOException {
        //创建socket对象;并请求与服务端的连接;
        Socket socket = new Socket("localhost",6666);
        //从socket通信中得到一个字节输出流,用来发数据给服务端;
        OutputStream outputStream = socket.getOutputStream();
        //把低级的字节输出流包装成数据输出流
        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("请说");
            String s = scanner.next();
            if(s.contains("886")){
                outputStream.close();
                socket.close();
                break;//出口;
            }
            //开始写数据出去;
            dataOutputStream.writeUTF(s);

            System.out.println(socket.getRemoteSocketAddress());
            //释放资源
            dataOutputStream.close();
            socket.close();
        }

    }
}

public class Server {
    public static void main(String[] args) throws IOException {
        System.out.println("启动服务端~~~");
        //1、创建ServerSocket的对象,同时为服务端注册端口。
        ServerSocket ss = new ServerSocket(6666);
        while (true) {
            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            Socket socket = ss.accept();
            System.out.println("有人上线了" + socket.getRemoteSocketAddress());
            new ServerThread(socket).start();
        }


    }
}
public class ServerThread extends Thread{
    private Socket socket;

    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //3、从socket通信管道中得到一个字节输入流。
            InputStream inputStream = socket.getInputStream();
            //4、把原始的字节输入流包装成数据输入流
            DataInputStream dataInputStream = new DataInputStream(inputStream);
            // 5、使用数据输入流读取客户端发送过来的消息
            while (true) {
                try {
                    String s = dataInputStream.readUTF();
                    System.out.println(s);
                } catch (IOException e) {
                    //释放资源;
                    dataInputStream.close();
                    socket.close();
                    break;//出口
                }

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

TCP通信(多线程)

为了让服务端能够支持多个客户端通信,就需要用到多线程技术。具体的实现思路如下图所示:每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。按照这样的设计,服务端就可以支持多个客户端连接了。

Java网络编程_第12张图片

 线程类:

public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            while (true){
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);

                } catch (Exception e) {
                    System.out.println("有人下线了:" + socket.getRemoteSocketAddress());
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端代码:

/**
 *  目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通信。
 */
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功-------");
        // 1、创建ServerSocket的对象,同时为服务端注册端口。
        ServerSocket serverSocket = new ServerSocket(8888);

        while (true) {
            // 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            Socket socket = serverSocket.accept();

            System.out.println("有人上线了:" + socket.getRemoteSocketAddress());

            // 3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。
            new ServerReaderThread(socket).start();
        }
    }
}

群聊的实现:

在服务端创建一个存储Socket的集合,每当一个客户端连接服务端,就可以把客户端Socket存储起来;当一个客户端给服务端发消息时,再遍历集合通过每个Socket将消息再转发给其他客户端。

Java网络编程_第13张图片

public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            while (true){
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);
                    // 把这个消息分发给全部客户端进行接收。
                    sendMsgToAll(msg);
                } catch (Exception e) {
                    System.out.println("有人下线了:" + socket.getRemoteSocketAddress());
                    Server.onLineSockets.remove(socket);
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void sendMsgToAll(String msg) throws IOException {
        // 发送给全部在线的socket管道接收。
        for (Socket onLineSocket : Server.onLineSockets) {
            OutputStream os = onLineSocket.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

你可能感兴趣的:(java基础,网络,java,java-ee,jvm)