java使用MulticastSocket實現(xiàn)基于廣播的多人聊天室
使用MulticastSocket實現(xiàn)多點廣播:
(1)DatagramSocket只允許數(shù)據(jù)報發(fā)給指定的目標地址,而MulticastSocket可以將數(shù)據(jù)報以廣播的方式發(fā)送到多個客戶端。
(2)IP協(xié)議為多點廣播提供了這批特殊的IP地址,這些IP地址的范圍是:224.0.0.0至239.255.255.255..
(3)MulticastSocket類時實現(xiàn)多點廣播的關(guān)鍵,當MulticastSocket把一個DaragramPocket發(fā)送到多點廣播的IP地址時,該數(shù)據(jù)報將會自動廣播到加入該地址的所有MulticastSocket。MulticastSocket既可以將數(shù)據(jù)報發(fā)送到多點廣播地址,也可以接收其他主機的廣播信息。
(4)事實上,MulticastSocket是DatagramSocket的子類,也就是說,MulticastSocket是特殊的DatagramSocket。當要發(fā)送一個數(shù)據(jù)報時,可以使用隨機端口創(chuàng)建MulticastSocket,也可以在指定端口創(chuàng)建MulticastSocket。MulticastSocket提供了如下三個構(gòu)造器:
public MulticastSocket() 使用本機默認地址,隨機端口來創(chuàng)建MulticastSocket對象
public MulticastSocket(int portNumber) 用本機默認地址,指定端口來創(chuàng)建MulticastSocket對象
public MulticastSocket(SocketAddress bindaddr) 用指定IP地址,指定端口來創(chuàng)建MulticastSocket對象
(5)創(chuàng)建MulticastSocket對象后,還需要將MulticastSocket加入到指定的多點廣播地址。MulticastSocket使用joinGroup()方法加入指定組;使用leaveGroup()方法脫離一個組。
joinGroup(InetAddress multicastAddr) 將該MulticastSocket加入到指定的多點廣播地址
leaveGroup(InetAddress multicastAddr) 將該MulticastSocket離開指定的多點廣播地址
(6)在某些系統(tǒng)中,可能有多個網(wǎng)絡(luò)接口,這可能為多點廣播帶來問題,這時候程序需要在一個指定的網(wǎng)絡(luò)接口上監(jiān)聽,通過調(diào)用setInterface()方法可以強制MulticastSocket使用指定的網(wǎng)絡(luò)接口‘也可以使用getInterface()方法查詢MulticastSocket監(jiān)聽的網(wǎng)絡(luò)接口。
(7)如果創(chuàng)建僅僅用于發(fā)送數(shù)據(jù)報的MulticastSocket對象,則使用默認地址,隨機端口即可。但如果創(chuàng)建接收用的MulticastSocket對象,’則該MulticastSocket對象必須有指定端口,否則無法確定發(fā)送數(shù)據(jù)報的目標端口。
(8)MulticastSocket用于發(fā)送接收數(shù)據(jù)報的方法與DatagramSocket完全一樣。但MulticastSocket比DatagramSocket多了一個setTimeToLive(int ttl)方法,該ttl用于設(shè)置數(shù)據(jù)報最多可以跨過多少個網(wǎng)絡(luò)。
當ttl為0時,指定數(shù)據(jù)報應(yīng)停留在本地主機
當ttl為1時,指定數(shù)據(jù)報發(fā)送到本地局域網(wǎng)
當ttl為32時,指定數(shù)據(jù)報發(fā)送到本站點的網(wǎng)絡(luò)上
當ttl為64時,意味著數(shù)據(jù)報應(yīng)該停留在本地區(qū)
當ttl為128時,意味著數(shù)據(jù)報應(yīng)保留在本大洲
當ttl為255時,意味著數(shù)據(jù)報可以發(fā)送到所有地方
默認情況下,ttl值為1.
程序?qū)嵗?/strong>
下面程序使用MulticastSocket實現(xiàn)一個基于廣播的多人聊天室。程序只需要一個MulticastSocket,兩個線程,其中MulticastSocket既用于發(fā)送,也用于接收;一個線程負責鍵盤輸入,并向MulticastSocket發(fā)送數(shù)據(jù);一個線程負責從MulticastSocket中讀取數(shù)據(jù)。
package com.talk;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Scanner;
//讓該類實現(xiàn)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;
//定義每個數(shù)據(jù)報大小最大為4kb
private static final int DATA_LEN=4096;
//定義本程序的MulticastSocket實例
private MulticastSocket socket=null;
private InetAddress broadcastAddress=null;
private Scanner scan=null;
//定義接收網(wǎng)絡(luò)數(shù)據(jù)的字節(jié)數(shù)組
byte[] inBuff=new byte[DATA_LEN];
//以指定字節(jié)數(shù)組創(chuàng)建準備接收數(shù)據(jù)的MulticastSocket對象
private DatagramPacket inPacket =new DatagramPacket(inBuff, inBuff.length);
//定義一個用于發(fā)送的DatagramPacket對象
private DatagramPacket outPacket=null;
public void init() throws IOException{
//創(chuàng)建鍵盤輸入流
Scanner scan=new Scanner(System.in);
//創(chuàng)建用于發(fā)送、接收數(shù)據(jù)的MulticastSocket對象,由于該MulticastSocket需要接收數(shù)據(jù),所以有指定端口
socket=new MulticastSocket(BROADCAST_PORT);
broadcastAddress=InetAddress.getByName(BROADCAST_IP);
//將該socket加入到指定的多點廣播地址
socket.joinGroup(broadcastAddress);
//設(shè)置本MulticastSocket發(fā)送的數(shù)據(jù)報會被回送到自身
socket.setLoopbackMode(false);
//初始化發(fā)送用的DatagramSocket,它包含一個長度為0的字節(jié)數(shù)組
outPacket =new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT);
//啟動本實例的run()方法作為線程執(zhí)行體的線程
new Thread(this).start();
//不斷的讀取鍵盤輸入
while(scan.hasNextLine()){
//將鍵盤輸入的一行字符轉(zhuǎn)換成字節(jié)數(shù)組
byte [] buff=scan.nextLine().getBytes();
//設(shè)置發(fā)送用的DatagramPacket里的字節(jié)數(shù)據(jù)
outPacket.setData(buff);
//發(fā)送數(shù)據(jù)報
socket.send(outPacket);
}
socket.close();
}
public void run() {
// TODO Auto-generated method stub
while(true){
//讀取Socket中的數(shù)據(jù),讀到的數(shù)據(jù)放入inPacket所封裝的字節(jié)組里
try {
socket.receive(inPacket);
//打印從socket讀取到的內(nèi)容
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);
//關(guān)閉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();
}
}
}下面將結(jié)合MulticastSocket和DatagramSocket開發(fā)一個簡單的局域網(wǎng)即時通訊工具,局域網(wǎng)內(nèi)每個用戶啟動該工具后,就可以看到該局域網(wǎng)內(nèi)所有的在線用戶,該用戶也會被其他用戶看到:
該程序的思路是:每個用戶都啟動兩個Socket,即MulticastSocket和DatagramSocket。其中MulticastSocket會周期性的向230.0.0.1發(fā)送在線信息,且所有的MulticastSocket都會加入到230.0.0.1這個多點廣播IP中,這樣每個用戶都會收到其他用戶的在線信息,如果系統(tǒng)在一段時間內(nèi)沒有收到某個用戶廣播的在線信息,則從用戶列表中刪除該用戶。除此之外,該MulticastSocket還用于向其他用戶發(fā)送廣播信息。
DatagramSocket主要用于發(fā)送私聊信息,當用戶收到其他用戶廣播來的DatagramSocket時,即可獲得該用戶MulticastSocket對應(yīng)的SocketAddress.這個SocketAddress將作為發(fā)送私聊信息的重要依據(jù)。—本程序讓MulticastSocket在30000端口監(jiān)聽,而DatagramSocket在30001端口監(jiān)聽,這樣程序就可以根據(jù)其他用戶廣播來的DatagramPacket得到他的DatagramSocket所在的地址。
本系統(tǒng)提供了一個UserInfo類,該類封裝了用戶名、圖標、對應(yīng)的SocketAddress以及該用戶對應(yīng)的交談窗口,失去聯(lián)系的次數(shù)等信息:
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;
// 該用戶失去聯(lián)系的次數(shù)
private int lost;
// 該用戶對應(yīng)的交談窗口
private ChatFrame chatFrame;
public UserInfo(){}
// 有參數(shù)的構(gòu)造器
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作為該用戶的標識,所以根據(jù)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類的列表,程序就可以實現(xiàn)廣播、發(fā)送私聊信息等功能。本程序的底層通信類則需要一個MulticastSocket和一個DatagramSocket,該工具類的代碼如下:
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;
// 定義每個數(shù)據(jù)報的最大大小為4K
private static final int DATA_LEN = 4096;
// 定義本程序的MulticastSocket實例
private MulticastSocket socket = null;
// 定義本程序私聊的Socket實例
private DatagramSocket singleSocket = null;
// 定義廣播的IP地址
private InetAddress broadcastAddress = null;
// 定義接收網(wǎng)絡(luò)數(shù)據(jù)的字節(jié)數(shù)組
byte[] inBuff = new byte[DATA_LEN];
// 以指定字節(jié)數(shù)組創(chuàng)建準備接受數(shù)據(jù)的DatagramPacket對象
private DatagramPacket inPacket =
new DatagramPacket(inBuff , inBuff.length);
// 定義一個用于發(fā)送的DatagramPacket對象
private DatagramPacket outPacket = null;
// 聊天的主界面程序
private LanTalk lanTalk;
// 構(gòu)造器,初始化資源
public ComUtil(LanTalk lanTalk) throws Exception
{
this.lanTalk = lanTalk;
// 創(chuàng)建用于發(fā)送、接收數(shù)據(jù)的MulticastSocket對象
// 因為該MulticastSocket對象需要接收,所以有指定端口
socket = new MulticastSocket(BROADCAST_PORT);
// 創(chuàng)建私聊用的DatagramSocket對象
singleSocket = new DatagramSocket(BROADCAST_PORT + 1);
broadcastAddress = InetAddress.getByName(BROADCAST_IP);
// 將該socket加入指定的多點廣播地址
socket.joinGroup(broadcastAddress);
// 設(shè)置本MulticastSocket發(fā)送的數(shù)據(jù)報被回送到自身
socket.setLoopbackMode(false);
// 初始化發(fā)送用的DatagramSocket,它包含一個長度為0的字節(jié)數(shù)組
outPacket = new DatagramPacket(new byte[0]
, 0 , broadcastAddress , BROADCAST_PORT);
// 啟動兩個讀取網(wǎng)絡(luò)數(shù)據(jù)的線程
new ReadBroad().start();
Thread.sleep(1);
new ReadSingle().start();
}
// 廣播消息的工具方法
public void broadCast(String msg)
{
try
{
// 將msg字符串轉(zhuǎn)換字節(jié)數(shù)組
byte[] buff = msg.getBytes(CHARSET);
// 設(shè)置發(fā)送用的DatagramPacket里的字節(jié)數(shù)據(jù)
outPacket.setData(buff);
// 發(fā)送數(shù)據(jù)報
socket.send(outPacket);
}
// 捕捉異常
catch (IOException ex)
{
ex.printStackTrace();
if (socket != null)
{
// 關(guān)閉該Socket對象
socket.close();
}
JOptionPane.showMessageDialog(null
, "發(fā)送信息異常,請確認30000端口空閑,且網(wǎng)絡(luò)連接正常!"
, "網(wǎng)絡(luò)異常", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
// 定義向單獨用戶發(fā)送消息的方法
public void sendSingle(String msg , SocketAddress dest)
{
try
{
// 將msg字符串轉(zhuǎn)換字節(jié)數(shù)組
byte[] buff = msg.getBytes(CHARSET);
DatagramPacket packet = new DatagramPacket(buff
, buff.length , dest);
singleSocket.send(packet);
}
// 捕捉異常
catch (IOException ex)
{
ex.printStackTrace();
if (singleSocket != null)
{
// 關(guān)閉該Socket對象
singleSocket.close();
}
JOptionPane.showMessageDialog(null
, "發(fā)送信息異常,請確認30001端口空閑,且網(wǎng)絡(luò)連接正常!"
, "網(wǎng)絡(luò)異常", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
// 不斷從DatagramSocket中讀取數(shù)據(jù)的線程
class ReadSingle extends Thread
{
// 定義接收網(wǎng)絡(luò)數(shù)據(jù)的字節(jié)數(shù)組
byte[] singleBuff = new byte[DATA_LEN];
private DatagramPacket singlePacket =
new DatagramPacket(singleBuff , singleBuff.length);
public void run()
{
while (true)
{
try
{
// 讀取Socket中的數(shù)據(jù)。
singleSocket.receive(singlePacket);
// 處理讀到的信息
lanTalk.processMsg(singlePacket , true);
}
// 捕捉異常
catch (IOException ex)
{
ex.printStackTrace();
if (singleSocket != null)
{
// 關(guān)閉該Socket對象
singleSocket.close();
}
JOptionPane.showMessageDialog(null
, "接收信息異常,請確認30001端口空閑,且網(wǎng)絡(luò)連接正常!"
, "網(wǎng)絡(luò)異常", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
}
}
// 持續(xù)讀取MulticastSocket的線程
class ReadBroad extends Thread
{
public void run()
{
while (true)
{
try
{
// 讀取Socket中的數(shù)據(jù)。
socket.receive(inPacket);
// 打印輸出從socket中讀取的內(nèi)容
String msg = new String(inBuff , 0
, inPacket.getLength() , CHARSET);
// 讀到的內(nèi)容是在線信息
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<>();
// 遍歷系統(tǒng)中已有的所有用戶,該循環(huán)必須循環(huán)完成
for (int i = 1 ; i < lanTalk.getUserNum() ; i++ )
{
UserInfo current = lanTalk.getUser(i);
// 將所有用戶失去聯(lián)系的次數(shù)加1
current.setLost(current.getLost() + 1);
// 如果該信息由指定用戶發(fā)送過來
if (current.equals(user))
{
current.setLost(0);
// 設(shè)置該用戶無須添加
addFlag = false;
}
if (current.getLost() > 2)
{
delList.add(i);
}
}
// 刪除delList中的所有索引對應(yīng)的用戶
for (int i = 0; i < delList.size() ; i++)
{
lanTalk.removeUser(delList.get(i));
}
if (addFlag)
{
// 添加新用戶
lanTalk.addUser(user);
}
}
// 讀到的內(nèi)容是公聊信息
else
{
// 處理讀到的信息
lanTalk.processMsg(inPacket , false);
}
}
// 捕捉異常
catch (IOException ex)
{
ex.printStackTrace();
if (socket != null)
{
// 關(guān)閉該Socket對象
socket.close();
}
JOptionPane.showMessageDialog(null
, "接收信息異常,請確認30000端口空閑,且網(wǎng)絡(luò)連接正常!"
, "網(wǎng)絡(luò)異常", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
}
}
}本程序的一個主類,LanTalk ,該類使用DefaultListModel來維護用戶列表,該類里的每個列表項就是一個UserInfo。該類還提供了一個ImageCellRenderer,該類用于將列表項繪制出用戶圖標和用戶名字。
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("局域網(wǎng)聊天");
// 設(shè)置該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);
}
// 根據(jù)地址來查詢用戶
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);
}
// 獲取該聊天窗口的用戶數(shù)量
public int getUserNum()
{
return listModel.size();
}
// 獲取指定位置的用戶
public UserInfo getUser(int pos)
{
return listModel.elementAt(pos);
}
// 實現(xiàn)JList上的鼠標雙擊事件的監(jiān)聽器
class ChangeMusicListener extends MouseAdapter
{
public void mouseClicked(MouseEvent e)
{
// 如果鼠標的擊鍵次數(shù)大于2
if (e.getClickCount() >= 2)
{
// 取出鼠標雙擊時選中的列表項
UserInfo user = (UserInfo)friendsList.getSelectedValue();
// 如果該列表項對應(yīng)用戶的交談窗口為null
if (user.getChatFrame() == null)
{
// 為該用戶創(chuàng)建一個交談窗口,并讓該用戶引用該窗口
user.setChatFrame(new ChatFrame(null , user));
}
// 如果該用戶的窗口沒有顯示,則讓該用戶的窗口顯示出來
if (!user.getChatFrame().isShowing())
{
user.getChatFrame().setVisible(true);
}
}
}
}
/**
* 處理網(wǎng)絡(luò)數(shù)據(jù)報,該方法將根據(jù)聊天信息得到聊天者,
* 并將信息顯示在聊天對話框中。
* @param packet 需要處理的數(shù)據(jù)報
* @param single 該信息是否為私聊信息
*/
public void processMsg(DatagramPacket packet , boolean single)
{
// 獲取該發(fā)送該數(shù)據(jù)報的SocketAddress
InetSocketAddress srcAddress = (InetSocketAddress)
packet.getSocketAddress();
// 如果是私聊信息,則該Packet獲取的是DatagramSocket的地址,
// 將端口減1才是對應(yīng)的MulticastSocket的地址
if (single)
{
srcAddress = new InetSocketAddress(srcAddress.getHostName()
, srcAddress.getPort() - 1);
}
UserInfo srcUser = getUserBySocketAddress(srcAddress);
if (srcUser != null)
{
// 確定消息將要顯示到哪個用戶對應(yīng)窗口上。
UserInfo alertUser = single ? srcUser : getUser(0);
// 如果該用戶對應(yīng)的窗口為空,顯示該窗口
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)
{
// 設(shè)置圖標
icon = new ImageIcon("ico/" + userInfo.getIcon() + ".gif");
name = userInfo.getName();
// 設(shè)置背景色、前景色
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 );
}
// 通過該方法來設(shè)置該ImageCellRenderer的最佳大小
public Dimension getPreferredSize()
{
return new Dimension(60, 80);
}
}
除了以上主要的代碼,還有YeekuProtocol ChatFrame LoginFrame等類:
package com.talk;
public interface YeekuProtocol
{
String PRESENCE = "⊿⊿";
String SPLITTER = "▓";
}
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;
// 構(gòu)造器,用于初始化的登錄對話框
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);
}
// 定義一個事件監(jiān)聽器
class MyActionListener implements ActionListener
{
private LoginFrame loginFrame;
public MyActionListener(LoginFrame loginFrame)
{
this.loginFrame = loginFrame;
}
// 當鼠標單擊事件發(fā)生時
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端口空閑,且網(wǎng)絡(luò)正常!");
}
}
}
}
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
{
// 聊天信息區(qū)
JTextArea msgArea = new JTextArea(12 , 45);
// 聊天輸入?yún)^(qū)
JTextField chatField = new JTextField(30);
// 發(fā)送聊天信息的按鈕
JButton sendBn = new JButton("發(fā)送");
// 該交談窗口對應(yīng)的用戶
UserInfo user;
// 構(gòu)造器,用于初始化交談對話框的界面
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);
// 發(fā)送消息的Action,Action是ActionListener的子接口
Action sendAction = new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent evt)
{
InetSocketAddress dest = (InetSocketAddress)user.getAddress();
// 在聊友列表中,所有人項的SocketAddress是null
// 這表明是向所有人發(fā)送消息
if (dest == null)
{
LoginFrame.comUtil.broadCast(chatField.getText());
msgArea.setText("您對大家說:"
+ chatField.getText() + "\n" + msgArea.getText());
}
// 向私人發(fā)送信息
else
{
// 獲取發(fā)送消息的目的
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"關(guān)聯(lián)
chatField.getInputMap().put(KeyStroke.getKeyStroke('\n'
, java.awt.event.InputEvent.CTRL_MASK) , "send");
// 將"send"與sendAction關(guān)聯(lián)
chatField.getActionMap().put("send", sendAction);
pack();
}
// 定義向聊天區(qū)域添加消息的方法
public void addString(String msg)
{
msgArea.setText(msg + "\n" + msgArea.getText());
}
}運行效果

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java面向?qū)ο蠡A(chǔ)知識之封裝,繼承,多態(tài)和抽象
這篇文章主要介紹了Java面向?qū)ο蟮姆庋b,繼承,多態(tài)和抽象,文中有非常詳細的代碼示例,對正在學習java基礎(chǔ)的小伙伴們有很好的幫助,需要的朋友可以參考下2021-11-11
Java接口返回省市區(qū)樹形結(jié)構(gòu)的實現(xiàn)
本文主要介紹了Java接口返回省市區(qū)樹形結(jié)構(gòu)的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-01-01
SpringBoot+隨機鹽值+雙重MD5實現(xiàn)加密登錄
數(shù)據(jù)加密在很多項目上都可以用到,大部分都會采用MD5進行加密,本文主要介紹了SpringBoot+隨機鹽值+雙重MD5實現(xiàn)加密登錄,具有一定的參考價值,感興趣的可以了解一下2024-02-02
Java使用觀察者模式實現(xiàn)氣象局高溫預(yù)警功能示例
這篇文章主要介紹了Java使用觀察者模式實現(xiàn)氣象局高溫預(yù)警功能,結(jié)合完整實例形式分析了java觀察者模式實現(xiàn)氣象局高溫預(yù)警的相關(guān)接口定義、使用、功能操作技巧,并總結(jié)了其設(shè)計原則與適用場合,具有一定參考借鑒價值,需要的朋友可以參考下2018-04-04
Java擴展庫RxJava的基本結(jié)構(gòu)與適用場景小結(jié)
RxJava(GitHub: https://github.com/ReactiveX/RxJava)能夠幫助Java進行異步與事務(wù)驅(qū)動的程序編寫,這里我們來作一個Java擴展庫RxJava的基本結(jié)構(gòu)與適用場景小結(jié),剛接觸RxJava的同學不妨看一下^^2016-06-06
SpringBoot實現(xiàn)RabbitMQ三種使用方式
本文主要介紹了SpringBoot實現(xiàn)RabbitMQ三種使用方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07

