• 【浅学Java】网络编程


    1. 网络编程基础

    1.1 什么是网络编程

    网络编程就是通过代码,来控制两个主机的进程之间的数据交互。

    1.2 靠什么来进行网络编程

    操作系统把网络编程的一些操作封装起来,对外提供一组 API 供程序员使用,在这里我们使用的是Socket API,它其实就是传输层提供给应用层的服务。

    1.3 常见的Socket API 分类

    1. 数据报套接字:底层使用UDP协议,数据传输的单位是数据报
    2. 流套接字:底层使用TCP协议,数据传输的单位是字节

    1.4 初始UDP,TCP协议

    UDP和TCP协议都是传输层的协议。

    1. UDP协议:无连接,不可靠传输,面向数据报,全双工
    2. TCP协议:有连接,可靠传输,面向字节流,全双工

    1.5 什么是socket?

    在操作系统中,一切皆文件,网卡作为一个硬件设备,操作系统也是用文件的形式来管理网卡,此处用来管理网卡的文件就是socket。

    socket就是一个文件描述符表。
    当某个进程被创建出来的时候,进程就会对应的创建一个PCB,PCB中就包含一个文件描述符表,文辞打开文件,就会为对应的文件分配一个表项。

    1.6 客户端/服务器端通讯的基本流程

    在这里插入图片描述
    其中第三步是程序最核心的部分,其他部分都是大同小异。

    2. UDP数据报套接字编程

    2.1 UDP socket中核心的两个类

    DatagramSocket:

    用来描述一个socket对象,里面的方法有:

    1. receive:用来接收数据,如果数据没有过来,就会阻塞等待,如果有数据了,就会返回一个DatagramPacket对象。
    2. send:用来发送数据,以DatagramPacket为单位进行发送

    注意:发送的时候,得知道发送得目标在哪里,接收得时候,也得知道数据从哪里来。

    DatagramPacket

    用来描述一个数据报,发送、接收都是以DatagramPacket为单位进行的。

    2.2 UDP编程——回显服务

    服务器端:

    package 回显服务UDP;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.SocketException;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: 3020104637
     * Date: 2022-08-01
     * Time: 23:49
     */
    public class UdpEchServer {
        private DatagramSocket socket = null;
        //port 表示端口号,服务器启动的时候,需要关联(绑定)一个端口号
        //收到数据的时候,就会根据这个端口号来决定把数据就给哪个进程
        //虽然这里的 port 写的是int类型,但是实际上是一个两个字节的无符号整数,范围为:0 ~ 65535
        public UdpEchServer(int port) throws SocketException {
            socket = new DatagramSocket(port);
        }
    
        //启动服务器
        public void start() throws IOException {
            System.out.println("服务器启动");
            //服务器一般都是维持运行 7*24h
            while(true){
                //1.读取请求,当前服务器不知道客户端啥时候发送过来请求,此时一直处于阻塞状态
                //   如果真的有请求过来了,此时 receive 就会返回
                //   参数DattagramPacket是一个输出型参数,socket读到的数据就会设置到这个对象中
                //   DatagramPacket 在构造的时候,需要指定一个内存缓冲区(就是一段内存空间,通常使用byte[])
                DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
                socket.receive(requestPacket);//把receive的返回值给requestPacket
                //把requestPacket对象里面的内容取出来,作为一个字符串
                String request = new String(requestPacket.getData(),0,requestPacket.getLength());
                //2.根据请求来响应计算
                String response = process(request);
                //3.把响应写回到客户端,这时候也需要构造一个DatapramPacket
                //  此处给DatagramPacket中设置长度,必须是“字节数的个数”
                //  如果直接取response.length(),此处得到的是,字符串的长度,也就是“字符的个数”
                //  当前的responsePacket在构造的时候,还需要指定这个包要发给谁
                //  其实发送给的目标,就是发请求的一方,用requestPacket.getSocketAddress()来获取
                DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                        response.getBytes().length,
                        requestPacket.getSocketAddress());
                socket.send(responsePacket);
                //4.加上日志打印,用格式化字符串的方式
                String log = String.format("[%s:%d] req: %s; resp: %s",
                        requestPacket.getAddress().toString(),
                        requestPacket.getPort(),
                        request,response);
                System.out.println(log);
            }
        }
        //次数的 process 方法负责的功能就是根据请求来计算响应
        //由于当前是一个回显服务,于是就将客户端发的请求直接返回即可
        private String process(String request){
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            UdpEchServer server = new UdpEchServer(9090);
            server.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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    客户端:

    package 回显服务UDP;
    
    import java.io.IOException;
    import java.net.*;
    import java.util.Scanner;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: 3020104637
     * Date: 2022-08-01
     * Time: 23:49
     */
    //客户端发什么,服务器回复什么
    public class UdpEchClient {
        private DatagramSocket socket = null;
        private String serverIp;
        private int serverPort;
        public UdpEchClient(String serverIp,int serverPort) throws SocketException {
            this.serverIp=serverIp;
            this.serverPort=serverPort;
            //构造的是客户端的socket,所以得有自己的端口,此时不指定端口就会随机获取一个空闲得端口
            this.socket=new DatagramSocket();
        }
        public void start() throws IOException {
            Scanner scanner = new Scanner(System.in);
            while (true){
                //1.从标准输入读入一个数据
                System.out.println("-> ");
                String request=scanner.nextLine();
                if(request.equals("exit")){
                    System.out.println("exit");
                    return;
                }
                //2.把字符串构造成一个 UDP 请求
                DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
                        request.getBytes().length,
                        InetAddress.getByName(serverIp),
                        serverPort);
                socket.send(requestPacket);
                //3.尝试从服务器读取响应
                DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
                socket.receive(responsePacket);
                String response = new String(responsePacket.getData(),0,responsePacket.getLength());
                //显示这个结果
                String log = String.format("req: %s; resp: %s",request,response);
                System.out.println(log);
            }
        }
        public static void main(String[] args) throws IOException {
            //127.0.0.1 是一个环回ip,表示主机本身
            UdpEchClient client = new UdpEchClient("127.0.0.1",9090);
            client.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
    • 53
    • 54
    • 55
    • 56

    2.3 程序解析

    DatagramPacket的三种构造方法:
    在这里插入图片描述
    第一种构造方法的特殊之处
    在这里插入图片描述

    3. TCP流套接字编程

    3.1 TCP socket种核心的类

    ServerSocket

    ServerSocket是创建TCP服务器的Socket API
    在这里插入图片描述

    Socket

    Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

    不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

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

    ServerSocket和socket的区别

    ServerSocket 一般仅用于设置端口号和监听,真正进行通信的是服务器端的Socket与客户端的Socket,在ServerSocket 进行accept之后,就将主动权转让了。
    详见: ServerSocket和socket的区别

    3.2 TCP编程——回显服务

    客户端

    public class TcpEchClient {
        private Socket socket =null;
        private String serverIp;
        private int serverPort;
        public TcpEchClient(String serverIp,int serverPort) throws IOException {
            this.serverIp = serverIp;
            this.serverPort = serverPort;
            //让socket创建的同时,就 尝试和服务器建立连接,此处就发生TCP的三次握手
            this.socket = new Socket(serverIp,serverPort);
        }
        public void start(){
            Scanner scanner =new Scanner(System.in);
            try(InputStream inputStream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()) {
                while(true){
                    //1.从键盘上输入内容
                    System.out.println("->");
                    String request = scanner.next();
                    if(request.equals("exit")){
                        System.out.println("exit");
                        break;
                    }
                    //2.把读取的内容,构造成请求
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(request);
                    writer.flush();//刷新缓冲区
                    //3.从服务器读取响应并解析
                    Scanner respScanner = new Scanner(inputStream);
                    String response=respScanner.next();
                    //4.把结果显示到界面上
                    String log = String.format("req:%s, res:%s",request,response);
                    System.out.println(log);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchClient client = new TcpEchClient("127.0.0.1",9000);
            client.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

    几个注意点:
    1.
    在这里插入图片描述
    2.
    在这里插入图片描述

    服务器端

    public class TcpEchServer {
        private ServerSocket listenSocket=null;
        public TcpEchServer(int port) throws IOException {
            listenSocket = new ServerSocket(port);
        }
        public void satrt() throws IOException {
            System.out.println("服务器启动");
            //可能会有多次连接
            while(true){
                //1.建立连接
                //  当没有服务器请求建立连接的话,accept就进行阻塞
                //  当没服务器请求建立连接的话,accept就会返回一个Socket对象
                Socket clientSocket=listenSocket.accept();
                //2.处理连接
                processConnection(clientSocket);
            }
        }
    
        private void processConnection(Socket clientSocket) throws IOException {
            //处理一个连接,在这里可能会设计到客户端和服务器端的多次交互
            String log = String.format("[%s:%d] 客户端上线了",
                    clientSocket.getInetAddress().toString(),
                    clientSocket.getPort());
            System.out.println(log);
            try(InputStream inputStream = clientSocket.getInputStream();
                OutputStream outputStream = clientSocket.getOutputStream()){
                while(true){
                    //1.读取请求并解析
                    Scanner scanner =new Scanner(inputStream);
                    if(!scanner.hasNext()){
                        log = String.format("[%s:%d] 客户端下线!",
                                clientSocket.getInetAddress().toString(),
                                clientSocket.getPort());
                        System.out.println(log);
                        break;
                    }
                    String request = scanner.next();
                    //2.根据请求计算两句
                    String response = process(request);
                    //3.把响应写回给客户端
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(response);
                    writer.flush();
                    log = String.format("[%s:%d], req:%s, res:%s",
                            clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(),
                            request,
                            response);
                    System.out.println(log);
                }
            }catch (IOException e){
                e.printStackTrace();
            }finally {
                //当前的clientSocket不是跟随整个生命周期,而是与连接有关
                //因此每个连接结束,都要进行关闭
                //否则,随着socket的增多,就会出现资源泄漏的问题
                clientSocket.close();
            }
        }
    
        private String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchServer server = new TcpEchServer(9000);
            server.satrt();
        }
    }
    
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    上述版本的话,当有多个客户端发起请求时,就无法处理,原因分析:
    在这里插入图片描述
    那怎么解决呢?
    可以采用多线程的方式解决这个问题。
    在这里插入图片描述
    但是上面的多线程方式,有多少个请求,就要创建多少个线程,这样下去就比较消耗资源。
    现实中有的线程并不一直有活可干,这样下去就比较消耗资源,因此可以采用线程池的方式。
    在这里插入图片描述

    4. Wrieshark抓包工具的使用

    1.选项
    在这里插入图片描述
    2.设置过滤器
    在这里插入图片描述
    3.根据结果分析问题
    在这里插入图片描述

  • 相关阅读:
    Redis哨兵模式详解
    操作系统(2)进程管理(上)进程与线程
    StripedFly恶意软件:悄无声息运行5年,感染百万设备
    数据结构:枚举
    为什么Java有了synchronized之后还造了Lock锁这个轮子?
    Android使用AndServer在安卓设备上搭建服务端(Java)(Kotlin)两种写法
    Qt TCP网络编程基本教程
    SpringBoot配置Druid
    国外资源加速下载器,代码全部开源
    单细胞论文记录(part18)--Spectral clustering based on learning similarity matrix
  • 原文地址:https://blog.csdn.net/qq_52276036/article/details/126157590