Socket多人聊天(文字+图片+多文件发送和接收)

来源:互联网 发布:零基础学算法 编辑:程序博客网 时间:2024/05/21 22:25

主要实现:

1.群聊

2.私聊

3.发送文字(可选择字体,颜色)

4.发送图片

5.发送文件,支持多个文件同时发送/接收。


消息对象:

package com.socket.tcp.basechat;import java.io.File;import java.io.Serializable;import java.util.ArrayList;import java.util.Date;import java.util.List;/** * 包装发送的消息对象。 * @author lucky star. * */public class MsgInfo implements Serializable{/** * serialVersionUID */private static final long serialVersionUID = 2817564515497626133L;private String clientId; // 标示每个客户端// 私聊人private String user;// 是否私聊,设置USER时自动设置为TRUE,否则为FALSE。private boolean isSL = false;// 文件大小private int fileLen = -1;public int getFileLen() {return fileLen;}public void setFileLen(int fileLen) {this.fileLen = fileLen;}// 文件列表private String[] files = {};public String[] getFiles() {return files;}public void setFiles(String[] files) {this.files = files;}public boolean isSL() {return isSL;}public String getUser() {return user;}public void setUser(String user) {this.user = user;isSL = true;}// 文本消息private String msgContent;// 图片消息private List<File> imgs = new ArrayList<File>();// 消息发送时间private Date sendTime;// 发送人private String sender;// online listprivate List<String> onlines = new ArrayList<String>();public List<String> getOnlines() {return onlines;}public void setOnlines(List<String> onlines) {this.onlines = onlines;}public String getClientId() {return clientId;}public void setClientId(String clientId) {this.clientId = clientId;}// 一个客户端下线后通知其他客户端时,new一个空的消息对象。// 仅携带onlines,通知其他客户端更新任意列表public MsgInfo() {super();}public MsgInfo(String msgContent, List<File> imgs, List<File> attaches,Date sendTime, String sender) {super();this.msgContent = msgContent;this.imgs = imgs;this.sendTime = sendTime;this.sender = sender;}// 私聊时用到这个构造方法。public MsgInfo(String msgContent, List<File> imgs, List<File> attaches,Date sendTime, String sender,String user) {this(msgContent,imgs,attaches,sendTime,sender);this.user = user;}public MsgInfo(String msgContent) {this.msgContent = msgContent;}public String getMsgContent() {return msgContent;}public void setMsgContent(String msgContent) {this.msgContent = msgContent;}public List<File> getImgs() {return imgs;}public void setImgs(List<File> imgs) {this.imgs = imgs;}public Date getSendTime() {return sendTime;}public void setSendTime(Date sendTime) {this.sendTime = sendTime;}public String getSender() {return sender;}public void setSender(String sender) {this.sender = sender;}public boolean isEmpty() {boolean a = msgContent == null || msgContent.equals("");boolean b = imgs == null || imgs.size() == 0;boolean d = files == null || files.length == 0;if (a && b && d) return true;return false;}public boolean isMsg() {if (isEmpty()) {if (sender != null && !"".equals(sender)) {return false;}}return true;}}

服务端:

package com.socket.tcp.basechat;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.net.BindException;import java.net.ServerSocket;import java.net.Socket;import java.util.ArrayList;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Set;/** * Server端。 * @author lucky star * */public class Server {private ServerSocket ss = null;private boolean isStarted = false;private Map<String,RecvThread> clients = new HashMap<String,Server.RecvThread>();public void start() {try {ss = new ServerSocket(8888);isStarted = true;System.out.println("Tcp Server started.");} catch (BindException e) {System.out.println("端口被占用,请关闭相关程序。");e.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}while (isStarted) {try {Socket s = ss.accept();RecvThread rt = new RecvThread(s);new Thread(rt).start();} catch (IOException e) {System.out.println(e.getMessage());//e.printStackTrace();}}}private class RecvThread implements Runnable {private Socket s = null;private ObjectInputStream ois = null;private ObjectOutputStream oos = null;private boolean isConnected = false;private String user;public RecvThread(Socket s) {this.s = s;try {ois = new ObjectInputStream(s.getInputStream());oos = new ObjectOutputStream(s.getOutputStream());//System.out.println("oos:" + oos);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}isConnected = true;}// 发送对象public void sendMsg(MsgInfo mi) {try {oos.writeObject(mi);oos.flush();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 转发消息给各客户端public void zfMsg(MsgInfo mi) {if (mi.isSL()) { // 私聊// 发给对方。// 根据用户名得到对方的RecvThreadRecvThread rt = clients.get(mi.getUser());rt.sendMsg(mi);System.out.println("sl.");} else { // 群聊System.out.println("群聊。" + clients.size());List<String> onlines = new ArrayList<String>();Set<String> keys = clients.keySet();Iterator<String> itKeys = keys.iterator();while (itKeys.hasNext()) {String key = itKeys.next();onlines.add(key);}mi.setOnlines(onlines);mi.setClientId(this.s.toString());// 转发消息给各个客户端itKeys = keys.iterator();while (itKeys.hasNext()) {String key = itKeys.next();RecvThread rt = clients.get(key);if (mi.isMsg()) { // 发送消息时不转发给自己if (rt != this) {rt.sendMsg(mi);}}else { // 获取在线用户列表。rt.sendMsg(mi);}System.out.println("转发消息给 " + rt.user);}}}public void run() {while (isConnected) {try {// 读取信息MsgInfo mi = (MsgInfo) ois.readObject();// 激活连接if (!mi.isMsg()) { // 建立连接后发送一条空的信息,将用户名带到服务器。clients.put(mi.getSender(), this);user = mi.getSender();System.out.println(mi.getSender() + " Join.");}// 转发给客户端zfMsg(mi);} catch (IOException e) {// disconnect();disConnect();// e.printStackTrace();} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}// 发送对象信息使用public void disConnect() {try {System.out.println("Client " + user+ " exit.");isConnected = false;if (ois != null)ois.close();if (oos != null)oos.close();if (s != null)s.close();clients.remove(this.user);// 一个客户端退出后,通知其他客户端MsgInfo mi = new MsgInfo();zfMsg(mi);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}/** * @param args */public static void main(String[] args) {new Server().start();}}


客户端:
package com.socket.tcp.basechat;import java.awt.BorderLayout;import java.awt.Color;import java.awt.FlowLayout;import java.awt.Font;import java.awt.GraphicsEnvironment;import java.awt.Image;import java.awt.MenuItem;import java.awt.Panel;import java.awt.PopupMenu;import java.awt.datatransfer.DataFlavor;import java.awt.datatransfer.Transferable;import java.awt.datatransfer.UnsupportedFlavorException;import java.awt.dnd.DnDConstants;import java.awt.dnd.DropTarget;import java.awt.dnd.DropTargetDragEvent;import java.awt.dnd.DropTargetDropEvent;import java.awt.dnd.DropTargetEvent;import java.awt.dnd.DropTargetListener;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.ItemEvent;import java.awt.event.ItemListener;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.awt.event.WindowAdapter;import java.awt.event.WindowEvent;import java.io.BufferedInputStream;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.RandomAccessFile;import java.net.InetAddress;import java.net.ServerSocket;import java.net.Socket;import java.net.UnknownHostException;import java.util.ArrayList;import java.util.List;import javax.imageio.ImageIO;import javax.swing.DefaultComboBoxModel;import javax.swing.ImageIcon;import javax.swing.JButton;import javax.swing.JCheckBox;import javax.swing.JColorChooser;import javax.swing.JComboBox;import javax.swing.JDialog;import javax.swing.JFileChooser;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JList;import javax.swing.JOptionPane;import javax.swing.JPanel;import javax.swing.JScrollPane;import javax.swing.JTable;import javax.swing.JTextArea;import javax.swing.JTextPane;import javax.swing.ListSelectionModel;import javax.swing.border.EmptyBorder;import javax.swing.event.ChangeEvent;import javax.swing.event.ChangeListener;import javax.swing.filechooser.FileFilter;import javax.swing.table.TableModel;import javax.swing.text.AttributeSet;import javax.swing.text.BadLocationException;import javax.swing.text.Document;import javax.swing.text.SimpleAttributeSet;import javax.swing.text.StyleConstants;/** * 实现群、私聊、发送文字、图片、文件。 * 文件支持单次同时发送7个,多线程接收。 * @author lucky star * */public class Main extends JFrame {private JPanel contentPane;private JTextPane sendPane = null;private JTextPane recvPane = null;private JList onlineList = null;private JButton sendFileBtn = null, recvFileBtn = null;// 发送和接收的进度条// private JProgressBar sendProgressBar = null, recvProgressBar = null;// 要发送的文件private List<String> sendFiles = new ArrayList<String>();// 要接收的文件private List<String> recvFiles = new ArrayList<String>();// 文件发送进度private int fileLen = 0;// chat use TCP protocolprivate Socket s = null;private ObjectOutputStream oos = null;private Document doc = null;private Document slDoc = null;private String user = null;// 是否勾选私聊private boolean isSL = false;// 与哪个私聊private String slUser = null;// 私聊面板private JTextPane sltextPane = null;private Font f = null; // 字体对话框返回的字体。// 发送和接受消息面板的样式private SimpleAttributeSet msgAttrSet = new SimpleAttributeSet();// 显示发送人和时间的样式private SimpleAttributeSet tipAttrSet = null;private JTable table;public Font getF() {return f;}public void setF(Font f) {this.f = f;}/** * Launch the application. *//* * public static void main(String[] args) { EventQueue.invokeLater(new * Runnable() { public void run() { try { Main frame = new Main(); * frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } * }); } */// connect to serverpublic void connect() {try {s = new Socket(InetAddress.getLocalHost(), 8888);oos = new ObjectOutputStream(s.getOutputStream());} catch (UnknownHostException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// send msg to serverpublic void sendMsg(MsgInfo mi) {try {oos.writeObject(mi);oos.flush();} catch (IOException e) {System.out.println("send msg failed.");e.printStackTrace();}}// disconnect to server when exit.public void disconnect() {try {if (oos != null)oos.close();if (s != null)s.close();} catch (IOException e) {System.out.println("exit chat.");e.printStackTrace();}}public void insertString(String str, AttributeSet attributeset) {try {doc.insertString(doc.getLength(), str, attributeset);} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public void insertImg(Image img) {recvPane.insertIcon(new ImageIcon(img));}/** * Create the frame. */public Main(String user) {this.user = user;setTitle("User " + user);FontAttr fa = new FontAttr(new Font("楷体", Font.PLAIN, 12), Color.BLUE,Color.WHITE);tipAttrSet = fa.getAttributeSet();setF(new Font("楷体", Font.PLAIN, 12));setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setBounds(100, 100, 773, 651);setLocationRelativeTo(null);contentPane = new JPanel();contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));setContentPane(contentPane);contentPane.setLayout(null);JScrollPane jscrollPane1 = new JScrollPane();jscrollPane1.setBounds(0, 28, 432, 199);contentPane.add(jscrollPane1);recvPane = new JTextPane();jscrollPane1.setViewportView(recvPane);recvPane.setEditable(false);doc = recvPane.getStyledDocument();JButton imgBtn = new JButton("\u56FE\u7247");imgBtn.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent arg0) {// 选择图片JFileChooser jfc = new JFileChooser();jfc.setDialogTitle("选择一张图片");jfc.setFileFilter(new FileFilter() {private String accepts[] = { "jpg", "jpeg", "gif", "png","bmp" };@Overridepublic String getDescription() {// TODO Auto-generated method stubreturn null;}@Overridepublic boolean accept(File arg0) {boolean b = false;for (String s : accepts) {if (arg0.getName().endsWith(s)) {b = true;break;}}return b;}});int r = jfc.showOpenDialog(Main.this);if (r == JFileChooser.APPROVE_OPTION) { // 确定File f = jfc.getSelectedFile();List<File> imgs = new ArrayList<File>();imgs.add(f);MsgInfo mi = new MsgInfo();mi.setSender(Main.this.user);mi.setImgs(imgs);if (isSL) { // 私聊mi.setUser(slUser);// 将图片添加到私聊窗口try {try {slDoc.insertString(slDoc.getLength(),Main.this.user + "\t"+ TimeUtil.getSysTime() + "\n",tipAttrSet);sltextPane.setCaretPosition(slDoc.getLength());sltextPane.insertIcon(new ImageIcon(ImageIO.read(f)));slDoc.insertString(slDoc.getLength(), "\n",tipAttrSet);} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}} else {// 将图片添加到群聊窗口try {insertString(Main.this.user + "\t"+ TimeUtil.getSysTime() + "\n",tipAttrSet);recvPane.setCaretPosition(doc.getLength());insertImg(ImageIO.read(f));insertString("\n", tipAttrSet);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}sendMsg(mi);}}});imgBtn.setBounds(264, 453, 65, 23);contentPane.add(imgBtn);JButton fileBtn = new JButton("\u6587\u4EF6");fileBtn.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent arg0) { // 发送文件JFileChooser jfc = new JFileChooser("D:/");jfc.setDialogTitle("选择文件或目录");jfc.setDialogType(JFileChooser.OPEN_DIALOG);int r = jfc.showOpenDialog(Main.this);if (r == JFileChooser.APPROVE_OPTION) { // 单击确定按钮File file = jfc.getSelectedFile();table.getModel().setValueAt(file.getName(), 0, 0);sendFiles.clear();sendFiles.add(file.getPath());sendFileBtn.setEnabled(true);//new SendFileTrd(10000, 0, file.getPath()).start();}}});fileBtn.setBounds(339, 453, 72, 23);contentPane.add(fileBtn);JScrollPane scrollPane_1 = new JScrollPane();scrollPane_1.setBounds(0, 487, 431, 86);contentPane.add(scrollPane_1);sendPane = new JTextPane();scrollPane_1.setViewportView(sendPane);JButton sendMsgBtn = new JButton("\u53D1\u9001");sendMsgBtn.addActionListener(new ActionListener() {sendMsgBtn.setBounds(366, 583, 66, 23);contentPane.add(sendMsgBtn);JLabel label_3 = new JLabel("\u5728\u7EBF\u5217\u8868");label_3.setBounds(450, 10, 54, 15);contentPane.add(label_3);JButton button = new JButton("\u5B57\u4F53");button.addActionListener(new ActionListener() { // 字体button.setBounds(0, 456, 72, 23);contentPane.add(button);JButton button_1 = new JButton("\u5B57\u4F53\u989C\u8272");button_1.addActionListener(new ActionListener() {button_1.setBounds(78, 455, 94, 23);contentPane.add(button_1);JButton button_2 = new JButton("\u80CC\u666F");button_2.addActionListener(new ActionListener() {button_2.setBounds(182, 453, 72, 23);contentPane.add(button_2);addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent windowevent) {disconnect();System.exit(0);}});JScrollPane scrollPane = new JScrollPane();scrollPane.setBounds(0, 272, 432, 171);contentPane.add(scrollPane);sltextPane = new JTextPane();slDoc = sltextPane.getStyledDocument();scrollPane.setViewportView(sltextPane);JLabel label_1 = new JLabel("\u7FA4\u804A\uFF1A");label_1.setBounds(0, 3, 54, 15);contentPane.add(label_1);onlineList = new JList();onlineList.setBounds(442, 28, 313, 314);contentPane.add(onlineList);JLabel label_2 = new JLabel("\u79C1\u804A\uFF1A");label_2.setBounds(0, 245, 54, 15);contentPane.add(label_2);final JCheckBox checkBox = new JCheckBox("\u79C1\u804A");checkBox.setBounds(239, 583, 103, 23);contentPane.add(checkBox);JScrollPane scrollPane_2 = new JScrollPane();scrollPane_2.setBounds(442, 387, 313, 192);contentPane.add(scrollPane_2);String[][] data = new String[7][2];for (int i = 0; i < 7; i++) {for (int j = 0; j < 2; j++) {data[i][j] = "";}}String[] headers = { "文件", "进度" };PopupMenu popupmenu = new PopupMenu();popupmenu.add(new MenuItem("移除"));table = new JTable(data, headers);scrollPane_2.setViewportView(table);table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);table.setRowHeight(25);table.add(popupmenu);table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);JLabel label = new JLabel("\u6587\u4EF6\u53D1\u9001/\u63A5\u6536\uFF1A");label.setBounds(450, 352, 138, 25);contentPane.add(label);onlineList.addMouseListener(new MouseAdapter() {@Overridepublic void mousePressed(MouseEvent mouseevent) {// 私聊slUser = (String) onlineList.getSelectedValue();}});// 私聊checkboxcheckBox.addChangeListener(new ChangeListener() {public void stateChanged(ChangeEvent arg0) {isSL = checkBox.isSelected();boolean a = false,b =false;if (isSL && (slUser == null || slUser.equals(""))) {a = true;}else if (slUser != null && slUser.equals(Main.this.user)) {b = true;}if (a || b) {String tipTxt = a?"请先选择要私聊的人":"跟自己聊天很有意思吗?";JOptionPane.showMessageDialog(Main.this, tipTxt, "提示", JOptionPane.WARNING_MESSAGE);checkBox.setSelected(false);isSL = false;}}});// table.add// 文件拖拽支持DropFile df = new DropFile();DropTarget dt = new DropTarget(contentPane,DnDConstants.ACTION_REFERENCE, df, true);contentPane.setDropTarget(dt);sendFileBtn = new JButton("\u53D1\u9001\u6587\u4EF6");sendFileBtn.setEnabled(false);sendFileBtn.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent actionevent) { // 发送文件sendFileBtn.setEnabled(false);int port = 9999; // 发送文件的端口号,10000起。// 启动发送文件的线程。for (String filename : Main.this.sendFiles) {port++;// 对应的行和列int rowIdx = 0;TableModel tm = table.getModel();int rows = tm.getRowCount();for (int i = 0; i < rows; i++) {String column0 = (String) tm.getValueAt(i, 0);if (filename.endsWith(column0)) {// 第二列是文件发送的进度。rowIdx = i;System.out.println(column0 + ":" + rowIdx);break;}}if (isSL) {try {slDoc.insertString(slDoc.getLength(),"您\t"+ TimeUtil.getSysTime()+ "\n给"+ slUser+ "发送文件:"+ filename.substring(filename.lastIndexOf("\\")) + "\n",tipAttrSet);} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}} else {insertString("您"+ TimeUtil.getSysTime()+ "\n"+ Main.this.user+ "发送文件:"+ filename.substring(filename.lastIndexOf("\\")) + "给所有人\n",tipAttrSet);}System.out.println("启动发送文件" + filename + "线程。");new SendFileTrd(port, rowIdx, filename).start();}}});sendFileBtn.setBounds(555, 583, 81, 23);contentPane.add(sendFileBtn);recvFileBtn = new JButton("\u63A5\u6536\u6587\u4EF6");recvFileBtn.setEnabled(false);recvFileBtn.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent actionevent) { // 接收文件recvFileBtn.setEnabled(false);// 弹出文件保存的位置String dir = null;int rowIdx = 0;JFileChooser jfc = new JFileChooser();jfc.setDialogTitle("选择文件保存的位置");jfc.setSelectedFile(new File("D:\\"));jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); // 选择文件夹。int r = jfc.showOpenDialog(Main.this);if (r == JFileChooser.APPROVE_OPTION) {File path = jfc.getSelectedFile();dir = path.getPath();}System.out.println("共"+Main.this.recvFiles.size()+"个文件。");int port = 9999; // 接收文件的端口号,10000起。for (int i = 0; i < Main.this.recvFiles.size(); i++) {port++;String filename = Main.this.recvFiles.get(i);int rows = table.getModel().getRowCount();for (int j=0;j<rows;j++) {if (table.getModel().getValueAt(j, 0).equals(filename)) {rowIdx = j;break;}}new RecvFileTrd(port, rowIdx, dir, filename).start();System.out.println("开始接收文件" + filename);}}});recvFileBtn.setBounds(646, 583, 81, 23);contentPane.add(recvFileBtn);connect();new Thread(new RecvTrd(this)).start();// send empty msg to get online user listMsgInfo mi = new MsgInfo();mi.setSender(user);sendMsg(mi);}public SimpleAttributeSet getMsgAttrSet() {return msgAttrSet;}public void setMsgAttrSet(SimpleAttributeSet msgAttrSet) {this.msgAttrSet = msgAttrSet;}/** * 接收消息、图片、文件的线程。 *  * @author lucky star *  */private class RecvTrd implements Runnable {private Main mcf = null;private boolean isConnected = false;private ObjectInputStream ois = null;public RecvTrd(Main mcf) {this.mcf = mcf;try {ois = new ObjectInputStream(mcf.s.getInputStream());isConnected = true;} catch (IOException e) {disconnect();System.out.println("连接服务器异常:" + e.getMessage());}}public void run() {while (isConnected) {try {// read data from InputStream.MsgInfo mi = (MsgInfo) ois.readObject();if (!mi.isSL()) { // 群聊信息显示到群聊窗口。// if data is not null,then append it to the receive// panel.if (mi.getMsgContent() != null&& !"".equals(mi.getMsgContent())) {insertString(mi.getSender() + "\t"+ TimeUtil.getSysTime() + "\n",tipAttrSet);insertString(mi.getMsgContent() + "\n", msgAttrSet);// put the frame to front,let the use to know that// there is msg coming.mcf.toFront();}if (mi.getImgs() != null && mi.getImgs().size() > 0) { // 发送图片List<File> imgs = mi.getImgs();insertString(mi.getSender() + "\t"+ TimeUtil.getSysTime() + "\n",tipAttrSet);recvPane.setCaretPosition(doc.getLength());for (int i = 0; i < imgs.size(); i++) {File f = imgs.get(i);insertImg(ImageIO.read(f));}insertString("\n", msgAttrSet);mcf.toFront();}// get online user listif (mi.getOnlines() != null&& mi.getOnlines().size() > 0) {// put the current online user to onlineListmcf.onlineList.setListData(mi.getOnlines().toArray());}} else { // 信息显示到私聊窗口。System.out.println("私聊。");if (mi.getMsgContent() != null&& !mi.getMsgContent().equals("")) {try {slDoc.insertString(slDoc.getLength(),mi.getSender() + "\t"+ TimeUtil.getSysTime() + "\n",tipAttrSet);slDoc.insertString(slDoc.getLength(),mi.getMsgContent() + "\n", msgAttrSet);} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}mcf.toFront();}if (mi.getImgs() != null && mi.getImgs().size() > 0) { // 发送图片List<File> imgs = mi.getImgs();try {slDoc.insertString(slDoc.getLength(),mi.getSender() + "\t"+ TimeUtil.getSysTime()+ "\n\n", tipAttrSet);} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}sltextPane.setCaretPosition(slDoc.getLength());for (int i = 0; i < imgs.size(); i++) {File f = imgs.get(i);sltextPane.insertIcon(new ImageIcon(ImageIO.read(f)));}insertString("\n", tipAttrSet);mcf.toFront();}}if (mi.getFiles() != null && mi.getFiles().length > 0) { // 发送过来文件System.out.println("接收文件");if (mi.isSL()) {Main.this.isSL = true;Main.this.slUser = mi.getSender();}String[] files = mi.getFiles();recvFiles.clear();System.out.println("发送文件"+files.length+"个");for (int i = 0; i < files.length; i++) {String f = files[i];// d:\a\b\c.txttable.getModel().setValueAt(f.substring(f.lastIndexOf("\\") + 1), i, 0);recvFiles.add(f.substring(f.lastIndexOf("\\")+1));}// 通知对方有文件接收recvFileBtn.setEnabled(true);mcf.toFront();}if (mi.getFileLen() != -1) { // 接收文件// recvProgressBar.setMaximum(mi.getFileLen());fileLen = mi.getFileLen();System.out.println("需发送数据包 " + fileLen + "次...");}} catch (IOException e) {System.out.println("read msg failed.");isConnected = false;try {if (ois != null)ois.close();if (s != null)s.close();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}// e.printStackTrace();} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}/** * 字体属性集合。 *  * @author lucky star *  */private class FontAttr {@Overridepublic String toString() {return "FontAttr [fontName=" + fontName + ", fontStyle="+ fontStyle + ", fontSize=" + fontSize + ", foreColor="+ foreColor + ", backColor=" + backColor + "]";}private String fontName;private int fontStyle;private int fontSize;private Color foreColor = null, backColor = null;private SimpleAttributeSet attrSet = new SimpleAttributeSet();public FontAttr(SimpleAttributeSet sas, Font f) {this(f);this.attrSet = sas;}public FontAttr(Font f, Color foreColor, Color backColor) {this(f);this.foreColor = foreColor;this.backColor = backColor;}public FontAttr(Font f) {this.fontName = f.getName();this.fontSize = f.getSize();this.fontStyle = f.getStyle();}public SimpleAttributeSet getAttributeSet() {if (fontName != null && !"".equals(fontName)) {StyleConstants.setFontFamily(attrSet, fontName);}if (fontStyle == 0) { // 常规StyleConstants.setBold(attrSet, false);StyleConstants.setItalic(attrSet, false);} else if (fontStyle == 1) { // 粗体StyleConstants.setBold(attrSet, true);StyleConstants.setItalic(attrSet, false);} else if (fontStyle == 2) { // 斜体StyleConstants.setBold(attrSet, false);StyleConstants.setItalic(attrSet, true);} else if (fontStyle == 3) { // 粗体 斜体StyleConstants.setBold(attrSet, true);StyleConstants.setItalic(attrSet, true);}if (foreColor != null) { // 背景色StyleConstants.setForeground(attrSet, foreColor);}if (backColor != null) { // 前景色StyleConstants.setBackground(attrSet, backColor);}if (fontSize != -1) {StyleConstants.setFontSize(attrSet, fontSize);}return attrSet;}}/** * 字体选择对话框。 *  * @author lucky star *  */private class FontDialog extends JDialog {private final JPanel contentPanel = new JPanel();private JComboBox fontNameBox = null;private JComboBox fontStyleBox = null;private JComboBox fontSizeBox = null;private JTextArea txtrHereIs = null;private String fontName;private String fontStyle;private String fontSize;private int fontSty;private int fontSiz;/** * Create the dialog. */public FontDialog(final Main main) {this.setModal(true);setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);setLocationRelativeTo(main);setTitle("\u5B57\u4F53");setBounds(100, 100, 483, 234);getContentPane().setLayout(new BorderLayout());contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));getContentPane().add(contentPanel, BorderLayout.CENTER);contentPanel.setLayout(null);{JLabel lblf = new JLabel("\u5B57\u4F53(F):");lblf.setBounds(0, 10, 54, 15);contentPanel.add(lblf);}{JLabel lbly = new JLabel("\u5B57\u5F62(Y):");lbly.setBounds(182, 10, 54, 15);contentPanel.add(lbly);}{JLabel lbls = new JLabel("\u5927\u5C0F(S):");lbls.setBounds(315, 10, 54, 15);contentPanel.add(lbls);}{JLabel label = new JLabel("\u663E\u793A\u6548\u679C:");label.setBounds(126, 82, 64, 15);contentPanel.add(label);}Panel panel = new Panel();panel.setBounds(196, 40, 228, 113);contentPanel.add(panel);panel.setLayout(null);{txtrHereIs = new JTextArea();txtrHereIs.setBounds(39, 38, 177, 44);txtrHereIs.setText("\u8FD9\u91CC\u663E\u793A\u9884\u89C8\r\nHere is the preview");panel.add(txtrHereIs);}{fontNameBox = new JComboBox();fontNameBox.setBounds(49, 7, 123, 21);contentPanel.add(fontNameBox);fontNameBox.addItemListener(new ItemListener() {public void itemStateChanged(ItemEvent itemevent) {fontName = (String) itemevent.getItem();System.out.println(fontName);// change previewFont f = new Font(fontName, fontSty, fontSiz);txtrHereIs.setFont(f);}});}{fontStyleBox = new JComboBox();fontStyleBox.setBounds(232, 7, 73, 21);contentPanel.add(fontStyleBox);fontStyleBox.addItemListener(new ItemListener() {public void itemStateChanged(ItemEvent itemevent) {fontStyle = (String) itemevent.getItem();fontSty = getFontStyleByCnName(fontStyle);// change previewFont f = new Font(fontName, fontSty, fontSiz);txtrHereIs.setFont(f);}});}{fontSizeBox = new JComboBox();fontSizeBox.setBounds(379, 7, 78, 21);contentPanel.add(fontSizeBox);fontSizeBox.addItemListener(new ItemListener() {public void itemStateChanged(ItemEvent itemevent) {fontSize = (String) itemevent.getItem();fontSiz = Integer.parseInt(fontSize);// change previewFont f = new Font(fontName, fontSty, fontSiz);txtrHereIs.setFont(f);}});}{JPanel buttonPane = new JPanel();buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));getContentPane().add(buttonPane, BorderLayout.SOUTH);{JButton okButton = new JButton("\u786E\u5B9A");okButton.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent actionevent) {int fontSty = getFontStyleByCnName(fontStyle);int fontSiz = Integer.parseInt(fontSize);/* * JOptionPane.showMessageDialog(FontDialog.this, * "你选择的字体名称:" + fontName + ",字体样式:" + fontStyle + * ",字体大小:" + fontSiz, "提示", * JOptionPane.CLOSED_OPTION); */main.setF(getChoice());Font f = main.getF();System.out.println("fontName:" + f.getName()+ ",fontStyle:" + f.getStyle()+ ",fontSize:" + f.getSize());FontDialog.this.dispose();}});okButton.setActionCommand("OK");buttonPane.add(okButton);getRootPane().setDefaultButton(okButton);}{JButton cancelButton = new JButton("\u53D6\u6D88");cancelButton.setActionCommand("Cancel");buttonPane.add(cancelButton);cancelButton.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent actionevent) {FontDialog.this.dispose();}});}}// 初始化字体名称GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();String[] fontNames = ge.getAvailableFontFamilyNames();fontNameBox.setModel(new DefaultComboBoxModel(fontNames));// 初始化字体样式String[] fontStyles = { "常规", "斜体", "粗体", "粗斜体" };fontStyleBox.setModel(new DefaultComboBoxModel(fontStyles));// 初始化字体大小String[] fontSizes = { "8", "9", "10", "11", "12", "14", "16","18", "20", "22", "24", "26", "28", "36", "48", "72" };fontSizeBox.setModel(new DefaultComboBoxModel(fontSizes));System.out.println("finish.");// 根据聊天窗口的字体设置来设置fontNameBox.setSelectedItem(main.getF().getName());fontSizeBox.setSelectedItem(main.getF().getSize() + "");fontStyleBox.setSelectedIndex(main.getF().getStyle());fontStyle = (String) fontStyleBox.getSelectedItem();fontSize = (String) fontSizeBox.getSelectedItem();fontSty = getFontStyleByCnName(fontStyle);fontSiz = Integer.parseInt(fontSize);}public Font getChoice() {return new Font(fontName, fontSty, fontSiz);}public int getFontStyleByCnName(String fontStyle) {if (fontStyle.equals("常规")) {return Font.PLAIN;}if (fontStyle.equals("斜体")) {return Font.ITALIC;}if (fontStyle.equals("粗体")) {return Font.BOLD;}if (fontStyle.equals("粗斜体")) {return Font.BOLD + Font.ITALIC;}return -1;}}/** * 拖拽文件。 *  * @author lucky star *  */private class DropFile implements DropTargetListener {public void dragEnter(DropTargetDragEvent droptargetdragevent) {// TODO Auto-generated method stub}public void dragOver(DropTargetDragEvent droptargetdragevent) {// TODO Auto-generated method stub}public void dropActionChanged(DropTargetDragEvent droptargetdragevent) {// TODO Auto-generated method stub}public void dragExit(DropTargetEvent droptargetevent) {// TODO Auto-generated method stub}public void drop(DropTargetDropEvent droptargetdropevent) {droptargetdropevent.acceptDrop(DnDConstants.ACTION_REFERENCE);if (droptargetdropevent.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {Transferable trans = droptargetdropevent.getTransferable();try {@SuppressWarnings("unchecked")List<File> files = (List<File>) trans.getTransferData(DataFlavor.javaFileListFlavor);if (files.size() > 7) { // 主要是显示发送/接收的table事写死的7行,没有添加动态添加行。JOptionPane.showMessageDialog(Main.this, "一次最多只能发送7个文件", "提示", JOptionPane.WARNING_MESSAGE);return; }for (int i = 0; i < files.size(); i++) {File f = files.get(i);table.getModel().setValueAt(f.getName(), i, 0);sendFiles.add(f.getPath());}if (files.size() > 0) {sendFileBtn.setEnabled(true);}} catch (UnsupportedFlavorException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}/** * 发送文件的线程,一次发送一个文件。多个文件时多个发送线程同时启动。 *  * @author lucky star *  */private class SendFileTrd extends Thread {private int port = 0;private int rowIdx = 0;private String filename = null;private int tmpfileLen = 0;public SendFileTrd(int port, int rowIdx, String filename) {this.port = port;this.rowIdx = rowIdx;this.filename = filename;}public void run() {// 获取要发送的文件File f = new File(filename);int db = 0;if ((f.length() % (1024 * 100) != 0)) {db = (int) (f.length() / (1024 * 100)) + 1;}if (db < 1) {db = 1;}System.out.println("[SendFileTrd]:将要发送数据包" + db + "次");// 先发信息通知对方接收文件,并将文件的总大小发过去。MsgInfo mi = new MsgInfo();mi.setSender(user);// 接收方只能看到文件名,不能看到发送方的文件完整路径。String[] sfiles = new String[sendFiles.size()];for (int i = 0; i < sendFiles.size(); i++) {sfiles[i] = sendFiles.get(i).substring(sendFiles.get(i).lastIndexOf("\\") + 1);System.out.println("通知对方接收文件:" + sfiles[i]);}mi.setFiles(sfiles);if (isSL) {mi.setUser(slUser);}mi.setFileLen(db); // 设置对方接收进度条的最大值。sendMsg(mi);System.out.println("已经通知对方。。。");ServerSocket ss = null;Socket s = null;DataOutputStream dos = null;FileInputStream fis = null;// 创建文件发送 服务器,对方点接收文件按钮后创建一个Socket,连接发送服务器。try {ss = new ServerSocket(port);s = ss.accept(); // 阻塞直到对方单击接收文件按钮,选择保存路径,这样就激活连接了。// 获取网络输出流dos = new DataOutputStream(s.getOutputStream());// sendProgressBar.setMaximum(db);// sendProgressBar.setMinimum(0);fis = new FileInputStream(f);byte[] buff = new byte[1024 * 100]; // 一次发送100Kbint len = -1;while ((len = fis.read(buff)) != -1) {tmpfileLen++;// sendProgressBar.setValue(tmpfileLen);// sendProgressBar// .setString("已发送"// + (100 * tmpfileLen / db)// + "%");table.getModel().setValueAt("已发送" + (100 * tmpfileLen / db) + "%", rowIdx, 1);// 写数据到网络输出流dos.write(buff, 0, len);dos.flush();}try {if (isSL) {slDoc.insertString(slDoc.getLength(), "系统消息:\t"+ TimeUtil.getSysTime() + "\n文件【" + filename+ "】发送完毕\n", tipAttrSet);} else {insertString("系统消息:\t" + TimeUtil.getSysTime()+ "\n文件【" + filename + "】发送完毕\n", tipAttrSet);}} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}// sendProgressBar.setValue(0);// sendProgressBar.setString("");sendFiles.remove(filename);// 一个文件发送完毕,从表格中清除。table.getModel().setValueAt("", rowIdx, 0);table.getModel().setValueAt("", rowIdx, 1);System.out.println("send rowIdx:" + rowIdx);if (sendFiles.size() == 0) {if (isSL) {try {slDoc.insertString(slDoc.getLength(), "系统消息:\t"+ TimeUtil.getSysTime() + "\n所有文件都已发送完毕\n",tipAttrSet);} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}} else {insertString("系统消息:\t" + TimeUtil.getSysTime()+ "\n所有文件都已发送完毕\n", tipAttrSet);}}} catch (IOException e) {try {if (isSL) {slDoc.insertString(slDoc.getLength(),"系统消息:\t" + TimeUtil.getSysTime() + "\n文件发送异常:"+ e.getMessage() + "\n", tipAttrSet);} else {insertString("系统消息:\t" + TimeUtil.getSysTime()+ "\n文件发送异常:" + e.getMessage() + "\n",tipAttrSet);}} catch (BadLocationException ex) {// TODO Auto-generated catch blocke.printStackTrace();}} finally {try {if (fis != null)fis.close();if (dos != null)dos.close();if (s != null)s.close();if (ss != null)ss.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}/** * 接收文件的线程。一个线程负责接收一个文件。多个文件时多个接收线程同时启动。 *  * @author lucky star *  */private class RecvFileTrd extends Thread {private Socket s = null;// table的row index,用于在接收某个文件完毕后,从表格中删除。private int rowIdx = 0;// 文件保存的位置private String filePath = null;// 接收的文件名private String recvName = null;// 端口号private int port = 0;private int tmpFileLen = 0;private int total = 0; // 记录已经读取的文件字节数。public RecvFileTrd(int port, int rowIdx, String filePath,String recvName) {this.port = port;this.rowIdx = rowIdx;this.filePath = filePath;this.recvName = recvName;}public void run() {DataInputStream dis = null;RandomAccessFile raf = null;try {// 连接文件发送服务器。s = new Socket(InetAddress.getLocalHost(), port);// 获取网络输入流。dis = new DataInputStream(new BufferedInputStream(s.getInputStream()));// 文件要保存的位置。File path = new File(filePath +File.separator+ recvName);int num = 0;while (path.exists()) { // 文件存在时自动改名。num++;recvName = recvName.substring(0,recvName.lastIndexOf(".")) + "("+num +")"+ recvName.substring(recvName.lastIndexOf("."));path = new File(filePath +File.separator+ recvName);}path.createNewFile();raf = new RandomAccessFile(path, "rw");// recvProgressBar.setMinimum(0);// recvProgressBar.setMaximum(fileLen);System.out.println("接收数据包" + fileLen + "次");byte[] buff = new byte[1024 * 100]; // 一次接收100Kbint len = -1;// 不能使用一次读取100K计数加1的方式计算读取的进度。// 因为可能没有读取到100K。这样计算的进度就有误。// 通过total叠加每次读取的字节数,然后除以100K,计算进度。// fileLen是文件发送服务器计算的文件长度。它是通过文件总大小除以100K计算来的。while ((len = dis.read(buff)) != -1) {total += len;tmpFileLen = total / (1024*100);// recvProgressBar.setValue(tmpFileLen);// recvProgressBar.setString("已接收"// + (100 * tmpFileLen / fileLen) + "%");// System.out.println("recv file progress:" + (100 *// tmpFileLen / fileLen) + "%");table.getModel().setValueAt("已接收" + (100 * tmpFileLen / fileLen) + "%", rowIdx,1);System.out.println("tmpFileLen:" + tmpFileLen + ",fileLen:" + fileLen + ",len:" + len);raf.write(buff, 0, len);}System.out.println("tmpFileLen :" + tmpFileLen + ",fileLen:" + fileLen);try {if (isSL) {slDoc.insertString(slDoc.getLength(), "系统消息:\t"+ TimeUtil.getSysTime() + "\n文件【" + recvName+ "】接收完毕。\n文件保存在" + filePath + "\n", tipAttrSet);} else {insertString("系统消息:\t" + TimeUtil.getSysTime()+ "\n文件【" + recvName + "】接收完毕。\n文件保存在"+ filePath + "\n", tipAttrSet);}} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}// recvProgressBar.setValue(0);// recvProgressBar.setString("");recvFiles.remove(recvName);// 一个文件接收完毕,从表格中删除。table.getModel().setValueAt("", rowIdx, 0);table.getModel().setValueAt("", rowIdx, 1);System.out.println("recv rowIdx:" + rowIdx);if (recvFiles.size() == 0) {if (isSL) {try {slDoc.insertString(slDoc.getLength(),"系统消息:\t" + TimeUtil.getSysTime()+ "\n所有文件都已接收完毕。\n", tipAttrSet);} catch (BadLocationException e) {// TODO Auto-generated catch blocke.printStackTrace();}} else {insertString("系统消息:\t" + TimeUtil.getSysTime()+ "\n所有文件都已接收完毕。\n", tipAttrSet);}}} catch (UnknownHostException e) {try {if (isSL) {slDoc.insertString(slDoc.getLength(), "系统消息:\t"+ TimeUtil.getSysTime() + "\n无法连接文件发送服务器\n",tipAttrSet);} else {insertString("系统消息:\t" + TimeUtil.getSysTime()+ "\n无法连接文件发送服务器\n", tipAttrSet);}} catch (BadLocationException ex) {// TODO Auto-generated catch blocke.printStackTrace();}} catch (IOException e) {try {if (isSL) {slDoc.insertString(slDoc.getLength(),"系统消息:\t" + TimeUtil.getSysTime() + "\n接收文件异常:"+ e.getMessage() + "\n", tipAttrSet);} else {insertString("系统消息:\t" + TimeUtil.getSysTime()+ "\n接收文件异常:" + e.getMessage() + "\n",tipAttrSet);}} catch (BadLocationException ex) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(e.getMessage());} finally {try {if (dis != null)dis.close();if (raf != null)raf.close();if (s != null)s.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}

辅助类:

package com.socket.tcp.basechat;import java.awt.BorderLayout;import java.awt.EventQueue;import java.awt.FlowLayout;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.JOptionPane;import javax.swing.JPanel;import javax.swing.JTextField;import javax.swing.border.EmptyBorder;/** * 登陆窗口。 * @author lucky star * */public class LoginChat extends JDialog {private final JPanel contentPanel = new JPanel();private JTextField userNameField;/** * Launch the application.b */public static void main(String[] args) {try {LoginChat dialog = new LoginChat();dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);dialog.setVisible(true);} catch (Exception e) {e.printStackTrace();}}/** * Create the dialog. */public LoginChat() {setLocationByPlatform(true);setBounds(100, 100, 450, 114);getContentPane().setLayout(new BorderLayout());contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));getContentPane().add(contentPanel, BorderLayout.CENTER);contentPanel.setLayout(null);{JLabel label = new JLabel("\u7528\u6237\u540D\uFF1A");label.setBounds(10, 10, 54, 15);contentPanel.add(label);}userNameField = new JTextField();userNameField.setBounds(75, 7, 349, 21);contentPanel.add(userNameField);userNameField.setColumns(10);{JPanel buttonPane = new JPanel();buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));getContentPane().add(buttonPane, BorderLayout.SOUTH);{JButton okButton = new JButton("OK");okButton.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent actionevent) {String txt = userNameField.getText();if (txt == null || "".equals(txt)) {JOptionPane.showMessageDialog(LoginChat.this, "请输入用户名","提示",JOptionPane.WARNING_MESSAGE);userNameField.requestFocus();return;}EventQueue.invokeLater(new Runnable() {public void run() {try {Main frame = new Main(userNameField.getText());frame.setVisible(true);} catch (Exception e) {e.printStackTrace();}}});LoginChat.this.dispose();}});okButton.setActionCommand("OK");buttonPane.add(okButton);getRootPane().setDefaultButton(okButton);}{JButton cancelButton = new JButton("Cancel");cancelButton.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent actionevent) {LoginChat.this.dispose();}});cancelButton.setActionCommand("Cancel");buttonPane.add(cancelButton);}}}}

package com.socket.tcp.basechat;import java.text.SimpleDateFormat;import java.util.Calendar;public final class TimeUtil {// get the current system date and time.public static String getSysTime() {return new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());}}

服务器端其实很简单:

1.启动服务器,监听某端口

2.阻塞等待客户端连接

3.客户端连接上了,启动一个线程处理客户端信息的转发(转发给所有人,或某个人)。

根据登录名区分各个客户端。——主要功能不在这,只是提供输入用户名并没有做注册和登陆验证。


客户端:

包含一个群聊面板,一个私聊面板,可选择字体,颜色,可发送图片,可选择发送单个文件,一个支持多个文件同时发送和接收的表格。

为每个发送的文件启动一个文件发送线程处理,在线程中先发送信息告诉接收方要接收的文件以及要发送的文件大小(除以100*1024后的结果,用于计算发送和接收文件的进度)。每次发送或接收100KB。在一个文件发送完毕后从表格中清除。

接收方单击接收按钮后提示选择文件保存的位置,然后启动为每个文件启动接收各个文件的接收文件线程。某个文件接收完毕后,从表格中删除。


如转载请注明出处,谢谢。

原创粉丝点击