如何使用套接字在Java中创建聊天控制台应用程序

 

Nam Ha Minh撰写  

最后更新时间为2019年7月18日|   打印  电子邮件


 

在此Java网络编程教程中,您将学习如何使用Socket编程在Java中创建聊天应用程序。提供了源代码供您下载。

 

 

 

1. Java聊天应用程序概述

您将要构建的Java Chat应用程序是一个从命令行启动的控制台应用程序。服务器和客户端可以在同一网络(例如局域网(LAN))中的不同计算机上运行。

 

可以有多个客户端连接到服务器,并且它们可以彼此聊天,就像在每个人都可以看到其他用户的消息的聊天室中一样。为简单起见,两个用户之间没有私人聊天。

 

连接到服务器后,用户必须提供其姓名才能进入聊天。服务器将当前在线用户的列表发送给新用户。

 

当有新用户到达时和有用户离开时,都会通知每个用户。每封邮件均以用户名作为前缀,以跟踪谁发送了邮件。

 

最后,用户说“再见”退出聊天。

 

该应用程序包括两部分:服务器和客户端。每个部分都可以在单独的计算机上独立运行。

 

现在,让我们详细了解如何编写此Java聊天应用程序的代码。

 


 

 

 

2.创建聊天服务器程序

该服务器由两个类实现:ChatServer UserThread

 

的ChatServer类启动服务器,侦听特定端口上。连接新客户端后,将创建UserThread实例来为该客户端提供服务。由于每个连接都是在单独的线程中处理的,因此服务器能够同时处理多个客户端。

 

以下是ChatServer类的源代码:

 

1个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18岁
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65岁
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package net.codejava.networking.chat.server;
 
import java.io.*;
import java.net.*;
import java.util.*;
 
/**
 * This is the chat server program.
 * Press Ctrl + C to terminate the program.
 *
 * @author www.codejava.net
 */
public class ChatServer {
    private int port;
    private Set<String> userNames = new HashSet<>();
    private Set<UserThread> userThreads = new HashSet<>();
 
    public ChatServer(int port) {
        this.port = port;
    }
 
    public void execute() {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
 
            System.out.println("Chat Server is listening on port " + port);
 
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("New user connected");
 
                UserThread newUser = new UserThread(socket, this);
                userThreads.add(newUser);
                newUser.start();
 
            }
 
        catch (IOException ex) {
            System.out.println("Error in the server: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
 
    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Syntax: java ChatServer <port-number>");
            System.exit(0);
        }
 
        int port = Integer.parseInt(args[0]);
 
        ChatServer server = new ChatServer(port);
        server.execute();
    }
 
    /**
     * Delivers a message from one user to others (broadcasting)
     */
    void broadcast(String message, UserThread excludeUser) {
        for (UserThread aUser : userThreads) {
            if (aUser != excludeUser) {
                aUser.sendMessage(message);
            }
        }
    }
 
    /**
     * Stores username of the newly connected client.
     */
    void addUserName(String userName) {
        userNames.add(userName);
    }
 
    /**
     * When a client is disconneted, removes the associated username and UserThread
     */
    void removeUser(String userName, UserThread aUser) {
        boolean removed = userNames.remove(userName);
        if (removed) {
            userThreads.remove(aUser);
            System.out.println("The user " + userName + " quitted");
        }
    }
 
    Set<String> getUserNames() {
        return this.userNames;
    }
 
    /**
     * Returns true if there are other users connected (not count the currently connected user)
     */
    boolean hasUsers() {
        return !this.userNames.isEmpty();
    }
}

如您所见,ChatServer类具有两个Set集合,以跟踪所连接客户端的名称和线程。使用Set是因为它不允许重复且元素的顺序无关紧要:

 

1个
2
private Set<String> userNames = new HashSet<>();
private Set<UserThread> userThreads = new HashSet<>();

ChatServer类中的一种重要方法是broadcast(),它可以将消息从一个客户端传递到所有其他客户端:

 

1个
2
3
4
5
6
7
void broadcast(String message, UserThread excludeUser) {
    for (UserThread aUser : userThreads) {
        if (aUser != excludeUser) {
            aUser.sendMessage(message);
        }
    }
}

UserThread类是负责读取从客户端发送的消息和广播消息发送给所有其他客户端。首先,它将在线用户列表发送给新用户。然后,它读取用户名并通知其他用户有关新用户的信息。

 

以下代码属于UserThread类:

 

1个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18岁
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65岁
66
67
68
69
70
71
72
73
74
75
76
77
package net.codejava.networking.chat.server;
 
import java.io.*;
import java.net.*;
import java.util.*;
 
/**
 * This thread handles connection for each connected client, so the server
 * can handle multiple clients at the same time.
 *
 * @author www.codejava.net
 */
public class UserThread extends Thread {
    private Socket socket;
    private ChatServer server;
    private PrintWriter writer;
 
    public UserThread(Socket socket, ChatServer server) {
        this.socket = socket;
        this.server = server;
    }
 
    public void run() {
        try {
            InputStream input = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
 
            OutputStream output = socket.getOutputStream();
            writer = new PrintWriter(output, true);
 
            printUsers();
 
            String userName = reader.readLine();
            server.addUserName(userName);
 
            String serverMessage = "New user connected: " + userName;
            server.broadcast(serverMessage, this);
 
            String clientMessage;
 
            do {
                clientMessage = reader.readLine();
                serverMessage = "[" + userName + "]: " + clientMessage;
                server.broadcast(serverMessage, this);
 
            while (!clientMessage.equals("bye"));
 
            server.removeUser(userName, this);
            socket.close();
 
            serverMessage = userName + " has quitted.";
            server.broadcast(serverMessage, this);
 
        catch (IOException ex) {
            System.out.println("Error in UserThread: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
 
    /**
     * Sends a list of online users to the newly connected user.
     */
    void printUsers() {
        if (server.hasUsers()) {
            writer.println("Connected users: " + server.getUserNames());
        else {
            writer.println("No other users connected");
        }
    }
 
    /**
     * Sends a message to the client.
     */
    void sendMessage(String message) {
        writer.println(message);
    }
}

然后进入从用户读取消息并将其发送给所有其他用户的循环,直到用户发送“再见”以表明他或她将要退出。最后,它通知其他用户该用户的断开连接并关闭连接。

 

 

 

3.创建聊天客户端程序

客户端由三个类实现:ChatClientReadThreadWriteThread

 

ChatClient启动客户端程序,连接到由指定服务器的主机名/ IP地址和端口号。建立连接后,它将创建并启动两个线程ReadThreadWriteThread

 

这是ChatClient类的源代码:

 

1个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18岁
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package net.codejava.networking.chat.client;
 
import java.net.*;
import java.io.*;
 
/**
 * This is the chat client program.
 * Type 'bye' to terminte the program.
 *
 * @author www.codejava.net
 */
public class ChatClient {
    private String hostname;
    private int port;
    private String userName;
 
    public ChatClient(String hostname, int port) {
        this.hostname = hostname;
        this.port = port;
    }
 
    public void execute() {
        try {
            Socket socket = new Socket(hostname, port);
 
            System.out.println("Connected to the chat server");
 
            new ReadThread(socket, this).start();
            new WriteThread(socket, this).start();
 
        catch (UnknownHostException ex) {
            System.out.println("Server not found: " + ex.getMessage());
        catch (IOException ex) {
            System.out.println("I/O Error: " + ex.getMessage());
        }
 
    }
 
    void setUserName(String userName) {
        this.userName = userName;
    }
 
    String getUserName() {
        return this.userName;
    }
 
 
    public static void main(String[] args) {
        if (args.length < 2return;
 
        String hostname = args[0];
        int port = Integer.parseInt(args[1]);
 
        ChatClient client = new ChatClient(hostname, port);
        client.execute();
    }
}

ReadThread负责从服务器读取输入并将其连续打印到控制台,直到客户端断开连接。此类的实现如下:

 

1个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18岁
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package net.codejava.networking.chat.client;
 
import java.io.*;
import java.net.*;
 
/**
 * This thread is responsible for reading server's input and printing it
 * to the console.
 * It runs in an infinite loop until the client disconnects from the server.
 *
 * @author www.codejava.net
 */
public class ReadThread extends Thread {
    private BufferedReader reader;
    private Socket socket;
    private ChatClient client;
 
    public ReadThread(Socket socket, ChatClient client) {
        this.socket = socket;
        this.client = client;
 
        try {
            InputStream input = socket.getInputStream();
            reader = new BufferedReader(new InputStreamReader(input));
        catch (IOException ex) {
            System.out.println("Error getting input stream: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
 
    public void run() {
        while (true) {
            try {
                String response = reader.readLine();
                System.out.println("\n" + response);
 
                // prints the username after displaying the server's message
                if (client.getUserName() != null) {
                    System.out.print("[" + client.getUserName() + "]: ");
                }
            catch (IOException ex) {
                System.out.println("Error reading from server: " + ex.getMessage());
                ex.printStackTrace();
                break;
            }
        }
    }
}

WriteThread负责从用户读取输入,并将其发送到服务器,不断直到用户输入“再见”结束聊天。此类的实现如下:

 

1个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18岁
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package net.codejava.networking.chat.client;
 
import java.io.*;
import java.net.*;
 
/**
 * This thread is responsible for reading user's input and send it
 * to the server.
 * It runs in an infinite loop until the user types 'bye' to quit.
 *
 * @author www.codejava.net
 */
public class WriteThread extends Thread {
    private PrintWriter writer;
    private Socket socket;
    private ChatClient client;
 
    public WriteThread(Socket socket, ChatClient client) {
        this.socket = socket;
        this.client = client;
 
        try {
            OutputStream output = socket.getOutputStream();
            writer = new PrintWriter(output, true);
        catch (IOException ex) {
            System.out.println("Error getting output stream: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
 
    public void run() {
 
        Console console = System.console();
 
        String userName = console.readLine("\nEnter your name: ");
        client.setUserName(userName);
        writer.println(userName);
 
        String text;
 
        do {
            text = console.readLine("[" + userName + "]: ");
            writer.println(text);
 
        while (!text.equals("bye"));
 
        try {
            socket.close();
        catch (IOException ex) {
 
            System.out.println("Error writing to server: " + ex.getMessage());
        }
    }
}

同时运行这两个线程的原因是,读取操作始终会阻塞当前线程(既从命令行读取用户的输入,又通过网络读取服务器的输入)。这意味着如果当前线程正在等待用户的输入,则无法从服务器读取输入。

 

因此,使用两个单独的线程来使客户端响应:它可以显示其他用户的消息,同时读取当前用户的消息。

 

这就是聊天应用程序的设计方式。有关更多详细信息,您可以阅读提供的源代码中的注释。但是没有太多注释,因为该代码是不言自明的。

 

 

 

4.如何运行聊天服务器

从命令行运行服务器程序时,需要指定端口号。例如:

 

1个
java ChatServer 8989

这将启动服务器侦听端口号8989,一旦启动,您将在服务器中看到以下输出:

 

1个
Chat Server is listening on port 8989

服务器永远在等待新的客户端,因此您必须按Ctrl + C终止它。

 

 

 

5.如何运行聊天客户端

要运行客户端,您需要在命令行中指定服务器的主机名/ IP地址和端口号。例如:

 

1个
java ChatClient localhost 8989

这告诉客户端在端口8989的本地主机上连接到服务器。然后您在服务器的控制台中看到以下消息:

 

1个
New user connected

在客户的控制台中:

 

1个
2
Connected to chat server
No other users connected

您会看到,服务器告诉客户端连接了多少个用户,但是此时没有用户。然后程序要求输入用户名:

 

1个
Enter your name:_

输入用户名,例如John,然后您可以开始聊天:

 

1个
2
Enter your name: John
[John]:_

现在,让我们启动第二个使用用户名Peter的客户端。这时,您会看到服务器告诉您有一个在线用户是John:

 

1个
Connected users: [John]

用户John收到有关新用户Peter的通知:

 

1个
New user connected: Peter

键入一些来自John和Peter的消息,您会看到每个用户都能看到对方的消息,就像在聊天室中聊天一样。

 

现在,John要退出,所以他键入“ bye”,客户端程序终止,您将在服务器控制台中看到以下输出:

 

1个
The user John quitted

Peter还从服务器收到一条消息:

 

1个
John has quitted.

基本上,这就是聊天应用程序的运行方式。您可以在更多客户端上对其进行测试,并且应用程序仍可以平稳运行。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值