• 什么是网络编程
    • 网络编程可以让程序与网络上的其他设备中的程序进行数据交互
  • 网络通信基本模式
    • 常见的通信模式有两种:Client-Server(CS)、Browser/Server(BS)

image-20230810163307496.png

1.网络通信三要素

1.1、要素一:IP地址

设备在网络中的地址,唯一的标识

  • IP(Internet Protocol):全称“互联网协议地址”,是分配给上网设备的唯一标志

  • 常见的IP分类

    • IPv4

image-20230810163314650.png

  • IPv6

image-20230810163329526.png

  • IP地址形式

    • 公网地址、私有地址(局域网使用)
    • 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0—192.168.255.255,专门为组织机构内部使用
  • IP常用命令

    • ipconfig:查看本机IP地址
    • ping IP地址:检查网络是否联通
  • 特殊IP地址

    • 本机IP:127.0.0.1或localhost—称为回送地址也可称本地回环地址,只会寻找当前所在本机

1.2、IP地址操作类—InetAddress

  • 使用
    • 此类标识Internet协议(IP)地址
  • InetAddress API

image-20230810163339391.png

public class InetAddressDemoOne {
    public static void main(String[] args) throws Exception {
        //1.获取本机地址对象
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());
        System.out.println("-----------------");
        //2.获取域名ip对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());
        System.out.println("-----------------");
        //3.获取公网ip对象
        InetAddress ip3 = InetAddress.getByName("39.156.66.14");
        System.out.println(ip3.getHostName());
        System.out.println(ip3.getHostAddress());
        //4.判断是否能通:ping 5s 之前测试是否可通
        System.out.println(ip3.isReachable(5000));
    }
}

1.3、要素二:端口号

image-20230810163347964.png

  • 端口号
    • 标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0~65535
  • 端口类型
    • 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
    • 注册端口:1024~49151,分配给用户进程或某些应用程序(如:Tomcat占用8080,MySQL占用3306)
    • 动态端口:49152~65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配

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

1.4、要素三:协议

  • 通信协议
    • 连接和通信数据的规则被称为网络通信协议
  • 参考模型
    • OSI参考模型:世界互联网标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广
    • TCP/IP参考模型:事实上的国际标准

image-20230810163357322.png

  • 传输层的2个常见协议
    • TCP:传输控制协议
    • UDP:用户数据报协议

2.UDP通信

  • UDP协议

    • UDP是一种无连接、不可靠传输的协议

    • 将数据源IP、目的地IP和端口封装称数据包,不需要建立连接

    • 每个数据包的大小限制在64KB内

    • 发送不管对方是否准备好,接收方收到不确认,故是不可靠的

    • 可以广播发送,发送数据结束时无需释放资源,开销小,速度快

  • UDP协议通信场景

    • 语音通话、视频会话等

image-20230810163434642.png

  • DatagramPacket:数据包对象(韭菜盒子)

image-20230810163440596.png

  • DatagramPacket常用方法

image-20230810163445950.png

  • DatagramSocket:发送端和接收端对象(人)

image-20230810163604147.png

  • DatagramSocket类成员方法

image-20230810163611450.png

2.1、UDP通信:快速入门

  1. 创建DatagramSocket对象(发送端对象)
  2. 创建DatagramPacket对象封装需要发送的数据(数据包对象)
  3. 使用DatagramSocket对象的send方法传入DatagramPacket对象
  4. 释放资源

  5. Client(客户端)

public class Client01 {
    public static void main(String[] args) throws Exception {
        System.out.println("===========客户端============");
        //1.创建发送端对象,发送端口自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(0220);
        //2.创建一个数据包对象封装数据(韭菜盘子)
        /*
        * byte buf[], int length,InetAddress address, int port
        * 参数一:字节数组
        * 参数二:长度
        * 参数三:服务端的主机IP地址
        * 参数四:端口号
        * */
        byte[] buffer = "吃韭菜,吃韭菜".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);
        //3.发数据出去
        socket.send(packet);

        //关闭资源
        socket.close();
    }
}
  • Server(服务端)
public class Server01 {
    public static void main(String[] args) throws Exception {
        System.out.println("===========服务端==========");
        //1.创建接收端,注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);
        //2.创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];// 1024为1K   而一次数据最多可有64K
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        //3.等待接收数据
        socket.receive(packet);

        //4.取出数据
        //读多少,倒多少
        int len = packet.getLength();
        System.out.println("收到数据:"+new String(buffer,0,len));

        //获取发送端的ip和端口
        String ip = packet.getSocketAddress().toString();
        int port = packet.getPort();
        System.out.println("ip:"+ip+",端口:"+port);
        socket.close();
    }
}

2.2、UDP通信:多发多收

  • 分析

    • 发送端可以一直发送消息
    • 接收端可以不断的接收多个发送端的消息展示
    • 发送端输入了exit则结束发送端程序
  • Client

public class Client01 {
    public static void main(String[] args) throws Exception {
        System.out.println("===========客户端============");
        //1.创建发送端对象,发送端口自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(7777);
        //2.创建一个数据包对象封装数据(韭菜盘子)
        Scanner input = new Scanner(System.in);
        String input_str;
        do {
            System.out.println("发送您要输入的信息:");
            input_str = input.nextLine();
            byte[] buffer = input_str.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);
            //3.发数据出去
            socket.send(packet);
        }while(!input_str.equals("exit"));
        //关闭资源
        socket.close();
    }
}
  • Server
public class Server01 {
    public static void main(String[] args) throws Exception {
        System.out.println("===========服务端==========");
        //1.创建接收端,注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);
        //2.创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];// 1024为1K   而一次数据最多可有64K
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        String receive_str;
        do {
            //3.等待接收数据
            socket.receive(packet);

            //4.取出数据
            //读多少,倒多少
            int len = packet.getLength();
            receive_str = new String(buffer, 0, len);
            System.out.println("收到了来自ip:"+packet.getSocketAddress().toString()+"端口:"+ packet.getPort()+"的数据\n——"+receive_str);
            //获取发送端的ip和端口
        }while(!receive_str.equals("exit"));
        socket.close();
    }
}

3.UDP通信-广播、组播

  • UDP的三种通信方式
    • 单播:单台主机与单台主机之间的通信
    • 广播:当前主机与所在网络中的所有主机通信
    • 组播:当前主机与选定的一组主机的通信
  • UDP如何实现广播
    • 使用广播地址:255.255.255.255
    • 具体操作
      • 发送端发送的数据包的目的地写的是广播地址、且指定端口号
      • 本机所在网段的其他主机的程序只要注册对应端口就可以接收到消息了
  • Client
public class Client01 {
    public static void main(String[] args) throws Exception {
        System.out.println("===========客户端============");
        //1.创建发送端对象,发送端口自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(7777);
        //2.创建一个数据包对象封装数据(韭菜盘子)
        Scanner input = new Scanner(System.in);
        String input_str;
        do {
            System.out.println("发送您要输入的信息:");
            input_str = input.nextLine();
            byte[] buffer = input_str.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("255.255.255.255"),8888);
            //3.发数据出去
            socket.send(packet);
        }while(!input_str.equals("exit"));
        //关闭资源
        socket.close();
    }
}
  • Server
public class Server01 {
    public static void main(String[] args) throws Exception {
        System.out.println("===========服务端==========");
        //1.创建接收端,注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);
        //2.创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];// 1024为1K   而一次数据最多可有64K
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        String receive_str;
        do {
            //3.等待接收数据
            socket.receive(packet);

            //4.取出数据
            //读多少,倒多少
            int len = packet.getLength();
            receive_str = new String(buffer, 0, len);
            System.out.println("收到了来自ip:"+packet.getSocketAddress().toString()+"端口:"+ packet.getPort()+"的数据\n——"+receive_str);
            //获取发送端的ip和端口
        }while(!receive_str.equals("exit"));
        socket.close();
    }
}
  • UDP如何实现组播
    • 使用组播地址:224.0.0.0~239.255.255.255
    • 具体操作
      • 发送端的数据包的目的地是组播IP(例如224.0.1.1)
      • 接收端必须绑定该组播IP,端口还要注册发送端的目的端口,这样即可接收组播消息
      • DatagramSocket的子类MulticastSocket可以在接收端绑定组件IP
  • Client
public class Client01 {
    public static void main(String[] args) throws Exception {
        System.out.println("===========客户端============");
        //1.创建发送端对象,发送端口自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(7777);
        //2.创建一个数据包对象封装数据(韭菜盘子)
        /*
        * byte buf[], int length,InetAddress address, int port
        * 参数一:字节数组
        * 参数二:长度
        * 参数三:服务端的主机IP地址
        * 参数四:端口号
        * */
        Scanner input = new Scanner(System.in);
        String input_str;
        do {
            System.out.println("发送您要输入的信息:");
            input_str = input.nextLine();
            byte[] buffer = input_str.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("224.0.1.1"),8888);
            //3.发数据出去
            socket.send(packet);
        }while(!input_str.equals("exit"));
        //关闭资源
        socket.close();
    }
}
  • Server
public class Server01 {
    public static void main(String[] args) throws Exception {
        System.out.println("===========服务端==========");
        //1.创建接收端,注册端口(人)
        MulticastSocket socket = new MulticastSocket(8888);

        //吧当前接收端加入到一个组播组中去,绑定对应的组播消息的组播IP
        socket.joinGroup(InetAddress.getByName("224.0.1.1"));

        //2.创建一个数据包对象接收数据(韭菜盘子)
        byte[] buffer = new byte[1024 * 64];// 1024为1K   而一次数据最多可有64K
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        String receive_str;
        do {
            //3.等待接收数据
            socket.receive(packet);

            //4.取出数据
            //读多少,倒多少
            int len = packet.getLength();
            receive_str = new String(buffer, 0, len);
            System.out.println("收到了来自ip:"+packet.getSocketAddress().toString()+"端口:"+ packet.getPort()+"的数据\n——"+receive_str);
            //获取发送端的ip和端口
        }while(!receive_str.equals("exit"));
        socket.close();
    }
}

4.TCP通信-快速入门

  • TCP协议特点
    • 使用TCP协议,必须双方先建立连接,它是一种面向连接可靠通信协议
    • 传输前,采用“三次握手”方式建立连接,所以是可靠的
    • 在连接中可进行大数据量的传输
    • 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低
  • TCP协议通信场景
    • 对信息安全要求较高的场景,例如(文件下载、金融等数据通信)

【TCP三次握手确立连接】

image-20230810163631293.png

【TCP四次挥手断开连接】

image-20230810163638245.png

  • TCP通信模式演示

image-20230810163644456.png

注意:在Java中只要使用 java.net.Socket类实现通信。底层即是使用了TCP协议

4.1、编写客户端代码

  • Scoket

image-20230810163651365.png

image-20230810163657001.png

  • 客户端发送消息
    1. 创建客户端的Scoket对象,请求与服务端的连接
    2. 使用socket对象调用getOutputSream()方法得到字节输出流
    3. 使用字节输出流完成数据的发送
    4. 释放资源,关闭socket管道
public class Client01 {
    public static void main(String[] args) {
        try {
            System.out.println("==========客户端===========");
            //1.创建Scoket通信管道请求有服务端的连接
            //参数一:IP地址    参数二:端口号
            Socket socket = new Socket("127.0.0.1",8888);
            //2.从socket管道中得到字节输出流,负责发送数据
            OutputStream os = socket.getOutputStream();
            //3.吧低级的字节流包装为打印流
            PrintStream ps = new PrintStream(os);
            //4.发送消息
            ps.println("我是TCP的客户端,我已经与你对接,并发出信息:你好");
            ps.flush();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

4.2、编写服务端代码、原理分析

  • ServierSocket(服务端)

image-20230810163705712.png

  • 服务端实现接收消息
    1. 创建ServerSocket对象,注册服务端端口
    2. 调用ServerSocket对象accept()方法,等待客户端的连接,并得到Socket管道对象
    3. 通过Scoket对象调用getInputStream()得到字节输入流,获取数据
    4. 释放资源,关闭socket通道
public class Server01 {
    public static void main(String[] args) throws Exception{
        System.out.println("==========服务端===========");
        //1.注册端口
        ServerSocket serverSocket = new ServerSocket(8888);
        //2.调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
        Socket socket = serverSocket.accept();
        //3.从socket通信管道中得到字节输入流
        InputStream is = socket.getInputStream();
        //4.包装为字符缓冲输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        //5.读取信息
        String line;
        if((line = br.readLine()) != null){
            System.out.println(socket.getRemoteSocketAddress()+"发送了信息:\n"+line);
        }
    }
}

5.TCP通信-多发多收消息

  • 使用TCP通信实现

    1. 使用死循环控制服务端收完信息继续等待接收下一个消息
    2. 客户端使用死循环等待用户不断输入信息
  • Client

public class Client01 {
    public static void main(String[] args) {
        try {
            System.out.println("==========客户端===========");
            //1.创建Scoket通信管道请求有服务端的连接
            //参数一:IP地址    参数二:端口号
            Socket socket = new Socket("127.0.0.1",8888);
            //2.从socket管道中得到字节输出流,负责发送数据
            OutputStream os = socket.getOutputStream();
            //3.吧低级的字节流包装为打印流
            PrintStream ps = new PrintStream(os);
            //4.发送消息
            Scanner input = new Scanner(System.in);
            String input_str;
            do {
                System.out.println("请输入您要发送的信息:\n");
                input_str = input.nextLine();
                ps.println(input_str);
            }while(!input_str.equals("exit"));
            ps.flush();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
  • Server
public class Server01 {
    public static void main(String[] args) throws Exception{
        System.out.println("==========服务端===========");
        //1.注册端口
        ServerSocket serverSocket = new ServerSocket(8888);
        //2.调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
        Socket socket = serverSocket.accept();
        //3.从socket通信管道中得到字节输入流
        InputStream is = socket.getInputStream();
        //4.包装为字符缓冲输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        //5.读取信息
        String line;
        while((line = br.readLine()) != null){
            System.out.println(socket.getRemoteSocketAddress()+"发送了信息:\n"+line);
        }
    }
}

问题:本案例实现了多发多收,那么是否可以同时接收多个客户端的消息

【不可以,因为服务端只有一个线程,只能与一个客户端进行通信】——线程

6.TCP通信-同时接收多个客户端消息【重点】

image-20230810163719672.png

  • Client 不变
  • ServerReaderThread
public class ServerReaderThread extends Thread{
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //3.从socket通信管道中得到字节输入流
            InputStream is = socket.getInputStream();
            //4.包装为字符缓冲输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5.读取信息
            String line;
            while((line = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress()+"发送了信息:\n"+line);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了");
        }
    }
}
  • Server
public class Server01 {
    public static void main(String[] args) throws Exception{
        System.out.println("==========服务端===========");
        //1.注册端口
        ServerSocket serverSocket = new ServerSocket(8888);
        //定义死循环,由主线程负责一直不断的接收客户端Socket管道连接
        while(true){
            //2.每接收到一个客户端的socket管道,吧它交给线程
            Socket socket = serverSocket.accept();
            System.out.println(socket.getRemoteSocketAddress()+"上线了!");
            new ServerReaderThread(socket).start();
        }
    }
}

7.TCP通信-使用线程池优化

  • 存在问题

    • 客户端与服务端的线程是: n-n的关系
    • 客户端并发越多,系统瘫痪的越快
  • Client 不变

  • ServerReaderRunnable
public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            //3.从socket通信管道中得到字节输入流
            InputStream is = socket.getInputStream();
            //4.包装为字符缓冲输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5.读取信息
            String line;
            while((line = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress()+"发送了信息:\n"+line);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了");
        }
    }
}
  • Server
public class Server01 {
    public static ExecutorService pools = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2),
            Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
    public static void main(String[] args) throws Exception{
        System.out.println("==========服务端===========");
        //1.注册端口
        ServerSocket serverSocket = new ServerSocket(8888);
        //定义死循环,由主线程负责一直不断的接收客户端Socket管道连接
        while(true){
            //2.每接收到一个客户端的socket管道,吧它交给线程
            Socket socket = serverSocket.accept();
            System.out.println(socket.getRemoteSocketAddress()+"上线了!");
            pools.execute(new ServerReaderRunnable(socket));
        }
    }
}

8.TCP通信实战案例——即时通信

  • 概述
    • 即时通信,指一个客户端发送的消息,其他客户端也可以接收到
    • 即时通信需要进行端口转发的设计思想
  • Client ClientReaderThread
public class Client {
    public static void main(String[] args) throws  Exception{
        Socket socket = new Socket("127.0.0.1",8888);
        Scanner input = new Scanner(System.in);
        PrintStream ps = new PrintStream(socket.getOutputStream());
        new ClientReaderThread(socket).start();
        while(true){
            System.out.println("请输入您要发送的消息:");
            String mes = input.nextLine();
            ps.println(mes);
            ps.flush();
        }
    }
}
class ClientReaderThread extends Thread{
    private Socket socket;
    public ClientReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            while((line = br.readLine())!=null){
                System.out.println(socket.getRemoteSocketAddress()+"发送了消息:\n"+line);
            }
        } catch (Exception e) {
            System.out.println("你被踢出去了!");
        }
    }
}
  • Server - ServerReaderThread
public class Server {
    public static ArrayList<Socket> AllOnlineSockets = new ArrayList<>();
    public static void main(String[] args) throws  Exception{
        ServerSocket serverSocket = new ServerSocket(8888);
        while(true){
            Socket socket = serverSocket.accept();
            System.out.println(socket.getRemoteSocketAddress()+"上线了!");
            new ServerReaderThread(socket).start();
            AllOnlineSockets.add(socket);
        }
    }
}
class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            while((line = br.readLine())!=null){
                System.out.println(socket.getRemoteSocketAddress()+"发送了消息:\n"+line);
                sendAllOnlineSockets(Server.AllOnlineSockets,line);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了!");
            Server.AllOnlineSockets.remove(socket);
        }
    }

    private void sendAllOnlineSockets(ArrayList<Socket> allOnlineSockets,String mes) {
        allOnlineSockets.forEach(s -> {
            try {
                PrintStream ps = new PrintStream(s.getOutputStream());
                ps.println(mes);
                ps.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

results matching ""

    No results matching ""