网络通信(Java)

一、认识网络编程

可以让设备中的程序与网络上其他设备的程序进行数据交互

(这里只做简单介绍,可能有不准确的地方,建议速速学习计算机网络)

1.通信架构

基本的通信架构有2种形式:CS架构、BS架构

1)CS架构(Client客户端/Server服务端)

需要开发客户端,与服务端建立网络通信

2)BS架构(Browser浏览器/Server服务端)

基于浏览器开发页面,与服务端建立网络通信

2. IP 地址

设备在网络中的地址,是唯一标识,它有两种形式:

        1)IPv4:32 bit (4字节),用点分十进制表示,如 192.168.1.66

        2)IPv6:128 bit,用冒分十六进制表示,如 2001:0db8:0000:0023:0008:0800:200c:417a

IP 域名:用于向服务器获取真实的IP地址,如 http://www.baidu.com

公网:可以连接互联网的IP地址

内网IP:也叫局域网IP,只能组织机构内部使用,192.168.开头的就是常见的局域网地址,范围即为 192.168.0.0 -- 192.168.255.255

127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机

IP常用命令:ipconfig:查看本机IP地址
                     ping IP地址:检查网络是否连通

 InetAddress:代表IP地址网络通信(Java)_第1张图片

public class Text {
    public static void main(String[] args) throws Exception {
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostAddress());// 10.3.109.76
        System.out.println(ip1.getHostName());   // LAPTOP-LDQI2FTF

        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostAddress());// 110.242.68.3
        System.out.println(ip2.getHostName());   // www.baidu.com
        System.out.println(ip2.isReachable(2000)); // true
    }
}

3.端口

应用程序在设备中的唯一标识,被规定为一个16位的二进制,范围是0~65535

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

注册端口:1024~49151,分配给用户进程或某些应用程序。

动态端口:49152到65535,它一般不固定分配某种进程,而是动态分配。

注意:开发程序一般选择注册端口,且一个设备中不能出现两个程序端口号一样,否则出错

4.协议

连接规则和传输数据的规则

OSI网络参考模型:全球网络互联参考标准,TCP/IP网络模型:事实上的国际标准

网络通信(Java)_第2张图片

二、UDP

1.认识UDP

UDP(User Datagram Protocol):用户数据报协议

特点:无连接、不可靠通信、通信效率高

        1)不事先建立连接,数据按照包发,一包数据包含:本机IP和程序端口,目的地IP、程序端口和数据(限制64KB)等

        2)发送方不管对方是否在线,数据在中间丢失也不管,接收方收到数据也不返回确认,是不可靠的

应用:语音通话、视频直播、游戏

2.面向对象编程(UDP)

DatagramSocket 类:用于创建客户端,服务端网络通信(Java)_第3张图片

DatagramPacket 类:用于创建数据包

1)客户端(发送端)实现步骤:

① 创建 DatagramSocket 对象(客户端对象)

② 创建 DatagramPacket 对象封装需要发送的数据(数据包对象)

③ 使用 DatagramSocket 对象的send方法,传入 DatagramPacket 对象

④ 释放资源

public class Client {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(6666);
        /*public DatagramPacket(byte[] buf, int length, InetAddress address, int port)*/
        byte[] bytes = "nmmd,小毕123".getBytes();
        DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),7777);
        System.out.println("客户端开始发送信息");
        socket.send(packet);
        System.out.println("客户端已发送信息");
        socket.close();
    }
}

注意:创建 DatagramPacket 对象时要多注意那四个参数

2)服务端(接收端)实现步骤:

①创建 DatagramSocket 对象并指定端口 (服务端对象)

②创建 DatagramPacket 对象接收数据 (数据包对象)

③使用 DatagramSocket 对象的 receive 方法,传入 DatagramPacket 对象

④释放资源

public class Server {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(7777);
        byte[] bytes = new byte[1024*32];
        DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
        System.out.println("服务端开始接收信息");
        socket.receive(packet);

        System.out.println(new String(bytes,0,packet.getLength()));
        System.out.println(packet.getAddress().getHostAddress());
        System.out.println(packet.getPort());
        System.out.println("服务端已接收信息");
        socket.close();
    }
}

注意:1)在输出接收的字节数组时,要注意打印相对应的字节数组长度,不然可能会乱码 

           2)可以调用 DatagramPacket 的 getAddress() 和 getPort() 方法来获取客户端IP的InetAddress 对象和它的端口号

3.实现多发多收

1)客户端(发送端)

public class Client {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket();

        System.out.println("---客户端开始发送信息---");
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.print("请输入:");
            String msg = sc.nextLine();
            if(msg.equals("exit")){
                break;
            }
            byte[] bytes = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),7777);
            socket.send(packet);
        }
        System.out.println("---客户端结束发送信息---");
        socket.close();
    }
}

2)服务端(接收端)

public class Server {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(7777);
        byte[] bytes = new byte[1024*32];
        DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
        System.out.println("---服务端开始接收信息---");
        while (true) {
            socket.receive(packet);
            System.out.println(new String(bytes,0,packet.getLength()));
            System.out.println(packet.getAddress().getHostAddress());
            System.out.println(packet.getPort());
            System.out.println("------------------------");
        }
    }
}

三、TCP

1.认识 TCP

TCP(Transmission Control Protocol):传输控制协议

特点:面向连接、可靠通信、通信效率相对不高

目的:要保证在不可靠的信道上实现可靠的传输

原理:三次握手建立连接,传输数据进行确认,四次挥手断开连接

        1)三次握手:网络通信(Java)_第4张图片

        2)四次挥手:网络通信(Java)_第5张图片

应用:网页,文件下载,支付

2.面向对象编程(TCP)

客户端                                                                                     服务端

网络通信(Java)_第6张图片

1)客户端

①创建客户端的 Socket 对象,请求与服务端的连接

②使用 socket 对象调用 getOutputStream() 方法得到字节输出流

③使用字节输出流完成数据的发送

④释放资源:关闭字节流和 socket 管道

网络通信(Java)_第7张图片

public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket(InetAddress.getLocalHost().getHostName(),8888);
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        System.out.println("---客户端开始传输信息---");
        dos.writeUTF("我爱你");
        System.out.println("---客户端结束传输信息---");
        dos.close();
        socket.close();
    }
}

注意:这里用到了数据字节流,方便传输和接收数据 

2)服务端

① 创建 ServerSocket 对象,注册服务端端口。

② 调用 ServerSocket 对象的 accept() 方法,等待客户端的连接,并得到 Socket 管道对象。

③ 通过 Socket 对象调用 getInputStream() 方法得到字节输入流,完成数据的接收

④释放资源:关闭字节流和 socket 管道

网络通信(Java)_第8张图片

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("---服务端开始接收信息---");
        Socket socket = serverSocket.accept();
        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        System.out.println(dis.readUTF());
        System.out.println(socket.getRemoteSocketAddress());
        System.out.println("------------------------");

    }
}

3.实现单一管道多发多收

public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket(InetAddress.getLocalHost().getHostName(),8888);
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        System.out.println("---客户端开始传输信息---");
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.print("请输入:");
            String msg = sc.nextLine();
            if(msg.equals("exit")){
                break;
            }
            dos.writeUTF(msg);
            dos.flush();
        }
        System.out.println("---客户端结束传输信息---");
        dos.close();
        socket.close();
    }
}
public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("---服务端开始接收信息---");
        Socket socket = serverSocket.accept();
        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()+"离线了");
                System.out.println("---服务端结束接收信息---");
                dis.close();
                socket.close();
                break;
            }
        }
    }
}

注意:当客户端停止运行,管道断开,服务端会报异常,此时要捕获异常,然后关闭服务端

4.实现服务端多管道接收

网络通信(Java)_第9张图片

利用多线程,创建多个管道,每一个管道的传输接收都交给一个子线程去处理

5.综合案例:群聊

实现多个客户端彼此相互连接网络通信(Java)_第10张图片

 原理:1)多个客户端与服务端建立 socket 管道

            2)将客户端信息打包成集合,统一传输到服务端

            3)将客户端的 socket 对象交给各个子线程处理

            4)将接收的每一条信息通过服务端发送到所有客户端

            5)客户端创建子线程来接收信息并输出

public class Server {
    public static List sockets = new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("---服务端开始接收信息---");
        while (true) {
            Socket socket = serverSocket.accept();
            sockets.add(socket);
            System.out.println(socket.getRemoteSocketAddress()+"上线了");
            new ServerReaderThread(socket).start();
        }
    }
}
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(socket.getRemoteSocketAddress()+"输出:"+msg);
                    sendMsgToAll(msg);
                } catch (Exception e) {
                    System.out.println(socket.getRemoteSocketAddress()+"离线了");
                    Server.sockets.remove(socket);
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private void sendMsgToAll(String msg) throws Exception {
        for(Socket socket:Server.sockets){
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}
public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket(InetAddress.getLocalHost().getHostName(),8888);
        new ClientReaderThread(socket).start();
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        System.out.println("---"+socket.getLocalPort()+"(本端)开始传输信息---");
        Scanner sc = new Scanner(System.in);
        while(true){
            String msg = sc.nextLine();
            if(msg.equals("exit")){
                break;
            }
            dos.writeUTF(msg);
            dos.flush();
        }
        System.out.println("---"+socket.getLocalPort()+"(本端)结束传输信息---");
        dos.close();
        socket.close();
    }
}
public class ClientReaderThread extends Thread{
    private Socket socket;
    public ClientReaderThread(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.getLocalPort()+"(本端)离线了");
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

6.实现简易的BS架构

网络通信(Java)_第11张图片

注意:想要传入数据,需要按照HTTP协议格式书写(这里只简单介绍)

网络通信(Java)_第12张图片网络通信(Java)_第13张图片

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("---服务端开始接收信息---");
        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println(socket.getRemoteSocketAddress()+"上线了");
            new ServerReaderThread(socket).start();
        }
    }
}
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            OutputStream os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/htmL;charset=UTF-8");
            ps.println(); // 必须换行
            ps.println("
你真是666
"); ps.close(); socket.close(); } catch (Exception e) { throw new RuntimeException(e); } } }

注意:在高并发的场景下,一个服务器请求创建一个线程消耗资源太大,所以应该使用线程池

网络通信(Java)_第14张图片

1)实现 socket 对象 Runnable 化

2)创建线程池来处理

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8080);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(16), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        System.out.println("---服务端开始接收信息---");
        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println(socket.getRemoteSocketAddress()+"上线了");
            Runnable target = new ServerReaderRunnable(socket);
            pool.execute(target);
        }
    }
}
public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            OutputStream os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/htmL;charset=UTF-8");
            ps.println(); // 必须换行
            ps.println("
你真是666
"); ps.close(); socket.close(); } catch (Exception e) { throw new RuntimeException(e); } } }

注意:这个 ServerReaderRunnable 和之前的 ServerReaderThread 效果一样,未做修改

(此节属于Javaweb的开篇,一些内容这里不再介绍) 

你可能感兴趣的:(网络)