java使用MulticastSocket实现基于广播的多人聊天室

时间:2022-06-29 22:40:27

使用multicastsocket实现多点广播:

(1)datagramsocket只允许数据报发给指定的目标地址,而multicastsocket可以将数据报以广播的方式发送到多个客户端。

(2)ip协议为多点广播提供了这批特殊的ip地址,这些ip地址的范围是:224.0.0.0至239.255.255.255..

(3)multicastsocket类时实现多点广播的关键,当multicastsocket把一个daragrampocket发送到多点广播的ip地址时,该数据报将会自动广播到加入该地址的所有multicastsocket。multicastsocket既可以将数据报发送到多点广播地址,也可以接收其他主机的广播信息。

(4)事实上,multicastsocket是datagramsocket的子类,也就是说,multicastsocket是特殊的datagramsocket。当要发送一个数据报时,可以使用随机端口创建multicastsocket,也可以在指定端口创建multicastsocket。multicastsocket提供了如下三个构造器:

public multicastsocket() 使用本机默认地址,随机端口来创建multicastsocket对象
public multicastsocket(int portnumber) 用本机默认地址,指定端口来创建multicastsocket对象
public multicastsocket(socketaddress bindaddr) 用指定ip地址,指定端口来创建multicastsocket对象

(5)创建multicastsocket对象后,还需要将multicastsocket加入到指定的多点广播地址。multicastsocket使用joingroup()方法加入指定组;使用leavegroup()方法脱离一个组。

joingroup(inetaddress multicastaddr) 将该multicastsocket加入到指定的多点广播地址

leavegroup(inetaddress multicastaddr) 将该multicastsocket离开指定的多点广播地址

(6)在某些系统中,可能有多个网络接口,这可能为多点广播带来问题,这时候程序需要在一个指定的网络接口上监听,通过调用setinterface()方法可以强制multicastsocket使用指定的网络接口‘也可以使用getinterface()方法查询multicastsocket监听的网络接口。

(7)如果创建仅仅用于发送数据报的multicastsocket对象,则使用默认地址,随机端口即可。但如果创建接收用的multicastsocket对象,'则该multicastsocket对象必须有指定端口,否则无法确定发送数据报的目标端口。

(8)multicastsocket用于发送接收数据报的方法与datagramsocket完全一样。但multicastsocket比datagramsocket多了一个settimetolive(int ttl)方法,该ttl用于设置数据报最多可以跨过多少个网络。
当ttl为0时,指定数据报应停留在本地主机
当ttl为1时,指定数据报发送到本地局域网
当ttl为32时,指定数据报发送到本站点的网络上
当ttl为64时,意味着数据报应该停留在本地区
当ttl为128时,意味着数据报应保留在本大洲
当ttl为255时,意味着数据报可以发送到所有地方
默认情况下,ttl值为1.

程序实例:

下面程序使用multicastsocket实现一个基于广播的多人聊天室。程序只需要一个multicastsocket,两个线程,其中multicastsocket既用于发送,也用于接收;一个线程负责键盘输入,并向multicastsocket发送数据;一个线程负责从multicastsocket中读取数据。

?
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
package com.talk;
import java.io.ioexception;
import java.net.datagrampacket;
import java.net.inetaddress;
import java.net.multicastsocket;
import java.util.scanner;
//让该类实现runnable接口,该类的实例可以作为线程的target
public class multicastsockettest implements runnable{
 
  //使用常量作为本程序多点广播的ip地址
  private static final string broadcast_ip="230.0.0.1";
  //使用常量作为本程序的多点广播的目的地端口
  public static final int broadcast_port=3000;
  //定义每个数据报大小最大为4kb
  private static final int data_len=4096;
  //定义本程序的multicastsocket实例
  private multicastsocket socket=null;
  private inetaddress broadcastaddress=null;
  private scanner scan=null;
 
  //定义接收网络数据的字节数组
  byte[] inbuff=new byte[data_len];
  //以指定字节数组创建准备接收数据的multicastsocket对象
  private datagrampacket inpacket =new datagrampacket(inbuff, inbuff.length);
 
  //定义一个用于发送的datagrampacket对象
  private datagrampacket outpacket=null;
 
  public void init() throws ioexception{
   //创建键盘输入流
   scanner scan=new scanner(system.in);
   //创建用于发送、接收数据的multicastsocket对象,由于该multicastsocket需要接收数据,所以有指定端口
   socket=new multicastsocket(broadcast_port);
   broadcastaddress=inetaddress.getbyname(broadcast_ip);
 
   //将该socket加入到指定的多点广播地址
   socket.joingroup(broadcastaddress);
   //设置本multicastsocket发送的数据报会被回送到自身
   socket.setloopbackmode(false);
 
   //初始化发送用的datagramsocket,它包含一个长度为0的字节数组
   outpacket =new datagrampacket(new byte[0], 0, broadcastaddress, broadcast_port);
 
   //启动本实例的run()方法作为线程执行体的线程
   new thread(this).start();
 
   //不断的读取键盘输入
   while(scan.hasnextline()){
    //将键盘输入的一行字符转换成字节数组
    byte [] buff=scan.nextline().getbytes();
    //设置发送用的datagrampacket里的字节数据
    outpacket.setdata(buff);
    //发送数据报
    socket.send(outpacket);
   }
   socket.close();
  }
 
  public void run() {
   // todo auto-generated method stub
 
   while(true){
    //读取socket中的数据,读到的数据放入inpacket所封装的字节组里
    try {
      socket.receive(inpacket);
      //打印从socket读取到的内容
      system.out.println("聊天信息:"+new string(inbuff,0,inpacket.getlength()));
    } catch (ioexception e) {
      // todo auto-generated catch block
      e.printstacktrace();
    }
 
    if(socket!=null){
      //让该socket离开多点ip广播地址
      try {
       socket.leavegroup(broadcastaddress);
       //关闭socket对象
       socket.close();
      } catch (ioexception e) {
       // todo auto-generated catch block
       e.printstacktrace();
      }
 
    }
 
    system.exit(1);
   }
  }
  public static void main(string[] args) {
   try {
    new multicastsockettest().init();
   } catch (ioexception e) {
    // todo auto-generated catch block
    e.printstacktrace();
   }
  }
}

下面将结合multicastsocket和datagramsocket开发一个简单的局域网即时通讯工具,局域网内每个用户启动该工具后,就可以看到该局域网内所有的在线用户,该用户也会被其他用户看到:

该程序的思路是:每个用户都启动两个socket,即multicastsocket和datagramsocket。其中multicastsocket会周期性的向230.0.0.1发送在线信息,且所有的multicastsocket都会加入到230.0.0.1这个多点广播ip中,这样每个用户都会收到其他用户的在线信息,如果系统在一段时间内没有收到某个用户广播的在线信息,则从用户列表中删除该用户。除此之外,该multicastsocket还用于向其他用户发送广播信息。

datagramsocket主要用于发送私聊信息,当用户收到其他用户广播来的datagramsocket时,即可获得该用户multicastsocket对应的socketaddress.这个socketaddress将作为发送私聊信息的重要依据。—本程序让multicastsocket在30000端口监听,而datagramsocket在30001端口监听,这样程序就可以根据其他用户广播来的datagrampacket得到他的datagramsocket所在的地址。

本系统提供了一个userinfo类,该类封装了用户名、图标、对应的socketaddress以及该用户对应的交谈窗口,失去联系的次数等信息:

?
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
package com.talk;
import java.net.socketaddress;
import com.bank.chatframe;
public class userinfo
{
  // 该用户的图标
  private string icon;
  // 该用户的名字
  private string name;
  // 该用户的mulitcastsocket所在的ip和端口
  private socketaddress address;
  // 该用户失去联系的次数
  private int lost;
  // 该用户对应的交谈窗口
  private chatframe chatframe;
  public userinfo(){}
  // 有参数的构造器
  public userinfo(string icon , string name
   , socketaddress address , int lost)
  {
   this.icon = icon;
   this.name = name;
   this.address = address;
   this.lost = lost;
  }
  // 省略所有成员变量的setter和getter方法
  // icon的setter和getter方法
  public void seticon(string icon)
  {
   this.icon = icon;
  }
  public string geticon()
  {
   return this.icon;
  }
  // name的setter和getter方法
  public void setname(string name)
  {
   this.name = name;
  }
  public string getname()
  {
   return this.name;
  }
  // address的setter和getter方法
  public void setaddress(socketaddress address)
  {
   this.address = address;
  }
  public socketaddress getaddress()
  {
   return this.address;
  }
  // lost的setter和getter方法
  public void setlost(int lost)
  {
   this.lost = lost;
  }
  public int getlost()
  {
   return this.lost;
  }
  // chatframe的setter和getter方法
  public void setchatframe(chatframe chatframe)
  {
   this.chatframe = chatframe;
  }
  public chatframe getchatframe()
  {
   return this.chatframe;
  }
  // 使用address作为该用户的标识,所以根据address作为
  // 重写hashcode()和equals方法的标准
  public int hashcode()
  {
   return address.hashcode();
  }
  public boolean equals(object obj)
  {
   if (obj != null && obj.getclass() == userinfo.class)
   {
    userinfo target = (userinfo)obj;
    if (address != null)
    {
      return address.equals(target.getaddress());
    }
   }
   return false;
  }
}

通过userinfo的封装,所有客户端只需要维护该userinfo类的列表,程序就可以实现广播、发送私聊信息等功能。本程序的底层通信类则需要一个multicastsocket和一个datagramsocket,该工具类的代码如下:

?
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package com.talk;
import java.io.ioexception;
import java.net.datagrampacket;
import java.net.datagramsocket;
import java.net.inetaddress;
import java.net.multicastsocket;
import java.net.socketaddress;
import java.util.arraylist;
import javax.swing.joptionpane;
public class comutil
{
  // 定义本程序通信所使用的字符集
  public static final string charset = "utf-8";
  // 使用常量作为本程序的多点广播ip地址
  private static final string broadcast_ip
   = "230.0.0.1";
  // 使用常量作为本程序的多点广播目的的端口
  // datagramsocket所用的的端口为该端口+1。
  public static final int broadcast_port = 30000;
  // 定义每个数据报的最大大小为4k
  private static final int data_len = 4096;
  // 定义本程序的multicastsocket实例
  private multicastsocket socket = null;
  // 定义本程序私聊的socket实例
  private datagramsocket singlesocket = null;
  // 定义广播的ip地址
  private inetaddress broadcastaddress = null;
  // 定义接收网络数据的字节数组
  byte[] inbuff = new byte[data_len];
  // 以指定字节数组创建准备接受数据的datagrampacket对象
  private datagrampacket inpacket =
   new datagrampacket(inbuff , inbuff.length);
  // 定义一个用于发送的datagrampacket对象
  private datagrampacket outpacket = null;
  // 聊天的主界面程序
  private lantalk lantalk;
  // 构造器,初始化资源
  public comutil(lantalk lantalk) throws exception
  {
   this.lantalk = lantalk;
   // 创建用于发送、接收数据的multicastsocket对象
   // 因为该multicastsocket对象需要接收,所以有指定端口
   socket = new multicastsocket(broadcast_port);
   // 创建私聊用的datagramsocket对象
   singlesocket = new datagramsocket(broadcast_port + 1);
   broadcastaddress = inetaddress.getbyname(broadcast_ip);
   // 将该socket加入指定的多点广播地址
   socket.joingroup(broadcastaddress);
   // 设置本multicastsocket发送的数据报被回送到自身
   socket.setloopbackmode(false);
   // 初始化发送用的datagramsocket,它包含一个长度为0的字节数组
   outpacket = new datagrampacket(new byte[0]
    , 0 , broadcastaddress , broadcast_port);
   // 启动两个读取网络数据的线程
   new readbroad().start();
   thread.sleep(1);
   new readsingle().start();
  }
  // 广播消息的工具方法
  public void broadcast(string msg)
  {
   try
   {
    // 将msg字符串转换字节数组
    byte[] buff = msg.getbytes(charset);
    // 设置发送用的datagrampacket里的字节数据
    outpacket.setdata(buff);
    // 发送数据报
    socket.send(outpacket);
   }
   // 捕捉异常
   catch (ioexception ex)
   {
    ex.printstacktrace();
    if (socket != null)
    {
      // 关闭该socket对象
      socket.close();
    }
    joptionpane.showmessagedialog(null
      , "发送信息异常,请确认30000端口空闲,且网络连接正常!"
      , "网络异常", joptionpane.error_message);
    system.exit(1);
   }
  }
  // 定义向单独用户发送消息的方法
  public void sendsingle(string msg , socketaddress dest)
  {
   try
   {
    // 将msg字符串转换字节数组
    byte[] buff = msg.getbytes(charset);
    datagrampacket packet = new datagrampacket(buff
      , buff.length , dest);
    singlesocket.send(packet);
   }
   // 捕捉异常
   catch (ioexception ex)
   {
    ex.printstacktrace();
    if (singlesocket != null)
    {
      // 关闭该socket对象
      singlesocket.close();
    }
    joptionpane.showmessagedialog(null
      , "发送信息异常,请确认30001端口空闲,且网络连接正常!"
      , "网络异常", joptionpane.error_message);
    system.exit(1);
   }
  }
  // 不断从datagramsocket中读取数据的线程
  class readsingle extends thread
  {
   // 定义接收网络数据的字节数组
   byte[] singlebuff = new byte[data_len];
   private datagrampacket singlepacket =
    new datagrampacket(singlebuff , singlebuff.length);
   public void run()
   {
    while (true)
    {
      try
      {
       // 读取socket中的数据。
       singlesocket.receive(singlepacket);
       // 处理读到的信息
       lantalk.processmsg(singlepacket , true);
      }
      // 捕捉异常
      catch (ioexception ex)
      {
       ex.printstacktrace();
       if (singlesocket != null)
       {
        // 关闭该socket对象
        singlesocket.close();
       }
       joptionpane.showmessagedialog(null
        , "接收信息异常,请确认30001端口空闲,且网络连接正常!"
        , "网络异常", joptionpane.error_message);
       system.exit(1);
      }
    }
   }
  }
  // 持续读取multicastsocket的线程
  class readbroad extends thread
  {
   public void run()
   {
    while (true)
    {
      try
      {
       // 读取socket中的数据。
       socket.receive(inpacket);
       // 打印输出从socket中读取的内容
       string msg = new string(inbuff , 0
        , inpacket.getlength() , charset);
       // 读到的内容是在线信息
       if (msg.startswith(yeekuprotocol.presence)
        && msg.endswith(yeekuprotocol.presence))
       {
        string usermsg = msg.substring(2
          , msg.length() - 2);
        string[] userinfo = usermsg.split(yeekuprotocol
          .splitter);
        userinfo user = new userinfo(userinfo[1]
          , userinfo[0] , inpacket.getsocketaddress(), 0);
        // 控制是否需要添加该用户的旗标
        boolean addflag = true;
        arraylist<integer> dellist = new arraylist<>();
        // 遍历系统中已有的所有用户,该循环必须循环完成
        for (int i = 1 ; i < lantalk.getusernum() ; i++ )
        {
          userinfo current = lantalk.getuser(i);
          // 将所有用户失去联系的次数加1
          current.setlost(current.getlost() + 1);
          // 如果该信息由指定用户发送过来
          if (current.equals(user))
          {
           current.setlost(0);
           // 设置该用户无须添加
           addflag = false;
          }
          if (current.getlost() > 2)
          {
           dellist.add(i);
          }
        }
        // 删除dellist中的所有索引对应的用户
        for (int i = 0; i < dellist.size() ; i++)
        {
          lantalk.removeuser(dellist.get(i));
        }
        if (addflag)
        {
          // 添加新用户
          lantalk.adduser(user);
        }
       }
       // 读到的内容是公聊信息
       else
       {
        // 处理读到的信息
        lantalk.processmsg(inpacket , false);
       }
      }
      // 捕捉异常
      catch (ioexception ex)
      {
       ex.printstacktrace();
       if (socket != null)
       {
        // 关闭该socket对象
        socket.close();
       }
       joptionpane.showmessagedialog(null
        , "接收信息异常,请确认30000端口空闲,且网络连接正常!"
        , "网络异常", joptionpane.error_message);
       system.exit(1);
      }
    }
   }
  }
}

本程序的一个主类,lantalk ,该类使用defaultlistmodel来维护用户列表,该类里的每个列表项就是一个userinfo。该类还提供了一个imagecellrenderer,该类用于将列表项绘制出用户图标和用户名字。

?
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package com.talk;
import java.awt.color;
import java.awt.component;
import java.awt.dimension;
import java.awt.font;
import java.awt.graphics;
import java.awt.event.mouseadapter;
import java.awt.event.mouseevent;
import java.net.datagrampacket;
import java.net.inetsocketaddress;
import java.net.socketaddress;
import java.text.dateformat;
import java.util.date;
import javax.swing.defaultlistmodel;
import javax.swing.imageicon;
import javax.swing.jframe;
import javax.swing.jlist;
import javax.swing.jpanel;
import javax.swing.jscrollpane;
import javax.swing.listcellrenderer;
import com.bank.chatframe;
import com.bank.loginframe;
public class lantalk extends jframe
{
  private defaultlistmodel<userinfo> listmodel
   = new defaultlistmodel<>();
  // 定义一个jlist对象
  private jlist<userinfo> friendslist = new jlist<>(listmodel);
  // 定义一个用于格式化日期的格式器
  private dateformat formatter = dateformat.getdatetimeinstance();
  public lantalk()
  {
   super("局域网聊天");
   // 设置该jlist使用imagecellrenderer作为单元格绘制器
   friendslist.setcellrenderer(new imagecellrenderer());
   listmodel.addelement(new userinfo("all" , "所有人"
    , null , -2000));
   friendslist.addmouselistener(new changemusiclistener());
   add(new jscrollpane(friendslist));
   setdefaultcloseoperation(jframe.exit_on_close);
   setbounds(2, 2, 160 , 600);
  }
  // 根据地址来查询用户
  public userinfo getuserbysocketaddress(socketaddress address)
  {
   for (int i = 1 ; i < getusernum() ; i++)
   {
    userinfo user = getuser(i);
    if (user.getaddress() != null
      && user.getaddress().equals(address))
    {
      return user;
    }
   }
   return null;
  }
  // ------下面四个方法是对listmodel的包装------
  // 向用户列表中添加用户
  public void adduser(userinfo user)
  {
   listmodel.addelement(user);
  }
  // 从用户列表中删除用户
  public void removeuser(int pos)
  {
   listmodel.removeelementat(pos);
  }
  // 获取该聊天窗口的用户数量
  public int getusernum()
  {
   return listmodel.size();
  }
  // 获取指定位置的用户
  public userinfo getuser(int pos)
  {
   return listmodel.elementat(pos);
  }
  // 实现jlist上的鼠标双击事件的监听器
  class changemusiclistener extends mouseadapter
  {
   public void mouseclicked(mouseevent e)
   {
    // 如果鼠标的击键次数大于2
    if (e.getclickcount() >= 2)
    {
      // 取出鼠标双击时选中的列表项
      userinfo user = (userinfo)friendslist.getselectedvalue();
      // 如果该列表项对应用户的交谈窗口为null
      if (user.getchatframe() == null)
      {
       // 为该用户创建一个交谈窗口,并让该用户引用该窗口
       user.setchatframe(new chatframe(null , user));
      }
      // 如果该用户的窗口没有显示,则让该用户的窗口显示出来
      if (!user.getchatframe().isshowing())
      {
       user.getchatframe().setvisible(true);
      }
    }
   }
  }
  /**
  * 处理网络数据报,该方法将根据聊天信息得到聊天者,
  * 并将信息显示在聊天对话框中。
  * @param packet 需要处理的数据报
  * @param single 该信息是否为私聊信息
  */
  public void processmsg(datagrampacket packet , boolean single)
  {
   // 获取该发送该数据报的socketaddress
   inetsocketaddress srcaddress = (inetsocketaddress)
    packet.getsocketaddress();
   // 如果是私聊信息,则该packet获取的是datagramsocket的地址,
   // 将端口减1才是对应的multicastsocket的地址
   if (single)
   {
    srcaddress = new inetsocketaddress(srcaddress.gethostname()
      , srcaddress.getport() - 1);
   }
   userinfo srcuser = getuserbysocketaddress(srcaddress);
   if (srcuser != null)
   {
    // 确定消息将要显示到哪个用户对应窗口上。
    userinfo alertuser = single ? srcuser : getuser(0);
    // 如果该用户对应的窗口为空,显示该窗口
    if (alertuser.getchatframe() == null)
    {
      alertuser.setchatframe(new chatframe(null , alertuser));
    }
    // 定义添加的提示信息
    string tipmsg = single ? "对您说:" : "对大家说:";
    try{
      // 显示提示信息
      alertuser.getchatframe().addstring(srcuser.getname()
       + tipmsg + "......................("
       + formatter.format(new date()) + ")\n"
       + new string(packet.getdata() , 0 , packet.getlength()
       , comutil.charset) + "\n");
    } catch (exception ex) { ex.printstacktrace(); }
    if (!alertuser.getchatframe().isshowing())
    {
      alertuser.getchatframe().setvisible(true);
    }
   }
  }
  // 主方法,程序的入口
  public static void main(string[] args)
  {
   lantalk lantalk = new lantalk();
   new loginframe(lantalk , "请输入用户名、头像后登录");
  }
}
// 定义用于改变jlist列表项外观的类
class imagecellrenderer extends jpanel
  implements listcellrenderer<userinfo>
{
  private imageicon icon;
  private string name;
  // 定义绘制单元格时的背景色
  private color background;
  // 定义绘制单元格时的前景色
  private color foreground;
  @override
  public component getlistcellrenderercomponent(jlist list
   , userinfo userinfo , int index
   , boolean isselected , boolean cellhasfocus)
  {
   // 设置图标
   icon = new imageicon("ico/" + userinfo.geticon() + ".gif");
   name = userinfo.getname();
   // 设置背景色、前景色
   background = isselected ? list.getselectionbackground()
    : list.getbackground();
   foreground = isselected ? list.getselectionforeground()
    : list.getforeground();
   // 返回该jpanel对象作为单元格绘制器
   return this;
  }
  // 重写paintcomponent方法,改变jpanel的外观
  public void paintcomponent(graphics g)
  {
   int imagewidth = icon.getimage().getwidth(null);
   int imageheight = icon.getimage().getheight(null);
   g.setcolor(background);
   g.fillrect(0, 0, getwidth(), getheight());
   g.setcolor(foreground);
   // 绘制好友图标
   g.drawimage(icon.getimage() , getwidth() / 2 - imagewidth / 2
    , 10 , null);
   g.setfont(new font("sansserif" , font.bold , 18));
   // 绘制好友用户名
   g.drawstring(name, getwidth() / 2 - name.length() * 10
    , imageheight + 30 );
  }
  // 通过该方法来设置该imagecellrenderer的最佳大小
  public dimension getpreferredsize()
  {
   return new dimension(60, 80);
  }
}

除了以上主要的代码,还有yeekuprotocol   chatframe   loginframe等类:

?
1
2
3
4
5
6
package com.talk;
public interface yeekuprotocol
{
  string presence = "⊿⊿";
  string splitter = "▓";
}
?
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
package com.bank;
import java.awt.dimension;
import java.awt.font;
import java.awt.gridlayout;
import java.awt.event.actionevent;
import java.awt.event.actionlistener;
import javax.swing.jbutton;
import javax.swing.jcombobox;
import javax.swing.jcomponent;
import javax.swing.jdialog;
import javax.swing.jlabel;
import javax.swing.jpanel;
import javax.swing.jtextfield;
import com.talk.comutil;
import com.talk.lantalk;
import com.talk.yeekuprotocol;
// 登录用的对话框
public class loginframe extends jdialog
{
  public jlabel tip;
  public jtextfield userfield = new jtextfield("钱钟书" , 20);
  public jcombobox<integer> iconlist = new jcombobox<>(
   new integer[]{1, 2, 3, 4, 5 , 6, 7, 8 ,9 ,10});
  private jbutton loginbn = new jbutton("登录");
  // 聊天的主界面
  private lantalk chatframe;
  // 聊天通信的工具实例
  public static comutil comutil;
  // 构造器,用于初始化的登录对话框
  public loginframe(lantalk parent , string msg)
  {
   super(parent , "输入名字后登录" , true);
   this.chatframe = parent;
   setlayout(new gridlayout(5, 1));
   jpanel jp = new jpanel();
   tip = new jlabel(msg);
   tip.setfont(new font("serif" , font.bold , 16));
   jp.add(tip);
   add(jp);
   add(getpanel("用户名" , userfield));
   iconlist.setpreferredsize(new dimension(224, 20));
   add(getpanel("图 标" , iconlist));
   jpanel bp = new jpanel();
   loginbn.addactionlistener(new myactionlistener(this));
   bp.add(loginbn);
   add(bp);
   pack();
   setvisible(true);
  }
  // 工具方法,该方法将一个字符串和组件组合成jpanel对象
  private jpanel getpanel(string name , jcomponent jf)
  {
   jpanel jp = new jpanel();
   jp.add(new jlabel(name + ":"));
   jp.add(jf);
   return jp;
  }
  // 该方法用于改变登录窗口最上面的提示信息
  public void settipmsg(string tip)
  {
   this.tip.settext(tip);
  }
  // 定义一个事件监听器
  class myactionlistener implements actionlistener
  {
   private loginframe loginframe;
   public myactionlistener(loginframe loginframe)
   {
    this.loginframe = loginframe;
   }
   // 当鼠标单击事件发生时
   public void actionperformed(actionevent evt)
   {
    try
    {
      // 初始化聊天通信类
      comutil = new comutil(chatframe);
      final string loginmsg = yeekuprotocol.presence + userfield.gettext()
       + yeekuprotocol.splitter + iconlist.getselectedobjects()[0]
       + yeekuprotocol.presence;
      comutil.broadcast(loginmsg);
      // 启动定时器每20秒广播一次在线信息
      javax.swing.timer timer = new javax.swing.timer(1000 * 10
       , event-> comutil.broadcast(loginmsg));
      timer.start();
      loginframe.setvisible(false);
      chatframe.setvisible(true);
    }
    catch (exception ex)
    {
      loginframe.settipmsg("确认30001端口空闲,且网络正常!");
    }
   }
  }
}
?
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
package com.bank;
import java.awt.borderlayout;
import java.awt.event.actionevent;
import java.net.inetsocketaddress;
import javax.swing.abstractaction;
import javax.swing.action;
import javax.swing.jbutton;
import javax.swing.jdialog;
import javax.swing.jlabel;
import javax.swing.jpanel;
import javax.swing.jscrollpane;
import javax.swing.jtextarea;
import javax.swing.jtextfield;
import javax.swing.keystroke;
import com.talk.lantalk;
import com.talk.userinfo;
// 定义交谈的对话框
public class chatframe extends jdialog
{
  // 聊天信息区
  jtextarea msgarea = new jtextarea(12 , 45);
  // 聊天输入区
  jtextfield chatfield = new jtextfield(30);
  // 发送聊天信息的按钮
  jbutton sendbn = new jbutton("发送");
  // 该交谈窗口对应的用户
  userinfo user;
  // 构造器,用于初始化交谈对话框的界面
  public chatframe(lantalk parent , final userinfo user)
  {
   super(parent , "和" + user.getname() + "聊天中" , false);
   this.user = user;
   msgarea.seteditable(false);
   add(new jscrollpane(msgarea));
   jpanel buttom = new jpanel();
   buttom.add(new jlabel("输入信息:"));
   buttom.add(chatfield);
   buttom.add(sendbn);
   add(buttom , borderlayout.south);
   // 发送消息的action,action是actionlistener的子接口
   action sendaction = new abstractaction()
   {
    @override
    public void actionperformed(actionevent evt)
    {
      inetsocketaddress dest = (inetsocketaddress)user.getaddress();
      // 在聊友列表中,所有人项的socketaddress是null
      // 这表明是向所有人发送消息
      if (dest == null)
      {
       loginframe.comutil.broadcast(chatfield.gettext());
       msgarea.settext("您对大家说:"
        + chatfield.gettext() + "\n" + msgarea.gettext());
      }
      // 向私人发送信息
      else
      {
       // 获取发送消息的目的
       dest = new inetsocketaddress(dest.gethostname(),
        dest.getport() + 1);
       loginframe.comutil.sendsingle(chatfield.gettext(), dest);
       msgarea.settext("您对" + user.getname() + "说:"
        + chatfield.gettext() + "\n" + msgarea.gettext());
      }
      chatfield.settext("");
    }
   };
   sendbn.addactionlistener(sendaction);
   // 将ctrl+enter键和"send"关联
   chatfield.getinputmap().put(keystroke.getkeystroke('\n'
    , java.awt.event.inputevent.ctrl_mask) , "send");
   // 将"send"与sendaction关联
   chatfield.getactionmap().put("send", sendaction);
   pack();
  }
  // 定义向聊天区域添加消息的方法
  public void addstring(string msg)
  {
   msgarea.settext(msg + "\n" + msgarea.gettext());
  }
}

运行效果

java使用MulticastSocket实现基于广播的多人聊天室

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

原文链接:https://blog.csdn.net/zlz18225318697/article/details/52607528