服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Java Socket+多线程实现多人聊天室功能

Java Socket+多线程实现多人聊天室功能

2021-10-11 10:29小刚真的皮 Java教程

这篇文章主要为大家详细介绍了Java Socket+多线程实现多人聊天室功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了Java Socket+多线程实现多人聊天室的具体代码,供大家参考,具体内容如下

思路简介

分为客户端和服务器两个类,所有的客户端将聊的内容发送给服务器,服务器接受后,将每一条内容发送给每一个客户端,客户端再显示在终端上。

客户端设计

客户端包含2个线程,1个用来接受服务器的信息,再显示,1个用来接收键盘的输入,发送给服务器。

?
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
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
 
public class WeChatClient {  //WeChat的客户端类
    private Socket client;
    private String name;
    private InputStream in;
    private OutputStream out;
    private MassageSenter massageSenter;
    private MassageGeter massageGeter;
    class MassageGeter extends Thread{  //一个子线程类,用于客户端接收消息
        MassageGeter() throws IOException{
            in = client.getInputStream();
        }
        @Override
        public void run() {
            int len;
            byte[] bytes = new byte[1024];
            try {
                while ((len = in.read(bytes)) != -1) { //此函数是阻塞的
                    System.out.println(new String(bytes,0,len, StandardCharsets.UTF_8));
                }
            }catch (IOException e){
                System.out.println(e.toString());
            }
            System.out.println("Connection interruption");
        }
    }
    class MassageSenter extends Thread{  //一个子线程类,用于发送消息给服务器
        MassageSenter() throws IOException{
            out = client.getOutputStream();
        }
 
        @Override
        public void run() {
            Scanner scanner = new Scanner(System.in);
            try {
                while (scanner.hasNextLine()) { //此函数为阻塞的函数
                    String massage = scanner.nextLine();
                    out.write((name + " : " + massage).getBytes(StandardCharsets.UTF_8));
                    if(massage.equals("//exit"))
                        break;
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
 
    WeChatClient(String name, String host, int port) throws IOException {//初始化,实例化发送和接收2个线程
        this.name = name;
        client = new Socket(host,port);
        massageGeter = new MassageGeter();
        massageSenter = new MassageSenter();
 
    }
 
    void login() throws IOException{//登录时,先发送名字给服务器,在接收到服务器的正确回应之后,启动线程
        out.write(name.getBytes(StandardCharsets.UTF_8));
        byte[] bytes = new byte[1024];
        int len;
        len = in.read(bytes);
        String answer = new String(bytes,0,len, StandardCharsets.UTF_8);
        if(answer.equals("logined!")) {
            System.out.println("Welcome to WeChat! "+name);
            massageSenter.start();
            massageGeter.start();
            try {
                massageSenter.join();//join()的作用是等线程结束之后再继续执行主线程(main)
                massageGeter.join();
            }catch (InterruptedException e){
                System.err.println(e.toString());
            }
 
        }else{
            System.out.println("Server Wrong");
        }
        client.close();
    }
 
 
    public static void main(String[] args) throws IOException{//程序入口
        String host = "127.0.0.1";
        WeChatClient client = new WeChatClient("Uzi",host,7777);
        client.login();
    }
 
}

服务器设计

服务器包含3个线程类,端口监听线程,客户端接收信息线程,发送信息线程。

服务器类还包含并维护着一个已经连接的用户列表,和一个待发送信息列表。

服务器有一个负责监听端口的线程,此线程在接收到客户端的连接请求后,将连接的客户端添加进用户列表;并为每一个连接的客户端实例化一个接受信息的线程类,从各个客户端接收员信息,并存入待发送信息列表。

发送信息线程查看列表是否为空,若不为空,则将里面的信息发送给用户列表的每一个用户。

?
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
 
public class WeChatServer {
    private ServerSocket server;
    private ArrayList<User> users;//用户列表
    private ArrayList<String> massages;//待发送消息队列
    private Listener listener;
    private MassageSenter massageSenter;
 
 
    class User{  //用户类,包含用户的登录id和一个输出流
        String name;
        OutputStream out;
        User(String name,OutputStream out){
            this.name = name;
            this.out = out;
        }
 
        @Override
        public String toString() {
            return name;
        }
    }
 
    private static String GetMassage(InputStream in) throws IOException{//从一个输入流接收一个字符串
        int len;
        byte[] bytes = new byte[1024];
        len = in.read(bytes);
        return new String(bytes,0,len,StandardCharsets.UTF_8);
    }
    private void UserList(){  //列出当前在线用户,调试用
        for(User user : users)
            System.out.println(user);
    }
 
    class Listener extends Thread{ //监听线程类,负则监听是否有客户端连接
        @Override
        public void run() {
            try {
                while (true) {
                    Socket socket = server.accept();//此函数是阻塞的
                    InputStream in = socket.getInputStream();
                    String name = GetMassage(in);//获取接入用户的name
                    System.out.println(name +" has connected");
                    massages.add(name+" has joined just now!!");//向聊天室报告用户连入的信息
                    OutputStream out = socket.getOutputStream();
                    out.write("logined!".getBytes(StandardCharsets.UTF_8));//发送成功建立连接的反馈
                    User user = new User(name,out);
                    users.add(user);//添加至在线用户列表
                    MassageListener listener = new MassageListener(user,in);//创建用于接收此用户信息的线程
                    listener.start();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    class MassageListener extends Thread{ //接收线程类,用于从一个客户端接收信息,并加入待发送列表
        private User user;
        private InputStream in;
        MassageListener(User user,InputStream in){
            this.user = user;
            this.in = in;
        }
 
        @Override
        public void run() {
            try {
                while (true){
                    String massage = GetMassage(in);
                    System.out.println("GET MASSAGE  "+massage);
                    if(massage.contains("//exit")){ //       "/exit" 是退出指令
                        break;
                    }
                    massages.add(massage);
                }//用户退出有两种形式,输入 “//exit” 或者直接关闭程序
                in.close();
                user.out.close();
 
            }catch (IOException e){//此异常是处理客户端异常关闭,即GetMassage(in)调用会抛出异常,因为in出入流已经自动关闭
                e.printStackTrace();
            }finally {
                System.out.println(user.name+" has exited!!");
                massages.add(user.name+" has exited!!");
                users.remove(user);//必须将已经断开连接的用户从用户列表中移除,否则会在发送信息时产生异常
                System.out.println("Now the users has");
                UserList();
            }
 
        }
    }
    private synchronized void SentToAll(String massage)throws IOException{//将信息发送给每一个用户,加入synchronized修饰,保证在发送时,用户列表不会被其他线程更改
        if(users.isEmpty())
            return;
        for(User user : users){
            user.out.write(massage.getBytes(StandardCharsets.UTF_8));
        }
    }
 
    class MassageSenter extends Thread{//消息发送线程
 
        @Override
        public void run() {
            while(true){
                try{
                    sleep(1);//此线程中没有阻塞的函数,加入沉睡语句防止线程过多抢占资源
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                if(!massages.isEmpty()){
                    String massage = massages.get(0);
                    massages.remove(0);
                    try {
                        SentToAll(massage);
                    }catch (IOException e){
                        e.printStackTrace();
                    }
 
                }
            }
        }
    }
 
    WeChatServer(int port) throws IOException {  //初始化
        server = new ServerSocket(port);
        users = new ArrayList<>();
        massages = new ArrayList<>();
        listener = new Listener();
        massageSenter = new MassageSenter();
    }
 
    private void start(){ //线程启动
        listener.start();
        massageSenter.start();
    }
 
    public static void main(String[] args) throws IOException{
        WeChatServer server = new WeChatServer(7777);
        server.start();
    }
 
}

总结

之所以需要多线程编程,是因为有的函数是阻塞的,例如

?
1
2
3
while ((len = in.read(bytes)) != -1) { //此函数是阻塞的
    System.out.println(new String(bytes,0,len, StandardCharsets.UTF_8));
}
?
1
2
3
4
5
6
while (scanner.hasNextLine()) { //此函数为阻塞的函数
        String massage = scanner.nextLine();
        out.write((name + " : " + massage).getBytes(StandardCharsets.UTF_8));
        if(massage.equals("//exit"))
     break;
  }
?
1
Socket socket = server.accept();//此函数是阻塞的

这些阻塞的函数是需要等待其他的程序,例如scanner.hasNextLine()需要等待程序员的输入才会返回值,in.read需要等待流的另一端传输数据,使用多线程就可以在这些函数处于阻塞状态时,去运行其他的线程。

所以,多线程编程的关键便是那些阻塞的函数。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/qq_40608763/article/details/90755336

延伸 · 阅读

精彩推荐
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    这篇文章主要介绍了Java使用SAX解析xml的示例,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程20个非常实用的Java程序代码片段

    20个非常实用的Java程序代码片段

    这篇文章主要为大家分享了20个非常实用的Java程序片段,对java开发项目有所帮助,感兴趣的小伙伴们可以参考一下 ...

    lijiao5352020-04-06
  • Java教程Java实现抢红包功能

    Java实现抢红包功能

    这篇文章主要为大家详细介绍了Java实现抢红包功能,采用多线程模拟多人同时抢红包,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙...

    littleschemer13532021-05-16
  • Java教程升级IDEA后Lombok不能使用的解决方法

    升级IDEA后Lombok不能使用的解决方法

    最近看到提示IDEA提示升级,寻思已经有好久没有升过级了。升级完毕重启之后,突然发现好多错误,本文就来介绍一下如何解决,感兴趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java BufferWriter写文件写不进去或缺失数据的解决

    Java BufferWriter写文件写不进去或缺失数据的解决

    这篇文章主要介绍了Java BufferWriter写文件写不进去或缺失数据的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望...

    spcoder14552021-10-18
  • Java教程小米推送Java代码

    小米推送Java代码

    今天小编就为大家分享一篇关于小米推送Java代码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    富贵稳中求8032021-07-12
  • Java教程Java8中Stream使用的一个注意事项

    Java8中Stream使用的一个注意事项

    最近在工作中发现了对于集合操作转换的神器,java8新特性 stream,但在使用中遇到了一个非常重要的注意点,所以这篇文章主要给大家介绍了关于Java8中S...

    阿杜7472021-02-04
  • Java教程xml与Java对象的转换详解

    xml与Java对象的转换详解

    这篇文章主要介绍了xml与Java对象的转换详解的相关资料,需要的朋友可以参考下...

    Java教程网2942020-09-17