1.多线程服务器端原理分析

  1. 一般情况下同一时刻服务器都不止和一个客户端进行通信,如果服务器只有一个线程,那么在多任务操作时任务之间就需要等待,因此需要为服务器创建多条线程提供给不同的同时客户端使用。
  2. 为服务器创建多线程,就类似于为服务器创建"影分身",让每一条线程都能享有一个独立的"影分身"服务器。
  3. 为了满足上述要求,每一条线程都必须传入一个新创建的Runnable接口实现类,实现类中的run()方法运行服务器的主程序。

2.客户端代码

客户端主要功能:

  1. 客户端向服务器发送信息。
  2. 客户端接收服务器的反馈信息。
代码如下
/*实现TCP通信的客户端程序
  实现步骤:
        1.创建Socket对象 (主动连接服务器)
            Socket(String host, int port)  host为服务器的地址(此处服务器也在本机所以使用环回地址),port为服务器中应用的端口
            
        2.OutputStream getOutputStream()
            返回套接字中的字节输出流,此时就可以使用write()方法写入数据,写入服务器
            
        3.InputStream getInputStream()
            返回套接字中的字节输入流对象,调用read()方法可以读取服务器发来的数据
            
        4.释放资源 close()
 */
 public class TCPClient {

    public static void main(String[] args) throws IOException {

        Socket client = new Socket("127.0.0.1",9000);

        OutputStream out = client.getOutputStream();

        byte[] bytes = new byte[1024];
        out.write("请求连接服务器".getBytes());  //没有写在文件中,写入了服务器(发送给了服务器)



        InputStream in = client.getInputStream();

        byte[] inBytes = new byte[1024];
        int inLen = in.read(inBytes);

        String s = new String(inBytes, 0, inLen);
        System.out.println(s);

        client.close();
    }

}

3.服务器代码

服务器主要功能

  1. 服务器需要接收客户端发来的消息。
  2. 接收到消息后,服务器需要向客户端发送反馈信息。
    由于需要为服务器创建多线程,因此服务器实现的程序需要放在Runnable实现类的run()方法中
代码如下
/*实现TCP通信中的服务器程序
  实现TCP服务器步骤:
      1.创建ServerSocket对象
        ServerSocket(in port) 端口号

      2.等待客户端的连接,如果没有客户端连接,永远等待
        ServerSocket类方法 accept()  (等待客户端的连接)
        accept() 方法的返回值为Socket对象(客户端套接字,包含客户端的IP地址,用于回复信息)

      3.Socket对象中获取字节输入流
        InputStream getInputStream()
        对象调用read()方法,读取客户端发来的数据

      4.Socket对象中获取字节输出流
      OutputStream getOutputStream()
        对象调用write()方法,向客户端写入(回复)数据

      5.释放资源  close()

 */
public class TCPThreadServerDemo {

    public static void main(String[] args) throws IOException {

        ServerSocket server = new ServerSocket(9000);
        
		//循环放在此处是为了,当每次客户端与服务器通信完成时,服务器不停止运行,而是又再一次进入侦听状态,侦听是否还有服务器向自己发送信息;
        while (true) {
            Socket accept = server.accept();

            Thread thread = new Thread(new ThreadServer(accept));

            thread.start();

        }
    }
}
public class ThreadServer implements Runnable{

    private Socket accept;
	//测试类中传入服务器的侦听accept()侦听到的客户端对象
    public ThreadServer(Socket accept){
        this.accept = accept;
    }

    @Override
    public void run() {

            try {
                Server.ServerMethod(accept);
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
}
public class Server {

    public static void ServerMethod(Socket accept) throws IOException {

        InputStream in = accept.getInputStream();

        byte[] bytes = new byte[1024];
        int len = in.read(bytes);
        String s = new String(bytes, 0, len);
        System.out.println(s + Thread.currentThread().getName());


        OutputStream out = accept.getOutputStream();
        out.write("连接成功!".getBytes());

        accept.close();

    }
}

测试使用了6个客户端与服务器进行通信,使用这种方式模拟多线程服务器的通信,服务器端结果如下:

请求连接服务器Thread-0
请求连接服务器Thread-1
请求连接服务器Thread-2
请求连接服务器Thread-3
请求连接服务器Thread-4
请求连接服务器Thread-5

4.分析结果

对上述结果进行分析,发现被使用过的线程任务执行完毕后就死亡了,不能被再次使用,下一个客户端对服务器发起连接时将使用新new出来的Thread,旧的线程资源其实已经使用完了但是无法再次调用start()使用,这要会导致资源浪费;因此我对代码进行了一些修改,同时开启多路线程,使得每个线程内部的run()方法套上while(true)循环使得线程永不终止,将服务器的侦听器放到线程的内部,测试类只需向线程代码传递服务器的连接(套接字)对象即可;当客户端向服务器发送数据时选择进入一个线程,完成线程任务即可。

5.修改版本

代码如下

代码只修改服务器端多线程入口部分,客户端和服务器端主题代码与上述一致,此处不重复书写

public class TCPThreadServerDemo {

    public static void main(String[] args) throws IOException {

        ServerSocket server = new ServerSocket(9000);

		//创建多条线程,每条线程都会独立的侦听客户端发送来的消息;
        Thread thread0 = new Thread(new ThreadServer(server));

        Thread thread1 = new Thread(new ThreadServer(server));

        Thread thread2 = new Thread(new ThreadServer(server));

        thread0.start();
        thread1.start();
        thread2.start();

    }
}
public class ThreadServer implements Runnable{

    private ServerSocket server;

    public ThreadServer(ServerSocket server){
        this.server = server;
    }


    @Override
    public void run() {

        while (true){

            try {
            //将侦听方法写在了run()方法内,这样每条线程都可以侦听+执行服务器命令,并且都可以反复侦听,而不是像上面的方法,侦听到客户端消息后再创建新线程;
                Socket accept = server.accept();
                Server.ServerMethod(accept);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这样线程可以被复用,不像上面的方法每条线程只能执行一次通信任务;但是与此同时也出现了问题,如果现在没有客户端向服务器发送请求,但是三个线程还是同时开启着并且不断侦听,十分浪费,如何做到多线程时线程可以复用,同时又能做到无任务时线程可以关闭,侦听到任务时线程才开启。此时就需要使用线程池,当需要使用线程时,向线程池获取线程,此线程就被激活,试用结束后归还线程(线程不会死亡),线程进入空闲状态;这样使用后的线程可以被复用,同时又不会在无任务状态下使多个线程持续运行,解决了上述两种服务器端多线程通信的两个矛盾点;

6.采用线程池的多线程服务器端

代码如下

省略上面已有的客户端和服务器端主程序
这展示被修改部分的代码

public class TCPThreadServerDemo {

    public static void main(String[] args) throws IOException {

        //创建线程池对象,线程池线程容量为3
        ExecutorService service = Executors.newFixedThreadPool(3);

        ServerSocket server = new ServerSocket(9000);


        while (true) {
            Socket accept = server.accept();

//            Thread thread = new Thread(new ThreadServer(accept));
			//提交线程任务,从线程池中获取线程
            service.submit(new ThreadServer(accept));
            //System.out.println(service);

        }
    }
}
public class ThreadServer implements Runnable{

    private Socket accept;

    public ThreadServer(Socket accept){
        this.accept = accept;
    }

    @Override
    public void run() {

            try {
                Server.ServerMethod(accept);
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
}

测试使用了7个客户端与服务器进行通信,服务器端结果如下:

请求连接服务器pool-1-thread-1
请求连接服务器pool-1-thread-2
请求连接服务器pool-1-thread-3
请求连接服务器pool-1-thread-1
请求连接服务器pool-1-thread-2
请求连接服务器pool-1-thread-3
请求连接服务器pool-1-thread-1

上述是初学者的个人想法,如果有任何问题或不同观点,或者我写的有错,都可以提出,大家一起讨论,不过请轻点喷 ^ - ^ !