首页   >   代码编程

Java Socket编写基于TCP协议的简易聊天室

Java提供了Socket套接字来实现网络编程,对TCP和UDP协议都有很好的的支持,在学习的时候,写的最多的可能就是聊天室了,很简单,但是能很好的将Socket和多线程结合起来。

好久没写了,这几天在复习Socket知识点的时候,顺手也写了一个基于TCP协议的简易聊天室:

服务端:负责消息转发和广播;

客户端:发送消息,接收消息;

服务器代码:

package com.wolffy.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by SongFei on 2019/3/24.
 */
public class Server {

    /**
     * 客户端集合
     */
    private static List<Socket> clients = new ArrayList<>();

    private void start() {
        try {
            // 开启服务,设置指定端口
            ServerSocket server = new ServerSocket(5555);
            System.out.println("服务开启,等待客户端连接中...");
            // 循环监听
            while (true) {
                // 等待客户端进行连接
                Socket client = server.accept();
                System.out.println("客户端[" + client.getRemoteSocketAddress() + "]连接成功,当前在线用户" + clients.size() + "个");
                // 将客户端添加到集合
                clients.add(client);
                // 每一个客户端开启一个线程处理消息
                new MessageListener(client).start();
            }
        } catch (IOException e) {
            // log
        }
    }

    /**
     * 消息处理线程,负责转发消息到聊天室里的人
     */
    class MessageListener extends Thread {

        // 将每个连接上的客户端传递进来,收消息和发消息
        private Socket client;

        // 将这几个变量抽出来公用,避免频繁new对象
        private OutputStream os;
        private PrintWriter pw;
        private InputStream is;
        private InputStreamReader isr;
        private BufferedReader br;

        public MessageListener(Socket socket) {
            this.client = socket;
        }

        @Override
        public void run() {
            try {
                // 每个用户连接上了,就发送一条系统消息(类似于广播)
                sendMsg(0, "[系统消息]:欢迎" + client.getRemoteSocketAddress() + "来到聊天室,当前共有" + clients.size() + "人在聊天");
                // 循环监听消息
                while (true) {
                    sendMsg(1, "[" + client.getRemoteSocketAddress() + "]:" + receiveMsg());
                }
            } catch (IOException e) {
                // log
            }
        }

        /**
         * 发送消息
         *
         * @param type 消息类型(0、系统消息;1、用户消息)
         * @param msg  消息内容
         * @throws IOException
         */
        private void sendMsg(int type, String msg) throws IOException {
            if (type != 0) {
                System.out.println("处理消息:" + msg);
            }
            for (Socket socket : clients) {
                if (type != 0 && socket == client) {
                    continue;
                }
                os = socket.getOutputStream();
                pw = new PrintWriter(os);
                pw.println(msg);// 这里需要特别注意,对方用readLine获取消息,就必须用print而不能用write,否则会导致消息获取不了
                pw.flush();
            }
        }

        /**
         * 接收消息
         *
         * @return 消息内容
         * @throws IOException
         */
        private String receiveMsg() throws IOException {
            is = client.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);
            return br.readLine();
        }
    }

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

}

服务器需要不停的监听客户端连接,并且负责消息的转发,本来还需要负责心跳,但是我这里偷懒就没有写。

客户端代码:

package com.wolffy.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;

/**
 * Created by SongFei on 2019/3/24.
 */
public class Client {

    private Socket server = null;

    private OutputStream os;
    private PrintWriter pw;
    private InputStream is;
    private InputStreamReader isr;
    private BufferedReader br;

    private void start() {
        try {
            // 连接服务器
            server = new Socket("127.0.0.1", 5555);
            System.out.println("连接服务器成功,身份证:" + server.getLocalSocketAddress());
            // 启动接受消息的线程
            new ReceiveMessageListener().start();
            // 启动发送消息的线程
            new SendMessageListener().start();
        } catch (SocketException e) {
            System.out.println("服务器" + server.getRemoteSocketAddress() + "嗝屁了");
        } catch (IOException e) {
            // log
        }
    }

    /**
     * 发送消息的线程
     */
    class SendMessageListener extends Thread {
        @Override
        public void run() {
            try {
                // 监听idea的console输入
                Scanner scanner = new Scanner(System.in);
                // 循环处理,只要有输入内容就立即发送
                while (true) {
                    sendMsg(scanner.next());
                }
            } catch (SocketException e) {
                System.out.println("服务器" + server.getRemoteSocketAddress() + "嗝屁了");
            } catch (IOException e) {
                // log
            }
        }
    }

    /**
     * 接受消息的线程
     */
    class ReceiveMessageListener extends Thread {
        @Override
        public void run() {
            try {
                // 循环监听,除非掉线,或者服务器宕机了
                while (true) {
                    System.out.println(receiveMsg());
                }
            } catch (SocketException e) {
                System.out.println("服务器" + server.getRemoteSocketAddress() + "嗝屁了");
            } catch (IOException e) {
                // log
            }
        }
    }

    /**
     * 发送消息
     *
     * @param msg 消息内容
     * @throws IOException
     */
    private void sendMsg(String msg) throws IOException {
        os = server.getOutputStream();
        pw = new PrintWriter(os);
        pw.println(msg);// 这里需要特别注意,对方用readLine获取消息,就必须用print而不能用write,否则会导致消息获取不了
        pw.flush();
    }

    /**
     * 接受消息
     *
     * @return 消息内容
     * @throws IOException
     */
    private String receiveMsg() throws IOException {
        is = server.getInputStream();
        isr = new InputStreamReader(is);
        br = new BufferedReader(isr);
        return br.readLine();
    }

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

}

客户端就简单许多,它不需要消息的转发,只仅仅是消息的发送和接受即可。

服务器代码运行效果图如下:

Java Socket编写基于TCP协议的简易聊天室

客户端代码运行效果图如下:

Java Socket编写基于TCP协议的简易聊天室

如果服务器关闭或宕机了,需要通知到客户端:

Java Socket编写基于TCP协议的简易聊天室

监听客户端下线的实现方式有两种:

1、允许输入指定消息退出,比如:输入exit下线;

2、利用TCP的keepAliave和timeOut来判定是否下限(心跳机制在服务器端);

3、启动单独的 线程来监听心跳,如果client在指定的时间没有心跳,就直接将其剔除下线(心跳机制在客户端);

上述三种方式中,第二种需要谨慎使用,因为服务器要负责消息的转发,压力本来就很大了,如果再将发送频率很高的心跳也放到服务器上,用户量上来之后,服务器搞不好会扛不住这个压力,所以最好还是使用一和三,这种由客户端发起请求,服务器的压力会小很多。

QQ群: 686430774  /  718410762

站长Q: 1347384268

如果文章有帮到你,可以考虑请博主喝杯咖啡!

分享到:

欢迎分享本文,转载请注明出处!

作者:不忘初心

发布时间:2019-03-27

永久地址:https://www.jiweichengzhu.com/article/ac533cbc9776469d84263e9105a65e1e

评论