目前,在互联网上比较常见的聊天软件通常都将程序分成两个部分使用,即客户机程序和服务器程序。客户机程序在网上发布供用户使用而服务器程序则只供服务器使用,一般不公开。采用这种模式可以方便管理员对所有用户进行管理,但它必须单独提供一台计算机作为服务器。而对于在局域网内使用的聊天软件,由于计算机的数量有限,而且计算机之间的服务关系经常变化或者根本没有服务关系,采用这种模式就显得力不从心了。
本程序在服务关系上做了作了一个新的设计,不再区分所为的服务器程序与客户端程序。当程序启动时,启动广播在线信息的线程,同时启动监听消息的线程。即用户输入用户名并打开在线用户的列表时,启动广播线程每隔一段时间便向局域网中的所有用户广播在线信息,同时启动监听其他主机发来的消息的线程,若接收到在线广播则将该用户添加到在线用户列表中。在进行消息通信时,通过指定端口发送消息,线程监听到其他用户发来的信息便提示用户查看。
本程序将所有的代码文件按功能分为user、utils、window三个个功能包,它们实现的功能分别为:
user包:
程序运行时需要的临时用户信息。
utils包:
程序的通信功能实现及程序中用到的常量。
window包:
构建本程序的主要窗口。
该程序工程的大致结构如图所示:
监听指定端口信息的实现
其他为界面的构建与逻辑的处理。
本程序在服务关系上做了作了一个新的设计,不再区分所为的服务器程序与客户端程序。当程序启动时,启动广播在线信息的线程,同时启动监听消息的线程。即用户输入用户名并打开在线用户的列表时,启动广播线程每隔一段时间便向局域网中的所有用户广播在线信息,同时启动监听其他主机发来的消息的线程,若接收到在线广播则将该用户添加到在线用户列表中。在进行消息通信时,通过指定端口发送消息,线程监听到其他用户发来的信息便提示用户查看。
本程序将所有的代码文件按功能分为user、utils、window三个个功能包,它们实现的功能分别为:
user包:
程序运行时需要的临时用户信息。
utils包:
程序的通信功能实现及程序中用到的常量。
window包:
构建本程序的主要窗口。
该程序工程的大致结构如图所示:
效果图如下:
广播的实现
package utils;
import user.UserInfo;
/*
* 通过指定端口广播在线信息
* 只广播用户名
*/
public class BroadCast implements Runnable{
@Override
public void run() {
System.out.println("广播进程已打开!");
SendPacket request = new SendPacket();
while(true){
if(request.postRequest(SendPacket.ONLINE_INFO,UserInfo.userName,Constant.BROADCAST_IP, Constant.BROADCAST_PORT,Constant.MSG_REC_PORT)){
System.out.println("在线信息广播成功!");
}else{
System.out.println("在线信息发送失败!");
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
发送数据包的实现
package utils;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import user.UserInfo;
/*
* 消息格式
* massage type#username#massage
*/
public class SendPacket {
private DatagramSocket dSocket;
private DatagramPacket dPacket;
public static final int ONLINE_INFO = 0; //发送上线信息的首部
public static final int OFFLINE_INFO = 1; //发送下线信息的首部
public static final int CONNECT_INFO = 2; //发送消息的首部
public boolean postRequest(int type ,String str ,String ip ,int port ,int toPort){
byte[] data = null;
try{
System.out.println("将---" + str + "---发送至<" + ip + ":" + toPort + ">");
dSocket = new DatagramSocket(port);
//将用户名转换为byte[]
switch(type){
case ONLINE_INFO:
data = (Constant.ONLINE_INFO + "#" + UserInfo.userName + "#" + str).getBytes();
break;
case OFFLINE_INFO:
data = (Constant.OFFLINE_INFO + "#" + UserInfo.userName + "#" + str).getBytes();
break;
case CONNECT_INFO:
data = (Constant.CONNECT_INFO + "#" + UserInfo.userName + "#" + str).getBytes();
break;
}
System.out.println("-------------------发送数据:" + data);
try{
while(true){
//封装userInfo广播信息
dPacket = new DatagramPacket(data,
data.length,
InetAddress.getByName(ip),
toPort);
//广播在线信息
dSocket.send(dPacket);
System.out.println("成功将---" + str + "---发送到" + ip);
return true;
}
}catch(Exception e){
e.printStackTrace();
System.out.println("-请求发送失败!");
return false;
}
}catch(Exception e){
e.printStackTrace();
System.out.println("-打开端口失败!");
return false;
}finally{
data = "".getBytes();
dPacket = null;
dSocket.close();
}
}
}
监听指定端口信息的实现
package utils;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.StringTokenizer;
import javax.swing.JOptionPane;
import window.W_Chatting;
import window.W_UserList;
/*
* 用于不断指定端口号接收其他在线主机发来的在线信息
* 并将在线信息放入ArrayList
* 同时更新在线用户列表
*/
public class MassageListener implements Runnable {
private DatagramSocket dSocket;
public void run() {
/*
* isInOnlineUserList = 0 不在在线列表中
* isInOnlineUserList = 1 在在线列表中
* isInOnlineUserList = 2 用户名更新
*/
int isInOnlineUserList;
int i = 0;
System.out.println("接收在线信息进程已打开!");
try {
dSocket = new DatagramSocket(Constant.MSG_REC_PORT);
try {
System.out.println("正在接收在线信息!");
// 将获取的数据保存到dPacket
while (true) {
byte[] dataBuf = new byte[Constant.MAX_MSG_SIZE + 2];
DatagramPacket dPacket = new DatagramPacket(dataBuf, dataBuf.length);
dSocket.receive(dPacket);
System.out.println("-------------------接收到数据:" + dataBuf);
String ip = dPacket.getAddress().getHostAddress();
int port = dPacket.getPort();
String dataPack = new String(dPacket.getData()).trim();
StringTokenizer token = new StringTokenizer(dataPack ,"#");
//分离消息
String head = token.nextToken();
String userName = token.nextToken();
String massage = dataPack.substring((head + "#" + userName + "#").length()).trim();
System.out.println("拦截到的首部 :" + head);
System.out.println("拦截到的消息:" + massage);
if (head.equals(Constant.ONLINE_INFO)) {
/*
* 接收到上线信息
*/
System.out.println();
System.out.println("来自ip为 " + ip + " <对方端口>: " + port
+ " 的上线信息!");
System.out.println(userName);
isInOnlineUserList = 0;
if (W_UserList.onlineUserArrayList.size() != 0) { // ArrayList不为空
for (i = 0; i < W_UserList.onlineUserArrayList
.size(); i++) {
// 已添加ip的用户
if (W_UserList.onlineUserArrayList.get(i).ipEquals(ip)
&& W_UserList.onlineUserArrayList.get(i).userNameEquals(userName)) {
isInOnlineUserList = 1;
System.out.println(userName + "<ip:" + ip
+ ">已经在您的好友列表中!");
}else if(W_UserList.onlineUserArrayList.get(i).ipEquals(ip)
&& (W_UserList.onlineUserArrayList.get(i).userNameEquals(userName) == false)) {
isInOnlineUserList = 2;
}
}
}
if (isInOnlineUserList == 0) {
W_UserList.addOnlineUser(userName, ip);
System.out.println("添加新的在线好友!");
} else if (isInOnlineUserList == 2) {
// ip已添加,但用户名更改
String str = userName + "<ip:" + ip + ">";
// 修改好友列表信息
W_UserList.modOnlineUser(
W_UserList.onlineUserArrayList.get(i)
.getDmtnUser(), str);
// 修改ArrayList中的username
W_UserList.onlineUserArrayList.get(i).setUserName(
userName);
System.out.println("<ip:" + ip + ">用户名更改为:"
+ userName);
}
} else if (head.equals(Constant.OFFLINE_INFO)) {
/*
* 接收到下线信息
*/
if (W_UserList.onlineUserArrayList.size() != 0) { // ArrayList不为空
for (i = 0; i < W_UserList.onlineUserArrayList
.size(); i++) {
if (W_UserList.onlineUserArrayList.get(i)
.ipEquals(ip)) {
W_UserList.delOnlineUser(i);
}
}
}
} else if (head.equals(Constant.CONNECT_INFO)) {
/*
* 接收到会话信息
*/
boolean flag = false;
int j = 0;
//检查是否已打开会话窗口
for (j = 0; j < W_UserList.chattingUserArrayList
.size(); i++) {
if (W_UserList.chattingUserArrayList.get(j)
.getUserIP().equals(ip)) {
flag = true;
break;
}
}
// 尚未打开会话窗口
if (!flag) {
int choice = JOptionPane.showConfirmDialog(null,
"是否打开新消息?" + userName + ":" + ip + "的消息!",
"新信息!",
JOptionPane.YES_NO_OPTION,
JOptionPane.ERROR_MESSAGE);
//打开新信息
if (choice == JOptionPane.YES_OPTION) {
try{
W_Chatting w_Chatting = new W_Chatting(userName ,ip);
//设置w_Chatting为可见
w_Chatting.setVisible(true);
W_UserList.addChattingUser(userName ,ip ,w_Chatting);
System.out.println("会话窗口成功打开!");
w_Chatting.appendMsg(massage);
}catch(Exception e1){
e1.printStackTrace();
System.out.println("会话窗口创建失败!");
}
}
} else {// 已打开会话窗口
W_UserList.chattingUserArrayList.get(j).getJfChatting().appendMsg(massage);
}
}
dPacket = null;
dataBuf = null;
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (SocketException e1) {
e1.printStackTrace();
System.out.println("在线信息接收失败!");
} finally {
dSocket.close();
}
}
}
其他为界面的构建与逻辑的处理。