【Java】使用socket实现控制台多线程聊天室

本文介绍了一个基于BIO的socket+多线程控制台聊天室的实现,包括github代码地址。该聊天室允许任意客户端发送的消息被转发给所有在线客户端,实现了基本的聊天室功能。同时,介绍了socket通信原理、服务端与客户端的连接过程,以及多线程和线程池的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

刚学习了socket编程和多线程相关知识,为了巩固下知识,动手实现了一个基于BIO的socket+多线程的控制台聊天室。

github代码地址:传送门

功能介绍

首先启动server端,然后启动client端。任意一个client发送给server的消息都将会被转发给所有在线的client,实现了一个聊天室功能。

client发送exit指令可以下线,所有的client端,以及server端都会收到它的下线通知。

原理

socket 通信是基于TCP/IP 网络层上的一种传送方式,有关TCP的介绍前面有写过(TCP概述),这里不再赘述。

首先,服务端初始化ServerSocket,然后对指定的端口(这里是65000)进行绑定,接着对端口进行监听,通过调用accept方法阻塞,此时,如果客户端有一个socket连接到服务端,那么服务端通过监听和accept方法可以与客户端进行连接,就创建了一个客户端线程。

然后就是客户端的启动和连接了,在服务端socket启动之后,启动客户端连接ServerSocket,这里实现了多线程,可以同时连接多个客户端。这里多线程方面使用了线程池,线程池的作用是:

  • 线程复用,减少创建线程耗时;
  • 防止短时间内高并发,指定线程池大小,超过数量将等待,防止短时间创建大量线程导致资源耗尽,服务挂掉。

代码

客户端:


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

public class TCPClient {

    Socket clientSocket = null;
    BufferedReader reader = null;
    private static final int PORT = 65000;

    public static void main(String[] args) throws IOException {
        new TCPClient();
        System.exit(0);  //client退出后自动结束此进程
    }

	//客户端逻辑写在构造方法中
    public TCPClient() {
        clientSocket = new Socket();
        try {
            clientSocket.connect(new InetSocketAddress("localhost", PORT));
            new getInputStream().start();
            System.out.println("Client started!");
            reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            String receiveStr = null;
            while ((receiveStr = reader.readLine()) != null) {
                //接收服务端发来的数据
                if (receiveStr.equals("exit")) {
                //如果发送exit,服务端会回传exit,这里判断回传的是不是exit。是的话就结束此线程
                    break;
                }
                System.out.println(receiveStr);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        	//exit -> 关闭客户端套接字、线程、BufferReader
            try {
                if (reader != null) {
                    reader.close();
                }
                if (clientSocket != null) {
                    clientSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

	//客户端线程,用于向服务端写入从客户端控制台输入的数据
    class getInputStream extends Thread {
        @Override
        public void run() {
            BufferedReader is = null;
            PrintWriter os = null;
            try {
                is = new BufferedReader(new InputStreamReader(System.in));
                os = new PrintWriter(new BufferedOutputStream(clientSocket.getOutputStream()));
                String str = null;
                while ((str = is.readLine()) != null) {
                    os.println(str);
                    os.flush();
                }

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                    if (os != null) {
                        os.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务端:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TCPServer {

    static ArrayList<Socket> clients = new ArrayList<>();

	//接受客户端信息线程,每个客户端对应一个,用来获取客户端发送来的数据并广播到所有客户端
    static class HandleMsg implements Runnable {

        Socket clientSocket;

        PrintWriter os = null;
        String msg = null;

        public HandleMsg(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }
        @Override
        public void run() {
            clients.add(clientSocket);  //添加进客户端套接字池
            //客户端刚连接时显示其端口、当前人数,并广播
            msg = "欢迎【" + clientSocket.getRemoteSocketAddress() + "】加入聊天室!聊天室当前人数为:" + clients.size();
            sendMsg(msg);
            BufferedReader is = null;
            try {
                //从InputStream中读取客户端发来的数据
                is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

                while ((msg = is.readLine()) != null) {
                    if (msg.equals("exit")) {
                        //客户端发送exit退出
                        msg = "【" + clientSocket.getRemoteSocketAddress() + "】下线了.";
                        sendMsg(msg);
                        System.out.println(msg);
                        os = new PrintWriter(new BufferedOutputStream(clientSocket.getOutputStream()));
                        os.println("exit");
                        os.flush();
                        break;
                    } else {
                        msg = "【" + clientSocket.getRemoteSocketAddress() + "】说: " + msg;
                        sendMsg(msg);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                    if (os != null) {
                        os.close();
                    }
                    clientSocket.close();
                    clients.remove(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

		//广播发送方法
        void sendMsg(String msg) {
            for (int i = clients.size() - 1; i >= 0; --i) {
                try {
                    os = new PrintWriter(new BufferedOutputStream(clients.get(i).getOutputStream()));
                    os.println(msg);
                    os.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        ServerSocket ss = null;
        Socket clientSocket = null;
        ExecutorService tp = Executors.newCachedThreadPool();
        ss = new ServerSocket(65000);
        System.out.println("Server " + ss.getLocalPort() + " started!");
        while (true) {
            try {
            	//阻塞,直到有客户端连接
                clientSocket = ss.accept();
                System.out.println(clientSocket.getRemoteSocketAddress() + " connect!");
                //为客户端创建一个接收信息的线程
                tp.execute(new HandleMsg(clientSocket));
            } catch (IOException e) {
                System.out.println(e);
            }
        }


    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值