首先来说明一下怎么实现:
大家都知道,Socket是点对点通讯,必需得有一个服务器,一个客户端。比如有A和B两位大神,当A大神向B大神发送消息时,A大神即为客户端,B大神即为服务器。反过来也一样。这就意味着,A大神即充当着客户端的角色,也充当着服务器的角色,只有这样他才能又能收又能发!而因为每个人都是服务器,也是客户端,所以没有必要使用一个专门的服务器处理消息!于是,无服务器的设想就诞生了!
那么,怎么样才能知道A大神向B大神发的是消息还是文件或者其它的什么东西(如视频请求)呢?嗯,想了想,既然HTTP有协议,FTP和蓝牙WIFI都有其协议,我们为什么不自定义一种协议来区别多种请求呢?这个设想肯定是可以的!
如何实现自定义协议?众所周知,HTTP协议是有报头和主体的区别的,受到这个启发,我们也可以定义一个协议,该协议包含报头和主体两部分。即报头用来指明发送的请求类型,而主体才是消息内容!
那么,如何区分报头和主体?我的想法很简单,因为Socket是通过网络流传送消息,那么,我们可以规定,流传递的消息中,前1024个字节为报头,以后的字节为消息主体!OK,万事具备,只欠编码了!
写界面的Swing代码太多,也没什么技术含量,在此不就贴贴出来了,只贴出主体部分代码。
1、消息发送线程,由于使用自已定义的协议,规定前1024个字节为报头,所以在发送时分两段发送:
/**
* <pre>
* Title: GuQiuSendThread.java
* Project: GuQiu
* Type: leoly.threads.GuQiuSendThread
* Author: 255507
* Create: 2011-9-7 下午03:49:38
* Copyright: Copyright (c) 2011
* Company:
* <pre>
*/
package leoly.threads;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.MessageFormat;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import leoly.utils.GuQiuConstants;
import leoly.utils.GuQiuUtils;
/**
* <pre>
* </pre>
* @author 255507
* @version 1.0, 2011-9-7
*/
public class GuQiuSendThread extends Thread
{
private JTextArea chatPanel;
private JLabel fileLabel;
private int msgType = GuQiuConstants.TEXT_MSG;
private String msg;
private String hostName;
@Override
public void run()
{
if (GuQiuUtils.isOneNull(chatPanel))
{
return;
}
switch (msgType)
{
// 文本消息
case GuQiuConstants.TEXT_MSG:
sendMessage();
break;
// 文件
case GuQiuConstants.FILE_MSG:
sendFile();
break;
// 表情
case GuQiuConstants.FACE_MSG:
break;
// 声音
case GuQiuConstants.SOUND_MSG:
break;
default:
break;
}
}
/**
* <pre>
* 发送普通消息
* </pre>
* @param msg
*/
private void sendMessage()
{
if (GuQiuUtils.isOneNull(hostName, msg))
{
return;
}
try
{
Socket socket = new Socket();
InetAddress addr = InetAddress.getByName(hostName);
InetSocketAddress endpoint = new InetSocketAddress(addr.getHostAddress(), GuQiuConstants.GUQIU_PORT);
socket.connect(endpoint, 1000 * 60 * 5);
OutputStream os = socket.getOutputStream();
// 报头,1000为普通消息,前1024个字节为报头信息
String title = MessageFormat.format(GuQiuConstants.TITLE_TEMPLATE, String.valueOf(GuQiuConstants.TEXT_MSG),
GuQiuConstants.EMPTY_STRING, GuQiuConstants.EMPTY_STRING);
byte[] sorceBytes = title.getBytes();
byte[] titleBytes = new byte[1024];
System.arraycopy(sorceBytes, 0, titleBytes, 0, sorceBytes.length);
os.write(titleBytes);
// 发完报头后发消息主体
os.write(msg.getBytes());
socket.shutdownOutput();
socket.close();
}
catch (Exception e)
{
chatPanel.append("消息发送失败,原因:" + e.getMessage() + '\n');
chatPanel.setCaretPosition(chatPanel.getText().length());
}
}
/**
* <pre>
* 发送文件
* </pre>
* @param msg
*/
private void sendFile()
{
try
{
fileLabel.setVisible(true);
Socket socket = new Socket();
InetAddress addr = InetAddress.getByName(hostName);
InetSocketAddress endpoint = new InetSocketAddress(addr.getHostAddress(), GuQiuConstants.GUQIU_PORT);
socket.connect(endpoint, 1000 * 60 * 5);
OutputStream os = socket.getOutputStream();
// 报头,1002为文件消息,前1024个字节为报头信息
File file = new File(msg);
long fileLength = file.length();
String title = MessageFormat.format(GuQiuConstants.TITLE_TEMPLATE, String.valueOf(GuQiuConstants.FILE_MSG),
file.getName(), fileLength);
byte[] sorceBytes = title.getBytes();
byte[] titleBytes = new byte[1024];
System.arraycopy(sorceBytes, 0, titleBytes, 0, sorceBytes.length);
os.write(titleBytes);
// 发完报头后发消息主体
fileLabel.setText(MessageFormat.format(GuQiuConstants.SEND_FILE_MSG, fileLength, 0));
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[2048 * 5];
int readCount = 0;
long tempCount = 0L;
while ((readCount = fis.read(buffer)) != -1)
{
os.write(buffer, 0, readCount);
fileLabel.setText(MessageFormat.format(GuQiuConstants.SEND_FILE_MSG, fileLength,
(tempCount + readCount)));
}
fis.close();
socket.shutdownOutput();
socket.close();
fileLabel.setVisible(false);
chatPanel.append("文件发送完成!\n");
}
catch (Exception e)
{
chatPanel.append("文件发送失败,原因:" + e.getMessage() + '\n');
}
chatPanel.setCaretPosition(chatPanel.getText().length());
}
public JTextArea getChatPanel()
{
return chatPanel;
}
public void setChatPanel(JTextArea chatPanel)
{
this.chatPanel = chatPanel;
}
public int getMsgType()
{
return msgType;
}
public void setMsgType(int msgType)
{
this.msgType = msgType;
}
public String getMsg()
{
return msg;
}
public void setMsg(String msg)
{
this.msg = msg;
}
public String getHostName()
{
return hostName;
}
public void setHostName(String hostName)
{
this.hostName = hostName;
}
public JLabel getFileLabel()
{
return fileLabel;
}
public void setFileLabel(JLabel fileLabel)
{
this.fileLabel = fileLabel;
}
}
/**
* <pre>
* Title: GuQiuServer.java
* Project: GuQiu
* Type: leoly.threads.GuQiuServer
* Author: 255507
* Create: 2011-9-7 上午11:17:09
* Copyright: Copyright (c) 2011
* Company:
* <pre>
*/
package leoly.threads;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import javax.swing.DefaultListModel;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import leoly.beans.ChatParamBean;
import leoly.beans.MessageTitle;
import leoly.frames.MainFrame;
import leoly.utils.ChatFrameFinder;
import leoly.utils.GuQiuConstants;
import leoly.utils.GuQiuUtils;
/**
* <pre>
* </pre>
* @author 255507
* @version 1.0, 2011-9-7
*/
public class GuQiuServer extends Thread
{
private MainFrame parentFrame;
@Override
public void run()
{
System.out.println("Create Server.");
try
{
ServerSocket server = new ServerSocket(GuQiuConstants.GUQIU_PORT);
while (true)
{
Socket socket = server.accept();
InputStream is = socket.getInputStream();
// 读取前1024个字节作为信息报头,可能发送的信息为:文本,文件,文本和表情
byte[] titleBytes = new byte[1024];
is.read(titleBytes, 0, titleBytes.length);
String title = new String(titleBytes);
MessageTitle mt = MessageTitle.analyzeMsg(title);
switch (mt.getMsgType())
{
case GuQiuConstants.VALIDATE_MSG:
processValidate(socket);
break;
case GuQiuConstants.TEXT_MSG:
processText(is, socket);
break;
case GuQiuConstants.FACE_MSG:
break;
case GuQiuConstants.FILE_MSG:
processFile(is, socket, mt);
break;
case GuQiuConstants.SOUND_MSG:
break;
default:
break;
}
socket.close();
}
}
catch (UnknownHostException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* <pre>
* 接收文件
* </pre>
* @param is
* @param socket
* @param msgContent
* @throws Exception
*/
private void processFile(InputStream is, Socket socket, MessageTitle mt) throws Exception
{
String hostName = socket.getInetAddress().getHostName();
int option = JOptionPane.showConfirmDialog(parentFrame, "收到来自:" + hostName + "的文件,需要接收吗?", "提示信息",
JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.NO_OPTION)
{
return;
}
ChatParamBean chatBean = getChatBean(hostName);
JLabel fileLabel = chatBean.getFileLabel();
JTextArea msgPanel = chatBean.getChatPanel();
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int chooseOption = chooser.showSaveDialog(parentFrame);
if (chooseOption == JFileChooser.APPROVE_OPTION)
{
String selectPath = chooser.getSelectedFile().getAbsolutePath();
if (GuQiuUtils.isEmpty(selectPath))
{
return;
}
fileLabel.setVisible(true);
fileLabel.setText(MessageFormat.format(GuQiuConstants.RECIEVE_FILE_MSG, mt.getMsgLength(), 0));
byte[] buffer = new byte[2048 * 5];
FileOutputStream fos = new FileOutputStream(selectPath + "\\" + mt.getMsgContent());
int readCount = 0;
long tempCount = 0L;
while ((readCount = is.read(buffer)) != -1)
{
fos.write(buffer, 0, readCount);
fileLabel.setText(MessageFormat.format(GuQiuConstants.RECIEVE_FILE_MSG, mt.getMsgLength(),
(tempCount + readCount)));
}
fos.close();
socket.shutdownInput();
fileLabel.setVisible(false);
msgPanel.append("文件接收完成!\n");
msgPanel.setCaretPosition(msgPanel.getText().length());
}
}
/**
* <pre>
* 接收消息
* </pre>
* @param is
* @param socket
* @throws Exception
*/
private void processText(InputStream is, Socket socket) throws Exception
{
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String msg = null;
StringBuffer sb = new StringBuffer();
while ((msg = reader.readLine()) != null)
{
sb.append(msg + "\n");
}
String hostName = socket.getInetAddress().getHostName();
String profix = '[' + GuQiuUtils.getFormatDate() + "] " + hostName + " 说:\n";
sb.insert(0, profix);
ChatParamBean chatBean = getChatBean(hostName);
JTextArea msgPanel = chatBean.getChatPanel();
msgPanel.append(sb.toString());
msgPanel.setCaretPosition(msgPanel.getText().length());
socket.shutdownInput();
}
/**
* <pre>
* 处理验证信息
* </pre>
* @param socket
*/
private void processValidate(Socket socket)
{
String hostName = socket.getInetAddress().getHostName();
DefaultListModel listModel = ChatFrameFinder.getListModel();
if (null != listModel && !listModel.contains(hostName))
{
listModel.addElement(hostName);
}
}
/**
* <pre>
* 获取聊天窗口参数
* </pre>
* @param hostName
* @return
*/
private ChatParamBean getChatBean(String hostName)
{
ChatParamBean chatBean = ChatFrameFinder.getChatBean(hostName);
if (GuQiuUtils.isOneNull(chatBean))
{
chatBean = parentFrame.createChatFrame(hostName);
}
return chatBean;
}
public MainFrame getParentFrame()
{
return parentFrame;
}
public void setParentFrame(MainFrame parentFrame)
{
this.parentFrame = parentFrame;
}
}
3、还有一个扩散寻找局域网某个网段内在线用户的类:
/**
* <pre>
* Title: GuQiuScaner.java
* Project: GuQiu
* Type: leoly.threads.GuQiuScaner
* Author: 255507
* Create: 2011-9-7 上午10:58:22
* Copyright: Copyright (c) 2011
* Company:
* <pre>
*/
package leoly.threads;
import javax.swing.DefaultListModel;
import javax.swing.JProgressBar;
import leoly.utils.GuQiuUtils;
/**
* <pre>
* 以本机IP为中心,扩散寻找在线用户
* </pre>
* @author 255507
* @version 1.0, 2011-9-7
*/
public class GuQiuScaner extends Thread
{
private DefaultListModel listModel;
private JProgressBar progress;
@Override
public void run()
{
if (GuQiuUtils.isOneNull(listModel, progress))
{
return;
}
String localIp = GuQiuUtils.getLocalHost();
String ipProfix = localIp.substring(0, localIp.lastIndexOf('.') + 1);
String tempIp = null;
String checkResult = null;
progress.setVisible(true);
for (int i = 25; i <= 255; i++)
{
tempIp = ipProfix + i;
System.out.println(tempIp);
checkResult = GuQiuUtils.checkConnected(tempIp);
if (!GuQiuUtils.isEmpty(checkResult) && !listModel.contains(checkResult))
{
listModel.addElement(checkResult);
}
progress.setValue(i);
}
progress.setVisible(false);
}
public DefaultListModel getListModel()
{
return listModel;
}
public void setListModel(DefaultListModel listModel)
{
this.listModel = listModel;
}
public JProgressBar getProgress()
{
return progress;
}
public void setProgress(JProgressBar progress)
{
this.progress = progress;
}
}
4、静态常量类:
package leoly.utils;
public interface GuQiuConstants
{
// 文本信息
int TEXT_MSG = 1000;
// 表情信息
int FACE_MSG = 1001;
// 文件信息
int FILE_MSG = 1002;
// 声音信息
int SOUND_MSG = 1003;
// 验证消息
int VALIDATE_MSG = 1004;
/**
* 没联网的IP
*/
String NO_LINKED = "127.0.0.1";
/**
* 古秋使用的端口
*/
int GUQIU_PORT = 5959;
/**
* 消息分隔符
*/
String TITLE_SEPORATOR = ";;;";
/**
* 空字符串
*/
String EMPTY_STRING = "";
/**
* 发送文件消息
*/
String SEND_FILE_MSG = "正在发送文件:大小[{0}],已完成[{1}]";
/**
* 接收文件消息
*/
String RECIEVE_FILE_MSG = "正在接收文件:大小[{0}],已完成[{1}]";
/**
* 报头模板
*/
String TITLE_TEMPLATE = "[{0};;;{1};;;{2}]";
}
/**
* <pre>
* Title: StringUtils.java
* Project: GuQiu
* Type: leoly.utils.StringUtils
* Author: 255507
* Create: 2011-9-7 上午09:08:05
* Copyright: Copyright (c) 2011
* Company:
* <pre>
*/
package leoly.utils;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* <pre>
* </pre>
* @author 255507
* @version 1.0, 2011-9-7
*/
public class GuQiuUtils
{
/**
* <pre>
*
* </pre>
* @param testStr
* @return
*/
public static boolean isEmpty(String testStr)
{
return (null == testStr || testStr.length() == 0 || "null".equalsIgnoreCase(testStr));
}
/**
* <pre>
*
* </pre>
* @param testStr
* @return
*/
public static boolean isEmpty(Collection testStr)
{
return (null == testStr || testStr.size() == 0);
}
/**
* <pre>
*
* </pre>
* @param testStr
* @return
*/
public static boolean isEmpty(Map testStr)
{
return (null == testStr || testStr.size() == 0);
}
/**
* 参数列表中是否存在空对象
* @param objects 参数列表
* @return
*/
public static boolean isOneNull(Object... objects)
{
boolean result = false;
if (null == objects)
{
result = true;
}
else
{
String tempStr = null;
for (Object tempObj : objects)
{
if ((tempObj instanceof String))
{
tempStr = (String) tempObj;
if (GuQiuUtils.isEmpty(tempStr))
{
result = true;
break;
}
}
else if (tempObj instanceof Collection)
{
List obj = (List) tempObj;
if (isEmpty(obj))
{
result = true;
break;
}
}
else if (tempObj instanceof Map)
{
Map obj = (Map) tempObj;
if (isEmpty(obj))
{
result = true;
break;
}
}
else if (null == tempObj)
{
result = true;
break;
}
}
}
return result;
}
/**
* <pre>
* 获取本机IP
* </pre>
* @return
*/
public static String getLocalHost()
{
String ipStr = GuQiuConstants.NO_LINKED;
try
{
InetAddress netAddr = InetAddress.getLocalHost();
ipStr = netAddr.getHostAddress();
}
catch (UnknownHostException e)
{
e.printStackTrace();
}
return ipStr;
}
/**
* <pre>
* 检查网络是否联通
* </pre>
* @return
*/
public static String checkConnected(String checkIp)
{
String result = null;
try
{
Socket socket = new Socket();
InetSocketAddress addr = new InetSocketAddress(checkIp, GuQiuConstants.GUQIU_PORT);
socket.connect(addr, 1200);
if (socket.isConnected())
{
String msg = MessageFormat
.format(GuQiuConstants.TITLE_TEMPLATE, String.valueOf(GuQiuConstants.VALIDATE_MSG),
GuQiuConstants.EMPTY_STRING, GuQiuConstants.EMPTY_STRING);
OutputStream os = socket.getOutputStream();
os.write(msg.getBytes());
result = addr.getHostName();
socket.shutdownOutput();
}
socket.close();
}
catch (Exception e)
{
}
return result;
}
/**
* <pre>
* 获取格式化后的当前时间
* </pre>
* @return
*/
public static String getFormatDate()
{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(Calendar.getInstance().getTime());
}
}
终于贴完核心代码了,大家如果需要全部的Eclipse工程源码,请到资源区下载吧: