Java如何实现客户端与服务器的多线程通信

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

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


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.服务器代码


服务器主要功能服务器需要接收客户端发来的消息。
接收到消息后,服务器需要向客户端发送反馈信息。
由于需要为服务器创建多线程,因此服务器实现的程序需要放在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


 

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现TCP客户端之间多线程通信需要以下步骤: 1. 创建Socket连接:使用Socket类创建客户端Socket对象,并指定服务器的IP地址和端口号。 2. 创建输入输出流:创建客户端的输入输出流,以便进行数据的读写。 3. 实现多线程通信:使用多线程技术实现客户端之间的通信,每个客户端启动一个线程来处理与其他客户端通信。 4. 消息传输:对于每个客户端,通过输出流把消息发送到服务器服务器再把消息发送给其他客户端。 5. 关闭连接:当客户端不再需要连接时,需要关闭Socket连接,释放资源。 实现聊天页面的UI需要以下步骤: 1. 设计UI界面:设计一个聊天窗口,包含输入框、发送按钮以及显示框。 2. 实现UI控件:使用Swing或JavaFX等工具实现UI控件,添加事件监听器。 3. 实现发送消息:当用户在输入框中输入消息并点击发送按钮时,将消息发送到服务器。 4. 实现接收消息:当用户接收到其他客户端发送的消息时,在显示框中显示消息。 5. 处理异常:在连接服务器时,可能会出现各种异常,需要在程序中进行处理。 注意事项: 1. 多线程通信需要注意线程安全问题,避免数据竞争和死锁。 2. UI设计需要考虑用户体验和美观度,应该尽可能地简洁明了。 3. 对于网络连接异常,应该给出友好的提示信息,避免用户因网络问题而感到困扰。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值