通过TCP网络协议实现控制台多人聊天功能,另附私聊@功能。(java)

何为TCP?我们应该首先知道这一个概念,TCP是一种可靠的、基于连接的网络协议,它是面向字节流的,即从一个经常到另一个进程的二进制序列。每一条TCP连接需要两个端点,一个是接受消息的端口,我们通常叫它为服务端,和发送消息的端口,我们通常叫它为客户端。

客户端实现细聊

每一个用户在启动的时候就用了一个Socket,因为要实现多人聊天所以就需要给每一个用户添加一个用户线程,防止在实现过程中出现阻塞的问题。

用户线程实现Runable接口,在run()方法里面不断读取从服务端传来的消息,其代码如下所示:

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

public class ClientThread implements Runnable{

    private Socket s;
    BufferedReader br = null;
    public ClientThread(Socket s) throws IOException {
        this.s = s;
        br = new BufferedReader(new InputStreamReader(s.getInputStream()));
    }

    @Override
    public void run() {
        try{
            String content = null;
            while ((content =  br.readLine()) != null){
                System.out.println(content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

另外我在这里实现了3个客户,每一个客户都开启一个多线程,由于每个用户实现的代码相同,所以只贴出一份。
代码如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class Client1 {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1", 3000);
        ClientThread clientThread = new ClientThread(s);
        new Thread(clientThread).start();
        PrintStream ps = new PrintStream(s.getOutputStream());

        String line = null;
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("请输入姓名: ");
        line = br.readLine();
        ps.println("!" + line + "!");
        while (true){
            line = br.readLine();
            ps.println(line);
        }
    }
}

服务端实现细聊

首先我们需要定义一个User类,其作用就是给每一个客户(Socket)给定一个唯一标识姓名。代码如下:

import java.net.Socket;

public class User {
    private Socket socket;
    private String name;

    public User() {
    }

    public User(Socket socket, String name) {
        this.socket = socket;
        this.name = name;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "socket=" + socket +
                ", name='" + name + '\'' +
                '}';
    }
}

当服务器开启后,就会等待客户上线,然后连接客户,此时服务端就接收到了这一个用户,然后我们将这个客户(Socket)添加到User用户类,由于此时客户只是上线了,还未输入姓名,所以此时的每一个客户姓名都为null,当我们输入姓名的时候,再返回看看客户代码我们给用户的姓名做了一点小小的操作,就是添加了两个!!,所以!xxx!,这两个!里面的就是姓名,而我们就需要去判断是否为!开头以及结尾,若是就判断他是这个客户的姓名,然后我们再为每一个用户设置一个姓名。
代码如下:

//判断是否为用户姓名,如果是就设置这个用户的姓名
flag_name = content.startsWith("!") && content.endsWith("!");
flag_port = this.s.getPort() == user.getSocket().getPort();
if (flag_name && flag_port){
    //分割出用户姓名
    user.setName(content.substring(1, content.length() - 1));
}

然后我们需要定义一个currentUser来确定从当前控制台输入的这个用户是谁,原理是通过从端口号是否相等来判断出是否为是这一个currentUser。代码如下所示:

//获取当前用户传来的消息
if (!flag_name && flag_port){
    currentUser = user;
    message = user.getName() + ": " + content;
    System.out.println(message); //得到客户端传来的话
}

就这样我们就确定了当前输入消息的人是谁了,然后就可以不把该用户说的话传给自己啦。实现方法为遍历该系统中所有的用户,如果是currentUser就跳过,代码如下所示:

for (User user : users) {
    if (!user.equals(currentUser)) {
        PrintStream ps = new    PrintStream(user.getSocket().getOutputStream());
        ps.println(message);
   }
}

通过以上的代码就已经实现了多人聊天功能了,然后我们在实现私聊@功能吧。

私聊往往都是通过@来实现的,但是@+姓名这才是真正的私聊某人,但是还有一种情况那就是如果@+错误的名字,那么这也会发生错误。所以我们也要排出这种情况。
当我们确定了是否为私聊后我们才能去实现发送私聊消息这些操作。我们再来分析一波这个私聊功能,当一个用户@了某一个人XXX后发送的这句消息我们只能发送给被@的这个人XXX,然后再XXX用户可以看到是谁向他说话了,分析完毕,让我们来看看代码吧,代码如下所示:

if (message.contains(": @")){
   for (User value : users) {
       String username = value.getName();
       //私聊某人
       if (message.contains(": @" + username)) {
          privateName = username;
          //提取出私聊某人的内容
          privateMessage = message.split("@" + username)[1];
          flag_private = true;
       }
   }
// 通过私聊用户名获取该用户
for (User user : users) {
    if (user.getName().equals(privateName)) {
         privateUser = user;
    }
}
if (flag_private){
    assert privateUser != null;
    PrintStream ps = new PrintStream(privateUser.getSocket().getOutputStream());
    ps.println(message.split(":")[0] + ": "+ privateMessage);
}else {
    assert currentUser != null;
    PrintStream ps = new PrintStream(currentUser.getSocket().getOutputStream());
    ps.println("没有找到这个人嘞!!!");
}

服务器的具体功能已经实现了,若还未懂的话可以再看看下面的具体代码,如下所示:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.ArrayList;

public class ServerThread implements Runnable{
    Socket s = null;
    String message = null;

    BufferedReader br = null;

    public ServerThread() {
    }

    public ServerThread(Socket s) throws IOException {
        this.s = s;
        br = new BufferedReader(new InputStreamReader(s.getInputStream()));
    }

    @Override
    public void run() {
        try{
            String content = null;
            while((content = readFormClient()) != null){
                ArrayList<User> users = Server.socketList;
                User currentUser = null;    //当前用户

                boolean flag_name = false;
                boolean flag_port = false;

                for (User user : Server.socketList) {

                    //判断是否为用户姓名,如果是就设置这个用户的姓名
                    flag_name = content.startsWith("!") && content.endsWith("!");
                    flag_port = this.s.getPort() == user.getSocket().getPort();
                    if (flag_name && flag_port){
                        //分割出用户姓名
                        user.setName(content.substring(1, content.length() - 1));
                    }

                    //获取当前用户传来的消息
                    if (!flag_name && flag_port){
                        currentUser = user;
                        message = user.getName() + ": " + content;
                        System.out.println(message); //得到客户端传来的话
                    }
                }

                String privateMessage = null;   //私聊消息
                String privateName = null;      //私聊用户名
                User privateUser = null;        //私聊用户
                boolean flag_private = false;
                if (message != null){
                    //如果有@符号说明就私聊
                    if (message.contains(": @")){
                        for (User value : users) {
                            String username = value.getName();
                            //私聊某人
                            if (message.contains(": @" + username)) {
                                privateName = username;
                                //提取出私聊某人的内容
                                privateMessage = message.split("@" + username)[1];
                                flag_private = true;
                            }
                        }
                        // 通过私聊用户名获取该用户
                        for (User user : users) {
                            if (user.getName().equals(privateName)) {
                                privateUser = user;
                            }
                        }
                        if (flag_private){
                            assert privateUser != null;
                            PrintStream ps = new PrintStream(privateUser.getSocket().getOutputStream());
                            ps.println(message.split(":")[0] + ": "+ privateMessage);
                        }else {
                            assert currentUser != null;
                            PrintStream ps = new PrintStream(currentUser.getSocket().getOutputStream());
                            ps.println("没有找到这个人嘞!!!");
                        }
                    }else {
                        //传入当前用户给其他用户输入的消息
                        for (User user : users) {
                            if (!user.equals(currentUser)) {
                                PrintStream ps = new PrintStream(user.getSocket().getOutputStream());
                                ps.println(message);
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 读取用户发来的每一条消息
     * @return
     */
    private String readFormClient(){
        try{
            return br.readLine();
        }catch (IOException e){
            Server.socketList.remove(s);
        }
        return null;
    }
}

最后我们则需要在服务器里面一直开启上面这个服务器线程,另外还要定义一个ArrayList集合来装入上线的用户。代码如下所示:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class Server {
    //存储所有的客户
    public static ArrayList<User> socketList = new ArrayList<>();

    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(3000);
        while (true){
            Socket s = ss.accept();
            User user = new User(s, "");
            socketList.add(user);
            //为每一个客户添加一个服务端多线程
            new Thread(new ServerThread(s)).start();
        }

    }
}

就这样我们实现的通过TCP网络协议实现控制台多人聊天功能,另附私聊@功能就这样完成咯。

最后我们看一看实现的效果吧,首先我们先将服务器开着,等待用户连接,当用户连接了就给自己输入一个姓名,我在这里开启了三个用户。

java实现私聊_tcpip


java实现私聊_java实现私聊_02


java实现私聊_tcpip_03