Java learning diary Day25

Realize that the client sends messages to the server in a loop

Client code:

package socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * 聊天室客户端
 */
public class Client {
    
    
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
        并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
        与远端计算机的数据交互工作。
        我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
        风(输出流),通过它们就可以与对方交流了。
     */
    private Socket socket;

    /**
     * 构造方法,用来初始化客户端
     */
    public Client(){
    
    
        try {
    
    
            System.out.println("正在链接服务端...");
            /*
                实例化Socket时要传入两个参数
                参数1:服务端的地址信息
                     可以是IP地址,如果链接本机可以写"localhost"
                参数2:服务端开启的服务端口
                我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
                的服务端应用程序。
                实例化的过程就是链接的过程,如果链接失败会抛出异常:
                java.net.ConnectException: Connection refused: connect
             */
            socket = new Socket("localhost",8088);
            System.out.println("与服务端建立链接!");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法
     */
    public void start(){
    
    
        try {
    
    
            /*
                Socket提供了一个方法:
                OutputStream getOutputStream()
                该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。
             */
            //低级流,将字节通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节
            OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
            //高级流,负责块写文本数据加速
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串,自动行刷新
            PrintWriter pw = new PrintWriter(bw,true);

            Scanner scanner = new Scanner(System.in);
            while(true) {
    
    
                String line = scanner.nextLine();
                if("exit".equalsIgnoreCase(line)){
    
    
                    break;
                }
                pw.println(line);
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                /*
                    通讯完毕后调用socket的close方法。
                    该方法会给对方发送断开信号。
                 */
                socket.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
    
    
        Client client = new Client();
        client.start();
    }
}

Server code:

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    
    
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
    
    
        try {
    
    
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
    
    
        try {
    
    
            System.out.println("等待客户端链接...");
            /*
                ServerSocket提供了接受客户端链接的方法:
                Socket accept()
                这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                通过这个Socket就可以与客户端进行交互了。

                可以理解为此操作是接电话,电话没响时就一直等。
             */
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端链接了!");

            /*
                Socket提供的方法:
                InputStream getInputStream()
                获取的字节输入流读取的是对方计算机发送过来的字节
             */
            InputStream in = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(in,"UTF-8");
            BufferedReader br = new BufferedReader(isr);

            String message = null;
            while((message = br.readLine())!=null) {
    
    
                System.out.println("客户端说:" + message);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        Server server = new Server();
        server.start();
    }
}

A few points to note:

1: When the client no longer communicates with the server, it needs to call socket.close() to disconnect, and then a disconnection signal will be sent to the server. At this time, the br.readLine() method on the server will return null, indicating that the client is disconnected.

2: When the client does not enter information to send to the server after connecting, the br.readLine() method of the server is in a blocking state until a line of string sent from the client is read.

Multi-client link

Previously only the first connected client could talk to the server.

reason:

The server only calls the accept method once, so only when the first client connects, the server accepts the connection and returns the Socket, and can interact with it at this time.

When the second client establishes a connection, because the server does not call accept again, it cannot interact with it.

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    
    
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
    
    
        try {
    
    
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
    
    
        try {
    
    
            while(true) {
    
    
                System.out.println("等待客户端链接...");
                /*
                    ServerSocket提供了接受客户端链接的方法:
                    Socket accept()
                    这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                    的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                    通过这个Socket就可以与客户端进行交互了。

                    可以理解为此操作是接电话,电话没响时就一直等。
                 */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                /*
                    Socket提供的方法:
                    InputStream getInputStream()
                    获取的字节输入流读取的是对方计算机发送过来的字节
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                String message = null;
                while ((message = br.readLine()) != null) {
    
    
                    System.out.println("客户端说:" + message);
                }
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        Server server = new Server();
        server.start();
    }
}

After adding the loop operation, it was found that it was still not possible.

the reason is:

An inner loop is nested in the outer while loop (looping to read messages sent by the client), and the loop execution mechanism determines that the inner loop does not end, and the outer loop cannot enter the second operation.

Multithreading

Thread: A sequential, single flow of program execution is a thread. The code is executed sentence by sentence in sequence.
Multi-threading: Multiple single-sequence-executed processes run concurrently. Creates the effect of "sensory simultaneous operation".
concurrency:

The actual operation of multiple threads is stop-and-go. The thread scheduler divides the CPU running time into several time segments and

It is allocated to each thread as evenly as possible, and the thread that gets the time slice is executed by the CPU for this period of time. When the thread schedules after a timeout

The program will again allocate a time slice to a thread for the CPU to execute it. so repeatedly. Since the CPU execution time is in

At the nanosecond level, we don't feel the process of switching threads to run. So stop and go on a micro level, and feel like running together on a macro level

The phenomenon becomes running concurrently!

use:
  • When there is a conflict in the execution order of multiple code fragments, they should be run "simultaneously" on different threads when they want to do their own work
  • One thread can run, but multiple threads can be faster, you can use multithreading
Thread life cycle diagram

There are two ways to create threads

Method 1: Inherit Thread and rewrite the run method

Define a thread class, rewrite the run method, and define the tasks to be performed by the thread (tasks that are expected to be performed concurrently with other threads).

Note: To start the thread, call the start method of the thread, not the run method! ! !

package thread;

/**
 * 多线程
 * 线程:程序中一个单一的顺序执行流程
 * 多线程:多个单一顺序执行流程"同时"执行
 *
 * 多线程改变了代码的执行方式,从原来的单一顺序执行流程变为多个执行流程"同时"执行。
 * 可以让多个代码片段的执行互不打扰。
 *
 * 线程之间是并发执行的,并非真正意义上的同时运行。
 * 常见线程有两种方式:
 * 1:继承Thread并重写run方法
 *
 */
public class ThreadDemo1 {
    
    
    public static void main(String[] args) {
    
    
        //创建两个线程
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();
        /*
            启动线程,注意:不要调用run方法!!
            线程调用完start方法后会纳入到系统的线程调度器程序中被统一管理。
            线程调度器会分配时间片段给线程,使得CPU执行该线程这段时间,用完后
            线程调度器会再分配一个时间片段给一个线程,如此反复,使得多个线程
            都有机会执行一会,做到走走停停,并发运行。
            线程第一次被分配到时间后会执行它的run方法开始工作。
         */
        t1.start();
        t2.start();

    }
}
/**
 * 第一种创建线程的优点:
 * 结构简单,利于匿名内部类形式创建。
 *
 * 缺点:
 * 1:由于java是单继承的,这会导致继承了Thread就无法再继承其他类去复用方法
 * 2:定义线程的同时重写了run方法,这等于将线程的任务定义在了这个线程中导致
 *   线程只能干这件事。重(chong)用性很低。
 */
class MyThread1 extends Thread{
    
    
    public void run(){
    
    
        for (int i=0;i<1000;i++){
    
    
            System.out.println("hello姐~");
        }
    }
}
class MyThread2 extends Thread{
    
    
    public void run(){
    
    
        for (int i=0;i<1000;i++){
    
    
            System.out.println("来了~老弟!");
        }
    }
}

The first way to create a thread

advantage:

The structure is simple, and it is convenient to create an anonymous inner class.

shortcoming:

  • 1: Directly inheriting threads will result in the inability to reuse methods in other classes, which is very inconvenient in actual development.
  • 2: Rewriting the run method while defining the thread will cause the thread and the thread task to be bound together, which is not conducive to the reuse of the thread.
Method 2: Implement the Runnable interface to define thread tasks separately
package thread;

/**
 * 第二种创建线程的方式
 * 实现Runnable接口单独定义线程任务
 */
public class ThreadDemo2 {
    
    
    public static void main(String[] args) {
    
    
        //实例化任务
        Runnable r1 = new MyRunnable1();
        Runnable r2 = new MyRunnable2();
        //创建线程并指派任务
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);

        t1.start();
        t2.start();
    }
}
class MyRunnable1 implements Runnable{
    
    
    public void run() {
    
    
        for (int i=0;i<1000;i++){
    
    
            System.out.println("你是谁啊?");
        }
    }
}
class MyRunnable2 implements Runnable{
    
    
    public void run() {
    
    
        for (int i=0;i<1000;i++){
    
    
            System.out.println("开门!查水表的!");
        }
    }
}

Thread creation in the form of an anonymous inner class

package thread;

/**
 * 使用匿名内部类完成线程的两种创建
 */
public class ThreadDemo3 {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(){
    
    
            public void run(){
    
    
                for(int i=0;i<1000;i++){
    
    
                    System.out.println("你是谁啊?");
                }
            }
        };
//        Runnable r2 = new Runnable() {
    
    
//            public void run() {
    
    
//                for(int i=0;i<1000;i++){
    
    
//                    System.out.println("我是查水表的!");
//                }
//            }
//        };
        //Runnable可以使用lambda表达式创建
        Runnable r2 = ()->{
    
    
                for(int i=0;i<1000;i++){
    
    
                    System.out.println("我是查水表的!");
                }
        };

        Thread t2 = new Thread(r2);

        t1.start();
        t2.start();
    }
}

The code in java is run by threads, and the thread that executes the main method is called the "main thread".

Threads provide a method:

  • static Thread currentThread()

    This method can get the thread running this method

package thread;

/**
 * java中所有的代码都是靠线程执行的,main方法也不例外。JVM启动后会创建一条线程来执行main
 * 方法,该线程的名字叫做"main",所以通常称它为"主线程"。
 * 我们自己定义的线程在不指定名字的情况下系统会分配一个名字,格式为"thread-x"(x是一个数)。
 *
 * Thread提供了一个静态方法:
 * static Thread currentThread()
 * 获取执行该方法的线程。
 *
 */
public class CurrentThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        /*
            后期会学习到一个很重要的API:ThreadLocal,它可以使得我们在一个线程上跨越多个
            方法时共享数据使用,其内部要用到currentThread方法来辨别线程。
            如spring的事物控制就是靠ThreadLocal实现的。
         */
        Thread main = Thread.currentThread();//获取执行main方法的线程(主线程)
        System.out.println("线程:"+main);

        dosome();//主线程执行dosome方法
    }
    public static void dosome(){
    
    
        Thread t = Thread.currentThread();//获取执行dosome方法的线程
        System.out.println("执行dosome方法的线程是:"+t);
    }
}

Use multithreading to realize multi-client connection server

flow chart

Server-side code modification:

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    
    
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
    
    
        try {
    
    
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
    
    
        try {
    
    
            while(true) {
    
    
                System.out.println("等待客户端链接...");
                /*
                    ServerSocket提供了接受客户端链接的方法:
                    Socket accept()
                    这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                    的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                    通过这个Socket就可以与客户端进行交互了。

                    可以理解为此操作是接电话,电话没响时就一直等。
                 */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程与该客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                Thread t = new Thread(clientHandler);
                t.start();

            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        Server server = new Server();
        server.start();
    }

    /**
     * 定义线程任务
     * 目的是让一个线程完成与特定客户端的交互工作
     */
    private class ClientHandler implements Runnable{
    
    
        private Socket socket;
        public ClientHandler(Socket socket){
    
    
            this.socket = socket;
        }
        public void run(){
    
    
            try{
    
    
                 /*
                    Socket提供的方法:
                    InputStream getInputStream()
                    获取的字节输入流读取的是对方计算机发送过来的字节
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                String message = null;
                while ((message = br.readLine()) != null) {
    
    
                    System.out.println("客户端说:" + message);
                }
            }catch(IOException e){
    
    
                e.printStackTrace();
            }
        }
    }


}

Thread API

Methods for obtaining thread-related information

package thread;

/**
 * 获取线程相关信息的一组方法
 */
public class ThreadInfoDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread main = Thread.currentThread();//获取主线程

        String name = main.getName();//获取线程的名字
        System.out.println("名字:"+name);

        long id = main.getId();//获取该线程的唯一标识
        System.out.println("id:"+id);

        int priority = main.getPriority();//获取该线程的优先级
        System.out.println("优先级:"+priority);

        boolean isAlive = main.isAlive();//该线程是否活着
        System.out.println("是否活着:"+isAlive);

        boolean isDaemon = main.isDaemon();//是否为守护线程
        System.out.println("是否为守护线程:"+isDaemon);

        boolean isInterrupted = main.isInterrupted();//是否被中断了
        System.out.println("是否被中断了:"+isInterrupted);

    }
}
thread priority

After the thread starts, it will be included in the thread scheduler for unified management. Threads can only be passively assigned time slices to run concurrently, but cannot actively ask for time slices. The thread scheduler allocates time slices to each thread as evenly as possible.

Threads have 10 priorities, represented by integers 1-10

  • 1 is the minimum priority, 10 is the highest priority. 5 is the default value
  • Adjusting the priority of the thread can interfere with the probability of obtaining time slices to the greatest extent. The higher the priority, the more times the thread obtains time slices, and vice versa.
package thread;

public class PriorityDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread max = new Thread(){
    
    
            public void run(){
    
    
                for(int i=0;i<10000;i++){
    
    
                    System.out.println("max");
                }
            }
        };
        Thread min = new Thread(){
    
    
            public void run(){
    
    
                for(int i=0;i<10000;i++){
    
    
                    System.out.println("min");
                }
            }
        };
        Thread norm = new Thread(){
    
    
            public void run(){
    
    
                for(int i=0;i<10000;i++){
    
    
                    System.out.println("nor");
                }
            }
        };
        min.setPriority(Thread.MIN_PRIORITY);
        max.setPriority(Thread.MAX_PRIORITY);
        min.start();
        norm.start();
        max.start();
    }
}

Summarize

Multithreading

Thread: A single sequential execution process is a thread, and sequential execution: the code is executed sentence by sentence.

Multithreading: Multiple threads execute concurrently. The code between threads is quickly switched and executed by the CPU, resulting in a perceptual "simultaneous" execution effect.

How threads are created

  1. Inherit Thread, rewrite the run method, and define the tasks to be performed by the thread in the run method

    advantage:

    • Simple structure, easy to create anonymous inner class

    shortcoming:

    • Inheritance conflict: due to java single inheritance, if you inherit thread, you can no longer inherit other classes to reuse methods

    • Coupling problem: threads and tasks are coupled together, which is not conducive to the reuse of threads.

  2. Implement the Runnable interface to define thread tasks separately

    advantage:

    • Since it implements the interface, there is no inheritance conflict problem
    • There is no coupling relationship between threads and tasks, which facilitates the reuse of threads

    shortcoming:

    • It is more complicated to create (in fact, it is not a disadvantage)

Common methods of thread Thread class

void run(): The thread itself has a run method, which can be rewritten to define the thread task when the first thread is created.

void start(): The method to start the thread . After the call, the thread is included in the thread scheduler for unified management, and is in the RUNNABLE state, waiting for the allocated time slice to start running concurrently.

     注:线程第一次获取时间片开始执行时会自动执行run方法。

              **启动线程一定是调用start方法,而不能调用run方法!**

String getName(): Get the thread name

long getId(): Get the unique ID of the thread

int getPriority(): Get the thread priority, corresponding to the integer 1-10

boolean isAlive(): Whether the thread is still alive

boolean isDaemon(): Whether it is a daemon thread

boolean isInterrupted(): Whether it is interrupted

void setPriority(int priority): Set the thread priority, the parameter can pass an integer 1-10. 1 is the lowest priority, 5 is the default priority, 10 is the highest priority

                               优先级越高的线程获取时间片的次数越多。可以使用Thread的常量MIN_PRIORITY,NORM_PRIORITY,MAX_PRIORITY。他们分别表示                                  最低,默认,最高优先级

static Thread currentThread(): Gets the thread running this method.

Guess you like

Origin blog.csdn.net/weixin_43121885/article/details/130315184