UDP做的聊天项目(源代码)

本文介绍了一个基于Java的聊天应用程序的设计与实现过程,包括图形界面构建、消息处理机制及网络通信细节。该程序支持用户间的即时消息交换,并具备添加好友等功能。
[size=large][color=darkblue]package com.itany.chat;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.itany.chat.communication.MessageHandler;
import com.itany.chat.communication.Messenger;
import com.itany.chat.communication.UDPMessager;
import com.itany.chat.utils.ChatUtils;
public class ChatFrame implements MessageHandler {
private JFrame frame;

private JTextArea recvMsg;//接收内容的对话框

private JTextArea enterMsg;//发送内容的对话框

private JList userList;//存放好友的列表

private JButton addUserBtn;//添加好友的按钮

private JButton sendBtn;//发送消息的按钮

private int port;

private String name; //用户名

private Messenger messenger;

private static final String ECHO_STRING = "echo";

private DefaultListModel userListModel;

public ChatFrame() {
buildGUI();
init();
}

private void buildGUI() {
frame = new JFrame();
frame.setSize(600, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

recvMsg = new JTextArea();
recvMsg.setRows(1);
recvMsg.setColumns(10);
JScrollPane scrollRecv = new JScrollPane(recvMsg);
recvMsg.setEditable(false);//设置接收信息的文本框不可编辑

enterMsg = new JTextArea();
JScrollPane scrollEnter = new JScrollPane(enterMsg);
scrollEnter.setMinimumSize(new Dimension(100, 80));
scrollEnter.setPreferredSize(new Dimension(100, 80));

userListModel = new DefaultListModel();
userList = new JList(userListModel);
addUserBtn = new JButton("添加好友");
sendBtn = new JButton("发送消息");

JPanel sendPanel = new JPanel();
sendPanel.setLayout(new BorderLayout(7, 0));
sendPanel.add(scrollEnter, BorderLayout.CENTER);
sendPanel.add(sendBtn, BorderLayout.EAST);

JPanel userListPanel = new JPanel();
JScrollPane scrollUserList = new JScrollPane(userList);
scrollUserList.setMinimumSize(new Dimension(80, 80));
scrollUserList.setPreferredSize(new Dimension(80, 80));
userListPanel.setLayout(new BorderLayout(0, 3));
userListPanel.add(scrollUserList, BorderLayout.CENTER);
userListPanel.add(addUserBtn, BorderLayout.SOUTH);

Container container = frame.getContentPane();
container.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
gbc.insets = new Insets(7, 7, 7, 7);
container.add(scrollRecv, gbc);

gbc.gridx = 1;
gbc.gridy = 0;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.weightx = 0;
gbc.weighty = 0;
gbc.fill = GridBagConstraints.BOTH;
gbc.insets = new Insets(7, 0, 7, 7);
container.add(userListPanel, gbc);

gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2;
gbc.gridheight = 1;
gbc.weightx = 1.0;
gbc.insets = new Insets(0, 7, 7, 7);
gbc.fill = GridBagConstraints.BOTH;
container.add(sendPanel, gbc);

}


private void init() {
while (true) {
String[][] initValue = new String[][] { { "用户名:", "陆晓锋" },
{ "端口:", "9999" } };// 默认的初始的用户名与端口号
InputMesssageDialog initDialog = new InputMesssageDialog(frame,
"请输入用户名和端口", true, initValue);// true用来表示对话框的模态,frame是对话框所在的窗体
initDialog.setVisible(true);// 设置对话框可见
String[] nameAndPort = initDialog.getValue();

if (nameAndPort == null) {
continue;
}
name=nameAndPort[0];//用户名
try {
port = Integer.valueOf(nameAndPort[1]);// 得到端口号
if (port <= 0 || port > 65535) {
String errMsg = "错误的端口号" + port + ",端口号必须在0至65536之间";
JOptionPane.showMessageDialog(frame, errMsg, "错误的端口号",
JOptionPane.ERROR_MESSAGE);// 弹出一个对话框来显示错误信息
continue;
}

} catch (NumberFormatException e) {
//JOptionPane 有助于方便地弹出要求用户提供值或向其发出通知的标准对话框
JOptionPane.showMessageDialog(frame, "输入的端口号不是数字", "错误的端口号",
JOptionPane.ERROR_MESSAGE);// 弹出一个对话框来显示错误信息
}
break;
}


try {
messenger = new UDPMessager(port);// 父类Messager引用指向子类对象
messenger.setMessageHandler(this);// 设置消息处理器(是类本身的一个对象)
messenger.startMessenger();// 开启两个线程
} catch (BindException e) {
// 弹出一个对话框(用来显示错误信息)
JOptionPane.showMessageDialog(frame, "9999端口已经被占用,程序将退出",
"端口号已经被占用", JOptionPane.ERROR_MESSAGE);
System.exit(1);// 退出系统
} catch (Exception e) {
e.printStackTrace();
}

addUserBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addUser();//调用添加好友信息的方法addUser()
}
} );

// 利用匿名内部类实现按钮监听器事件
sendBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
/* String content = enterMsg.getText();// 得到输入对话框中的内容
SocketAddress adder = new InetSocketAddress("192.168.2.42",
9999);//对方的Ip地址和端口号
sendMessage(content, adder);
enterMsg.setText("");//设置输入内容发后之后清空
*/
// messenger.sendData(content.getBytes(), adder);// 把消息保存到list列表中去,调用了 sendMessage方法
sendChatMessage();
}
});
userList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
userList.addListSelectionListener(new ListSelectionListener(){

public void valueChanged(ListSelectionEvent e) {
Object selection=userList.getSelectedValue();
if (selection == null || !(selection instanceof UserModel)) {
return;
}
UserModel user = (UserModel) selection;
recvMsg.setText(user.getMessageHistory().toString());
updateTitle();
}
});
updateTitle();
}



private void sendMessage(String content, SocketAddress addr) {
byte[] data=ChatUtils.buildMessage(name, content);
messenger.sendData(data, addr);
}


//在聊天窗体上显示聊天人名字与端口号
private void updateTitle() {
String title = name + "的聊天窗口,通讯端口为" + port;
Object selection = userList.getSelectedValue();
if (selection != null && (selection instanceof UserModel)) {
title = title + ",对方为" + ((UserModel) selection).getName();
}
this.frame.setTitle(title);
}


private void sendChatMessage() {
Object selection = userList.getSelectedValue();
if (selection == null || !(selection instanceof UserModel)) {
JOptionPane.showMessageDialog(frame,"请选择好友,再发送消息","请选择好友",
JOptionPane.ERROR_MESSAGE);
return;
}
UserModel user = (UserModel) selection;
String content = enterMsg.getText();
sendMessage(content,user.getAddr());
if(content.length()==0){
return ;
}
enterMsg.setText("");
user.getMessageHistory().append(name+"说:\r\n"+content+"\r\n");
recvMsg.setText(user.getMessageHistory().toString());
}




/*private void sendChatMessage() {
Object selection = userList.getSelectedValue();
if (selection == null || !(selection instanceof UserModel)) {
JOptionPane.showMessageDialog(frame, "请先选择好友,再发送消息", "请选择好友",
JOptionPane.ERROR_MESSAGE);
return;
}
UserModel user = (UserModel) selection;
String content = enterMsg.getText();
sendMessage(content, user.getAddr());
if (content.length() == 0) {
return;
}
enterMsg.setText("");
user.getMessageHistory().append(name + "说:\r\n " + content + "\r\n");
recvMsg.setText(user.getMessageHistory().toString());
}
*/

// 窗体可见
public void showFrame() {
this.frame.setVisible(true);
}

//添加好友的方法
private void addUser() {
String[][] defaultUser = new String[][] { { "IP地址:", "192.168.2.43" },{ "端口号:", "7777" } };
InputMesssageDialog userProp = new InputMesssageDialog(frame, "请输入好友信息",
true, defaultUser);
userProp.setVisible(true);
String [] value=userProp.getValue();
if(value==null){
return ;
}

InetSocketAddress socketAddr = ChatUtils.createSocketAddrFromStr(
value[0], value[1]);
if (socketAddr == null) {
JOptionPane.showMessageDialog(frame, "输入的信息错误,无法识别的IP地址和端口号",
"信息错误", JOptionPane.ERROR_MESSAGE);
return;
}
sendMessage(ECHO_STRING, socketAddr);//实际添加好友发送的信息是发送name,content,与ECHO_STRING
}





private void updateChatHistory(String history) {
recvMsg.setText(history);
int lastLineStart = history.lastIndexOf('\n');
if (lastLineStart == -1) {
return;
}
recvMsg.setCaretPosition(lastLineStart);
}


//查找某对象是否存在
private UserModel findUser(UserModel model){
int n=userListModel.getSize();
for(int i=0;i<n;i++){
UserModel user= (UserModel)userListModel.get(i);
if(user.equals(model)){
return user;
}
}
return null;
}


// 因为类实现了处理消息借口 MessageHandler,所有要重写它的方法
public void handleMessage(byte[] data, SocketAddress addr) {
String[] content = ChatUtils.parseMessage(data);//对字节数组进行解析
String userName = content[0];//第一个元素是发给我信息人的名字
String message = content[1];//发送信息的内容
recvMsg.setText(userName+"说"+message);
UserModel newUser=new UserModel(userName,addr);
UserModel user=findUser(newUser);
if(user==null){
userListModel.addElement(newUser);
user=newUser;
}
if(message.equals(ECHO_STRING)){
this.sendMessage("",addr);
return ;

}

if(message.length()>0){
user.getMessageHistory().append(userName+"说:\r\n"+message+"\r\n");
}
userList.setSelectedValue(user, true);
updateChatHistory(user.getMessageHistory().toString());
}

}


package com.itany.chat;

import java.awt.Container;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JSeparator;
import javax.swing.JTextField;

import com.itany.chat.utils.ChatUtils;

public class InputMesssageDialog extends JDialog {
private String[][] items;
private JTextField[] values;
private String[] retValues;

public InputMesssageDialog(Frame owner, String title, boolean modal,
String[][] items) {
super(owner, title, modal);//modal指对话框的模式,模式的对话框只能操作对话框,其它的都不可以
this.items = items;
init();
}

private void init() {
Container container = this.getContentPane();
container.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
int count = items.length;
gbc.gridx = 0;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.fill = GridBagConstraints.NONE;
gbc.insets = new Insets(3, 3, 3, 3);
for (int i = 0; i < count; i++) {
gbc.gridy = i;
container.add(new JLabel(items[i][0]), gbc);
}

values = new JTextField[count];
gbc.gridx = 1;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.weightx = 1.0;
gbc.weighty = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
for (int i = 0; i < count; i++) {
gbc.gridy = i;
values[i] = new JTextField(items[i][1]);
container.add(values[i], gbc);
}

gbc.gridx = 0;
gbc.gridy = count;
gbc.gridwidth = 2;
gbc.gridheight = 1;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.SOUTH;
container.add(new JSeparator(), gbc);

gbc.gridy = count + 1;
gbc.weightx = 1.0;
gbc.weighty = 0;
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.SOUTHEAST;
gbc.insets = new Insets(7, 7, 7, 7);
JButton btn = new JButton("确定");
container.add(btn, gbc);
btn.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {
retValues = new String[items.length];
for (int i = 0; i < retValues.length; i++) {
retValues[i] = values[i].getText();
}
InputMesssageDialog.this.dispose();
}
});
this.setSize(300, count * 30 + 70);
ChatUtils.locateDialogCenter(this);
}

public String[] getValue() {

return retValues;
}

}
package com.itany.chat;

import java.net.SocketAddress;

public class UserModel {
private String name;

private SocketAddress addr;

private StringBuffer messageHistory;

public UserModel(String name, SocketAddress addr) {
this.name = name;
this.addr = addr;
this.messageHistory =new StringBuffer() ;
}

public SocketAddress getAddr() {
return addr;
}

public void setAddr(SocketAddress addr) {
this.addr = addr;
}

public StringBuffer getMessageHistory() {
return messageHistory;
}

public void setMessageHistory(StringBuffer messageHistory) {
this.messageHistory = messageHistory;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
public int hashCode(){
int result;
result=(addr==null)?0:addr.hashCode();
return result;
}
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj==null){
return false;
}
if(getClass()!=obj.getClass()){
return false;
}
UserModel u=(UserModel)obj;
if(!addr.equals(u.addr)){
return false;
}
return true;

}

public String toString() {
return name;
}
}
package com.itany.chat;

public class ChatFrameMain {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ChatFrame chat=new ChatFrame();
chat.showFrame();
}



}


package com.itany.chat.utils;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.net.InetSocketAddress;

import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JFrame;
import java.net.*;
public class ChatUtils {
private static JFrame frame;
public static final String SEPARATOR="#";
public static void locateDialogCenter(JDialog dialog) {
int frameWidth = dialog.getWidth();// 对话框的宽度
int frameHeight = dialog.getHeight();
Toolkit toolkit = Toolkit.getDefaultToolkit();// 获取默认工具包
Dimension screen = toolkit.getScreenSize();// 获取屏幕的大小
int screenwidth = screen.width;// 得到屏幕的宽度
int screenheight = screen.height;
// 设置对话框的位置
dialog.setLocation((screenwidth - frameWidth) / 2,
(screenheight - frameHeight) / 2);
}

public static InetSocketAddress createSocketAddrFromStr(String ipstr,
String port) {
int Port;
Port = Integer.valueOf(port);// 得到端口号
try {
if (Port <= 0 || Port > 65535) {
return null;
}
} catch (NumberFormatException e) {
return null;
}
String ip[] = ipstr.split("\\.");

try {
if (ip.length != 4) {
return null;
}
for (int i = 0; i < ip.length; i++) {
if (Integer.valueOf(ip[i]) > 255 || Integer.valueOf(ip[i]) < 0) {
return null;

}
}
} catch (NumberFormatException e) {
return null;
}
/*
* try { Port = Integer.valueOf(port);// 得到端口号 if (Port <= 0 || Port >
* 65535) { String errMsg = "错误的端口号" + port + ",端口号必须在0至65536之间";
* JOptionPane.showMessageDialog(frame, errMsg, "错误的端口号",
* JOptionPane.ERROR_MESSAGE);// 弹出一个对话框来显示错误信息 }
* } catch (NumberFormatException e) {
* JOptionPane.showMessageDialog(frame, "输入的端口号不是数字", "错误的端口号",
* JOptionPane.ERROR_MESSAGE);}
*
*
* String ip[]= ipstr.split("\\.");
*
* try{ if(ip.length!=4){ String errMsg = "错误的ip地址" + port +
* ",ip地址必须是四位"; JOptionPane.showMessageDialog(frame, errMsg,
* "错误的ip地址",JOptionPane.ERROR_MESSAGE); } for(int i=0;i<ip.length;i++){
* if(Integer.valueOf(ip[i])>255||Integer.valueOf(ip[i])<0){ String
* errMsg = "错误的ip地址" + port + ",ip地址必须在0到255之间";
* JOptionPane.showMessageDialog(frame, errMsg,
* "错误的ip地址",JOptionPane.ERROR_MESSAGE); } } } catch
* (NumberFormatException e) { JOptionPane.showMessageDialog(frame,
* "输入的ip地址不是数字", "错误的ip地址", JOptionPane.ERROR_MESSAGE);} return null;
*/

return new InetSocketAddress(ipstr,Port);
}


public static byte[] buildMessage(String name,String content){
String message=name+SEPARATOR+content;
return message.getBytes();

}

public static String[]parseMessage(byte[] data){
String message=new String(data);
int pos=message.indexOf(SEPARATOR);
if(pos==-1){
System.out.println("收到不符合格式的消息"+message);
return null;
}
String [] msg=new String[2];
msg[0]=message.substring(0,pos);
msg[1]=message.substring(pos+1);
return msg;

}

}


communication包:
package com.itany.chat.communication;

import java.net.SocketAddress;

//代表消息处理模块的接口
public interface MessageHandler {

//处理消息的方法,这个方法用来处理UDP消息收发模块接收到的消息
/**
* data 接收到的数据
* addr 消息来自哪个Socket地址
* */
public void handleMessage(byte[] data,SocketAddress addr);

}
package com.itany.chat.communication;

import java.net.SocketAddress;

//代表消息收发模块的接口
public interface Messenger {

//发送数据,消息处理模块和图形界面模块使用这个方法来发送UDP消息,所有的消息
//都由这个消息发送方法统一负责发送
public void sendData(byte[] data,SocketAddress addr);

//为UDP消息收发模块设置一个处理消息的类,UDP消息收发模块不知道如何
//处理消息,每次UDP消息收发模块收到一个消息后,就由消息处理器处理消息
public void setMessageHandler(MessageHandler hanler);

/*
*启动消息发送模块。应该启动两个线程,分别负责发送和接收UDP消息
* */
public void startMessenger();
}
package com.itany.chat.communication;

import java.net.SocketAddress;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;

import java.io.IOException;

public class UDPMessager implements Messenger {

public static int DEFAULT_PORT = 9999;

private DatagramSocket UDPWorker;

// 消息列表,每次发送消息的时候,其实将消息
// 封装成一个DatagramPacke对象
private List<DatagramPacket> messageList;

private MessageHandler handler;

public UDPMessager() throws SocketException {
this(DEFAULT_PORT);
}

public UDPMessager(int port) throws SocketException {
UDPWorker = new DatagramSocket(port);
messageList = new ArrayList<DatagramPacket>();
}

/*
* 把消息封装在DatagramPacket对象中,然后把它放入到messageList中
* 唤醒等待在messageList上的线程,等待在messageList上的线程就是 消息发送线程。
*/
public void sendData(byte[] data, SocketAddress addr) {
synchronized (messageList) {
DatagramPacket msg = null;
msg = new DatagramPacket(data, data.length);
msg.setSocketAddress(addr);
messageList.add(msg);
messageList.notifyAll();
}
}

public void setMessageHandler(MessageHandler hanler) {
this.handler = hanler;
}

public void startMessenger() {
Thread recvThread = new Thread(new MessageReciever());
recvThread.start();

Thread sendThread = new Thread(new MessageSender());
sendThread.start();
}

// 接收线程,内部类
class MessageReciever implements Runnable {
public void run() {
byte[] data = new byte[1024];
while (!Thread.currentThread().isInterrupted()) {
DatagramPacket msg = new DatagramPacket(data, data.length);

try {
UDPWorker.receive(msg);
} catch (Exception e) {
e.printStackTrace();
}

byte[] recvData = msg.getData();
byte[] realData = new byte[msg.getLength()];
System.arraycopy(recvData, 0, realData, 0, msg.getLength());
handler.handleMessage(realData, msg.getSocketAddress());
}
}
}

// 发送内部类,实现Runnable接口
class MessageSender implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
synchronized (messageList) {
while (messageList.size() == 0) { // 如果列表为空
try {
messageList.wait();
} catch (Exception e) {
e.printStackTrace();
}
}

DatagramPacket date = messageList
.get(messageList.size() - 1);
messageList.remove(messageList.size() - 1);

try {
UDPWorker.send(date);
} catch (Exception e) {
e.printStackTrace();
}

}

}
}
}

}[/color][/size]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值