JavaSocket实现通信
要让 UDP 数可靠的传送数据,最直接的办法是模拟 tcp 协议的实现,对 UDP 进行
简单的差错重传控制。 我们设定基本的控制规则如下:
1.c 向 s 发送一个数据包,如果 s 收到,必须在 3 秒之内回送一个应答包;发送的数据包必须有一
个唯一的序列号,s 回应时的数据包带上这个序列号,当 c 收到应答时,根据序列号匹配,即
可确认某个消息 s 己收到。
2.如果 c 己发出的消息 3 秒内还未收到应答,即认为超时,c 应重发这条消息,最多重发 3 次,如
还未收到应答,则丢弃这条消息,并向上层应用报告丢失的消息。
3.c 未收到应答有二种情况,第一种是消息包并未发送到 s,所以 s 不发送应答包;第二各情况就麻 烦了,是 s 发的应答包也丢了!随后,如果 c 再重发,就导致 s 重复接收数据。所以最好 s 端也
必须设置超时机制。
4.如果 s 端收到重复的消息(根据消息序列号)即丢弃这条消息,并再次回送应答包,最多重复三次;
如果应答包一直未被 c 收到,结果就是 c 会认为 s 未收到消息;而 s 确实己收到 —这是我们现
在无法解决的问题。
根据以上规则,c 和 s 端必须维持一个消息队列,并启动另外的线程执行丢包重传和回复应答的
动作。以下,我们通过简单代码模拟这个过程:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.SocketAddress;
public class NetJavaMsg {
// 消息负载数据
private int totalLen; // 数据总长度
private int id;// 唯一ID
private byte[] data;// 数据内容
// 本地参数,为简化起见,不发送
private SocketAddress recvRespAdd;// 发送者接收应答地址
private SocketAddress destAdd;// 接收者地址
private int sendCount = 0;// 己发送次数
private long lastSendTime;// 最后一次发送时间
/**
* 创建数据包时的构造器
*
* @param id:唯一序号
* @param data:数据字节内容
* @param timeStamp:创建时间
*/
public NetJavaMsg(int id, byte[] data) {
this.id = id;
this.data = data;
totalLen = 4 + 4 + data.length;// 计算消息字节总长
}
public NetJavaMsg(int id, byte[] data, SocketAddress recvRespAdd, SocketAddress destAdd) {
this.id = id;
this.data = data;
this.recvRespAdd = recvRespAdd;
this.destAdd = destAdd;
totalLen = 4 + 4 + data.length;// 计算消息字节总长
}
/**
* 将接收到的udp数据解析为NetJavaMsg对象的属性
*
* @param udpData:从udp包上收到的数据
*/
public NetJavaMsg(byte[] udpData) {
try {
java.io.ByteArrayInputStream bins = new ByteArrayInputStream(udpData);
java.io.DataInputStream dins = new DataInputStream(bins);
this.totalLen = dins.readInt();
this.id = dins.readInt();
this.data = new byte[totalLen - 4 - 4];
dins.readFully(this.data);
} catch (Exception ef) {
ef.printStackTrace();
}
}
/**
* 将消息包转为字节数组
*
* @return:要发送时的字节数组
*/
public byte[] toBytes() {
try {
java.io.ByteArrayOutputStream bous = new ByteArrayOutputStream();
java.io.DataOutputStream dous = new DataOutputStream(bous);
dous.writeInt(totalLen); // 4个总长
dous.writeInt(id); // 4个序号
// dous.writeChars(recvRespAdd.toString()); // 16
// dous.writeChars(destAdd.toString()); // 16
dous.writeInt(100); // 16
dous.write(data);// 负载的数据
dous.flush();
return bous.toByteArray();// 转为字节数组返回
} catch (Exception ef) {
ef.printStackTrace();
}
return null;
}
public String toString() {
return "id:" + id + " content:" + new String(data) + " totalLen:" + totalLen + " senderAdd:" + recvRespAdd
+ " destAdd:" + destAdd;
}
public int getTotalLen() {
return totalLen;
}
public void setTotalLen(int totalLen) {
this.totalLen = totalLen;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public SocketAddress getRecvRespAdd() {
return recvRespAdd;
}
public void setRecvRespAdd(SocketAddress recvRespAdd) {
this.recvRespAdd = recvRespAdd;
}
public SocketAddress getDestAdd() {
return destAdd;
}
public void setDestAdd(SocketAddress destAdd) {
this.destAdd = destAdd;
}
public int getSendCount() {
return sendCount;
}
public void setSendCount(int sendCount) {
this.sendCount = sendCount;
}
public long getLastSendTime() {
return lastSendTime;
}
public void setLastSendTime(long lastSendTime) {
this.lastSendTime = lastSendTime;
}
}
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class NetJavaRespMsg {
//消息负载数据
private int totalLen; // 数据总长度
private int respId;// 回复对应接收到消息的ID
private byte state = 0;// 状态 0:正确接收 其它:错误代码
private long resTime;// 应答方的发送时间
/**
* 创建数据包时的构造器
*
* @param respId:回复的消息的唯一序号
* @param state:回复状态标志 * @param resTime:回复方发送的时间
*/
public NetJavaRespMsg(int respId, byte state, long resTime) {
this.respId = respId;
this.state = state;
this.resTime = resTime;
totalLen = 4 + 4 + 1 + 8;// 计算消息字节总长
}
/**
* 将接收到的udp数据解析为对象的属性
*
* @param udpData:从udp包上收到的数据
*/
public NetJavaRespMsg(byte[] udpData) {
try {
java.io.ByteArrayInputStream bins = new ByteArrayInputStream(udpData);
java.io.DataInputStream dins = new DataInputStream(bins);
this.totalLen = dins.readInt();
this.respId = dins.readInt();
this.state = dins.readByte();
this.resTime = dins.readLong();
} catch (Exception ef) {
ef.printStackTrace();
}
}
/**
* 将消息包转为字节数组
*
* @return:要发送时的字节数组
*/
public byte[] toBytes() {
try {
java.io.ByteArrayOutputStream bous = new ByteArrayOutputStream();
java.io.DataOutputStream dous = new DataOutputStream(bous);
dous.writeInt(this.totalLen); // 4个总长
dous.writeInt(this.respId);
dous.writeByte(this.state);
dous.writeLong(this.resTime);
dous.flush();
return bous.toByteArray();// 转为字节数组返回
} catch (Exception ef) {
ef.printStackTrace();
}
return null;
}
public String toString() {
return "totalLen: " + totalLen + " respId:" + respId + " state:" + state + " resTime:" + resTime;
}
public int getTotalLen() {
return totalLen;
}
public void setTotalLen(int totalLen) {
this.totalLen = totalLen;
}
public int getRespId() {
return respId;
}
public void setRespId(int respId) {
this.respId = respId;
}
public byte getState() {
return state;
}
public void setState(byte state) {
this.state = state;
}
public long getResTime() {
return resTime;
}
public void setResTime(long resTime) {
this.resTime = resTime;
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class DatagramSender {
private SocketAddress localAddr;// 创建要用来发送的本地地址对象
private DatagramSocket dSender;// 发送的DatagramSocket对象
private SocketAddress destAddr;// 发送的目标地址对象
//本地缓存己发送的消息队列key为消息ID,value为消息对象
private Map<Integer, NetJavaMsg> msgQueue = new HashMap();
//构造器,创建时初始化
public DatagramSender() throws Exception {
localAddr = new InetSocketAddress("127.0.0.1", 13000);
dSender = new DatagramSocket(localAddr);
destAddr = new InetSocketAddress("127.0.0.1", 14000);
System.out.println(destAddr.toString().length());
startSendThread();// 启动发送线程
startRecvResponseThread();// 启动接收应答线程
startResendThread();// 启动重发消息的线程
}
// 启动发送线程
public void startSendThread() {
new Thread(new Runnable() {
public void run() {
try {
send();
} catch (Exception ef) {
ef.printStackTrace();
}
}
}).start();
}
//启动接收应答线程
public void startRecvResponseThread() {
new Thread(new Runnable() {
public void run() {
try {
recvResponse();
} catch (Exception ef) {
ef.printStackTrace();
}
}
}).start();
}
//启动重发消息的线程
public void startResendThread() {
new Thread(new Runnable() {
public void run() {
try {
System.out.println("客户机-丢失重发线程己启动. . .");
while (true) {
resendMsg();
Thread.sleep(1000);
}
} catch (Exception ef) {
ef.printStackTrace();
}
}
}).start();
}
//模拟发送消息
private void send() throws Exception {
System.out.println("客户机-发送数据线程己启动. . .");
int id = 0;
while (true) {
id++;
// 创建要发送数据转为字节数组
byte[] msgData = (id + "-hello").getBytes();
// 创建要发送的消息对象
NetJavaMsg sendMsg = new NetJavaMsg(id, msgData);
// 要送送的数据:将要发送的数据转为字节组
byte buffer[] = sendMsg.toBytes();
// 创建要发送的数据包,指定内容,指定目标地址
DatagramPacket dp = new DatagramPacket(buffer, buffer.length, destAddr);
dSender.send(dp);// 发送
// 保存发送地址和目标地址
sendMsg.setRecvRespAdd(localAddr);// 本地接收应答的地址
sendMsg.setDestAdd(destAddr);// 这个数据包被发向的目标地址
sendMsg.setSendCount(1);// 发送次数和最后发送时间
sendMsg.setLastSendTime(System.currentTimeMillis());
msgQueue.put(id, sendMsg);// 保存到队列中,等回应
System.out.println("客户机-数据己发送: " + sendMsg);
Thread.sleep(1000);
}
}
// 接收应答消息
public void recvResponse() throws Exception {
System.out.println("客户机-接收应答线程己启动. . .");
while (true) {
byte[] recvData = new byte[100];
// 创建接收数据包对象
DatagramPacket recvPacket = new DatagramPacket(recvData, recvData.length);
dSender.receive(recvPacket);
// 解包应答消息对象
NetJavaRespMsg resp = new NetJavaRespMsg(recvPacket.getData());
System.out.println(" 客户机-接收到应答数据: " + resp);
// 匹配等待队列中的消息
int respID = resp.getRespId();
NetJavaMsg msg = msgQueue.get(new Integer(respID));
if (msg != null) {
System.out.println(" 客户机-己确认收到: " + msg);
msgQueue.remove(respID);// 从队列中移除
}
}
}
// 判断队列中的消息,如果超过3秒未收到应答,则重发!
public void resendMsg() {
Set<Integer> keySet = msgQueue.keySet();
Iterator<Integer> it = keySet.iterator();
while (it.hasNext()) {
Integer key = it.next();
NetJavaMsg msg = msgQueue.get(key);
if (msg.getSendCount() >= 4) {// 发了四次,还未收到应答
it.remove();
System.out.println(" *** 客户机--检测到丢失的消息:" + msg);
}
long cTime = System.currentTimeMillis();
// 己三秒未收到,重发!
if ((cTime - msg.getLastSendTime()) > 3000 && msg.getSendCount() < 4) {
byte buffer[] = msg.toBytes();
try {
// 创建要发送的数据包,指定内容,指定目标地址
DatagramPacket dp = new DatagramPacket(buffer, buffer.length, msg.getDestAdd());
dSender.send(dp);// 发送
msg.setSendCount(msg.getSendCount() + 1);// 发送次数自增
System.out.println("客户机--重发消息:" + msg);
} catch (Exception ef) {
ef.printStackTrace();
}
}
}
}
// 主函数
public static void main(String args[]) throws Exception {
new DatagramSender();
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class DatagramReceiver {
private SocketAddress localAddr;// 创建要用来发送的本地地址对象
private DatagramSocket dSender;// 创建发送的Socket对象
// 构造器,创建时初始化
public DatagramReceiver() throws Exception {
localAddr = new InetSocketAddress("127.0.0.1", 14000);
dSender = new DatagramSocket(localAddr);
startRecvThread();
}
// 启动接收应答线程
public void startRecvThread() {
new Thread(new Runnable() {
public void run() {
try {
recvMsg();
} catch (Exception ef) {
ef.printStackTrace();
}
}
}).start();
}
// 接收发来的消息
public void recvMsg() throws Exception {
System.out.println("服务器-接收数据线程己启动. . .");
while (true) {
byte[] recvData = new byte[100];
// 创建接收数据包对象
DatagramPacket recvPacket = new DatagramPacket(recvData, recvData.length);
dSender.receive(recvPacket);
// 根据接收到的数据创建消息对象,
NetJavaMsg recvMsg = new NetJavaMsg(recvPacket.getData());
System.out.println("服务器-接收到数据包: " + recvMsg);
System.out.println("senderAdd: " + recvPacket.getSocketAddress() + " destAdd:" + localAddr);
// 马上回复应答:
NetJavaRespMsg resp = new NetJavaRespMsg(recvMsg.getId(), (byte) 0, System.currentTimeMillis());
// 创建要发送的数据包,指定内容,指定目标地址
byte[] data = resp.toBytes();
DatagramPacket dp = new DatagramPacket(data, data.length, recvPacket.getSocketAddress());
dSender.send(dp);// 发送
System.out.println("服务器-己发送应答: " + resp);
}
}
// 主函数
public static void main(String args[]) throws Exception {
new DatagramReceiver();
}
}
简单分析一下代码,NetJavaMsg和NetJavaRespMsg这两个类分别用来保存发送的数据包的信息和接收的数据包的信息,它们的id其实就是seq和ack,用来确定是哪一个数据包,其中还有一些读写的处理这里就不加赘述了。主要讲一下DatagramSender和DatagramReceiver这两个类,DatagramSender开启了三个线程,分别用于发送、接收、重发数据包,同时创建了一个HashMap用于充当消息列表,保存发送了但尚未收到响应的数据包。当发送一个数据包时,这个数据包会被保存进入这个消息队列中,如果这个数据包在3s内收到了响应(前面说的,通过id来识别时哪个数据包),则将它从消息队列中移除(有点像android的anr,当我做了一个可能会引起anr的操作,就调用Handler.postDelayed方法,在一定时间后发送一个anr的消息,如果在规段时间内操作完成了,则移除这个消息,否则这个消息就会被处理,系统就会收到anr提示)否则我们认为s没有收到数据包,就重新发送,并将发送次数+1,如果发送次数超过四次,就将它移除,即放弃了对这个数据包的发送。DatagramReceiver就简单了,只要开启一个收到数据包后响应的线程即可
组播通信是指一点发送消息,多点可接收消息的模式;在 tcp/ip 协议中规定了组播消息可以使用的 IP 地址,即 224.0.0.0~239.255.255.255 的地址,称做 D 类地址;要参与组播通信的主机,都必须加入到同一个 D 类地址,或者说同一个组中,而且必须是在同一端口上收发消息。在一个局网内部,某台机器上的程序如果将消息通过组播发送,这条消息就会被发送给局网内的所有其它机器,如果某个机器上恰好有“等待在这个组上接收消息”的服务运行,它就会收到到这条消息。组播消息的发送,发送者只发送一次,广播式发送到其它机器的过程,则是由路由器完成的;如下图所示:
而如果是点对点模式的通信,发送消息的程序就需要将这条消息给其它的每个机器发送一次,
如下图示:
由这两张图的对比可以看出:组播通信比点对点具有更灵活的收发机制,加入组播的程序只
要发送一次,组内其它机器都可收到这条消息;接收者可以选择是否加入某组发送消息或广播消
息。要实现组播 UDP 通信,与以前的 UDP 通信不一样的是,要使用 java.net. MulticastSocket 对象
收发消息,发送消息的地址必须是一个组播 IP 地址;接收的 Socket 对象也必须是绑定在一个组播
IP 地址上,需要参与某一组通信的程序,只需要绑定到 224.0.0.0~239.255.255.255 间的一个 IP 地
址上收发数据即可,这个 IP 地址在你的机器上并不真实存在。
需要注意的时,创建MulticastSocket对象后,还需要将该MulticastSocket通过加入到指定的多点广播地址,MulticastSocket使用joinGroup(InetAddress multicastAddr)方法加入指定组;使用leaveGroup(InetAddress multicastAddr)方法脱离一个组。
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class MulticastChat extends Thread {
private static int portTem = 9999;
private static String mutiAddr = "230.0.0.1";// 组播IP地址
private InetAddress inetAddress;// 组播消息的目标地址
private MulticastSocket multicastSocket;// 本机组播发送端
// 显示接收到消息的组件
private JTextArea jta_recive = new JTextArea(10, 25);
public MulticastChat() {
try {
inetAddress = InetAddress.getByName(mutiAddr);
multicastSocket = new MulticastSocket();
setUpUI();
} catch (Exception ef) {
ef.printStackTrace();
}
}
// 启动聊天界面
public void setUpUI() {
JFrame jf = new JFrame();
jf.setTitle("组播聊天示例");
java.awt.FlowLayout fl = new java.awt.FlowLayout(0);
jf.setLayout(fl);
jf.setSize(300, 400);
JLabel la_name = new JLabel("接收到的消息:");
JLabel la_users = new JLabel("你的名字:");
final JTextField jtf_name = new JTextField(5);// 用户名输入框
final JTextField jtf_send = new JTextField(20);// 发送输入框
javax.swing.JButton bu_send = new javax.swing.JButton("Send");
jf.add(la_name);
jf.add(jta_recive);
jf.add(la_users);
jf.add(jtf_name);
jf.add(jtf_send);
jf.add(bu_send);
//发送事件监听器
ActionListener sendListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
String name = jtf_name.getText();
String msg = jtf_send.getText();
msg = name + "说:" + msg;
sendMsg(msg);// 发送一条组播消息
jtf_send.setText("");
}
};
bu_send.addActionListener(sendListener);
jtf_send.addActionListener(sendListener);
jf.setVisible(true);
jf.setDefaultCloseOperation(3);
}
// 发送一条消息到组中
public void sendMsg(String msg) {
try {
byte[] data = (msg).getBytes();
DatagramPacket datagramPacket = new DatagramPacket(data, data.length, inetAddress, portTem);
multicastSocket.send(datagramPacket);
} catch (Exception ef) {
ef.printStackTrace();
}
}
// 接收组播消息线程
public void run() {
try {// 在同一端口上创建接收组播Socket对象
MulticastSocket recvSocket = new MulticastSocket(portTem);
recvSocket.joinGroup(inetAddress);// 加入到组中,否则收不到消息
while (true) {
byte[] data = new byte[100];
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
recvSocket.receive(datagramPacket);
String input = new String(data).trim();
jta_recive.append(input + "\r\n");
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
// 主函数
public static void main(String[] arstring) {
MulticastChat mc = new MulticastChat();
mc.start();
}
}
这里的代码比较简单就不讲了
- UDP实现P2P通信
如下网络结构:
A1 和 B1 要实现相互间的直接通信,必须经过以下步骤:
1.A1 向公网服务器C发送数据包,C 记录网关 A2 上的映射地址;
2.B1 向公网服务器C发送数据包,C 记录网关 B2 上的映射地址;
3.C 将 A2 或 B2 的地址发送给 B1 或 A1;
4.A1 或 B1 再发送数据包时,数据包的目标地址写为收到的对方的映射地址。即将数据包发给
B2 或 A1 上的地址。网关即可将收到的数据包转发给其映射的内网的主机。到了这一步,就没有C
什么事了;A1 和 B1 只要记着对方的映射地址,即可点对点的将数据发送给对端主机,即实现了
P2P 通信,如下图示:
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;
public class DatagramRouteServer {
//存放所有客户机地址的队列
private Set<InetSocketAddress> clientAddSet = new HashSet();
// 启动udp服务器,接收消息,转发消息
public void startServer() throws Exception {
//启动接收的UDP端口服务器,
InetSocketAddress localAddr = new InetSocketAddress("127.0.0.1", 13000);
DatagramSocket socket = new DatagramSocket(localAddr);
System.out.println("UDP服务器等待接收数据:" + socket.getLocalSocketAddress());
while (true) {
// 指定接收缓冲区大小
byte[] buffer = new byte[256];
// 创建接收数据包对象
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 阻塞等待数据到来,如果收到数据,存入packet中的缓冲区中
socket.receive(packet);
// 得到发送方的ip和端口
InetAddress clientAdd = packet.getAddress();
int clientPort = packet.getPort();
InetSocketAddress address = new InetSocketAddress(clientAdd, clientPort);
// 将这个地址加入到队列中,用的是set,会自动排重
clientAddSet.add(address);
byte[] recvData = packet.getData();// 提取接收到的数据
// 发送的是字符串,收到后,去掉空格
String s = new String(recvData).trim();
// 接收到后,打印出收到的数据长度
System.out.println("服务器收到数据:" + s + " from:" + address);
for (InetSocketAddress dclien : clientAddSet) {
String temf = address + ",到服务器取地址了";
// 转发服务器端的地址列表数据
ByteArrayOutputStream bous = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bous);
oos.writeObject(temf);
oos.flush();
byte[] data = bous.toByteArray();
DatagramPacket mp = new DatagramPacket(data, data.length);
mp.setSocketAddress(dclien);// 发给目标客户机的地址
socket.send(mp);
}
// 转发服务器端的地址列表数据
ByteArrayOutputStream bous = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bous);
oos.writeObject(clientAddSet);
oos.flush();
byte[] data = bous.toByteArray();
// 发送服务器端保存的各个客户机的地址信息
DatagramPacket sendP = new DatagramPacket(data, data.length);
sendP.setSocketAddress(address);// 发给目标客户机的地址
socket.send(sendP);
}
}
// 启动主函数
public static void main(String args[]) throws Exception {
DatagramRouteServer reciver = new DatagramRouteServer();
reciver.startServer();
}
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class DatagramINetClient extends Thread {
// 公网服务器地址
private SocketAddress destAdd = new InetSocketAddress("127.0.0.1", 13000);
private DatagramSocket sendSocket;// 发送Socket对象
// 显示接收到消息的组件
private JTextArea jta_recive = new JTextArea(10, 25);
private JComboBox jcb_addList = new JComboBox();// 其它客户机的地址显示
public DatagramINetClient() {
try {
sendSocket = new DatagramSocket();
} catch (Exception ef) {
ef.printStackTrace();
}
}
public void run() {
try {
while (true) {
byte[] recvData = new byte[1024];
// 创建接收数据包对象
DatagramPacket recvPacket = new DatagramPacket(recvData, recvData.length);
System.out.println("等待接收数据到来... ");
sendSocket.receive(recvPacket);
byte[] data = recvPacket.getData();
System.out.println("收到数据: " + new String(data).trim());
// 读取信息
ByteArrayInputStream bins = new ByteArrayInputStream(data);
ObjectInputStream oins = new ObjectInputStream(bins);
Object dataO = oins.readObject();
if (dataO instanceof Set) {// 服务器端的地址列表
Set<InetSocketAddress> othersAdds = (Set) dataO;
jcb_addList.removeAllItems();
// 将收到的地址列表加入到界面下拉框中
for (InetSocketAddress it : othersAdds) {
jcb_addList.addItem(it);
}
} else if (dataO instanceof String) {
String s = (String) dataO;
// 显示到界面上
jta_recive.append(s + "\r\n");
} else {
String s = "unknown msg:" + dataO;
jta_recive.append(s + "\r\n");
}
}
} catch (Exception ef) {
ef.printStackTrace();
}
}
// 给另外一个客户机发送一条p2p消息
public void sendP2PMsg(String msg, InetSocketAddress dest) {
try {
ByteArrayOutputStream bous = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bous);
oos.writeObject(msg);
oos.flush();
byte[] data = bous.toByteArray();
DatagramPacket dp = new DatagramPacket(data, data.length, dest);
sendSocket.send(dp);
System.out.println("己发送条点对点消息to: " + dest);
} catch (Exception ef) {
ef.printStackTrace();
}
}
// 给服务器发送一条获取地址的消息
public void sendRequestMsg(String msg) {
try {
byte buffer[] = msg.getBytes();
DatagramPacket dp = new DatagramPacket(buffer, buffer.length, destAdd);
sendSocket.send(dp);
System.out.println("己发送给服务器:" + msg);
} catch (Exception ef) {
ef.printStackTrace();
}
}
// 显示主界面
public void setUpUI() {
JFrame jf = new JFrame();
jf.setTitle("蓝杰p2p测试-客户端");
java.awt.FlowLayout fl = new java.awt.FlowLayout(0);
jf.setLayout(fl);
jf.setSize(300, 350);
JButton jb_get = new JButton("获取其它客户机地址");
jf.add(jb_get);
jf.add(jcb_addList);
// 发送请求服务器端的其它客户机列表消息
jb_get.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
sendRequestMsg("取得地址");// 发送一条消息
}
});
// 用户名密码的标签
JLabel la_name = new JLabel("接收到的消息:");
JLabel la_users = new JLabel("发送给:");
final JTextField jtf_sned = new JTextField(20);// 发送输入框
javax.swing.JButton bu_send = new javax.swing.JButton("Send");
jf.add(la_name);
jf.add(jta_recive);
jf.add(la_users);
jf.add(jtf_sned);
jf.add(bu_send);
// 发送事件监听器
ActionListener sendListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
String msg = jtf_sned.getText();
// 得到选中的目标地址
InetSocketAddress dest = (InetSocketAddress) jcb_addList.getSelectedItem();
sendP2PMsg(msg, dest);// 发送一条点对点消息
jtf_sned.setText("");
}
};
bu_send.addActionListener(sendListener);
jtf_sned.addActionListener(sendListener);
jf.setVisible(true);
jf.setDefaultCloseOperation(3);
}
// 主函数
public static void main(String args[]) throws Exception {
DatagramINetClient sender = new DatagramINetClient();
sender.start();
sender.setUpUI();
}
}
大致思路是这样的:客户端每次启动后需要获取地址,点击按钮后就会向服务端发送一个获取地址的信号,服务端收到后就将客户端的地址加入到在Set中(而且Set本身就能去重),随后将该客户端取到地址的消息和新的Set发送给所有的在线客户端,客户端判断收到的是Set后就可以更新自己的在线客户端地址列表,接下来就可以选择列表中的客户端进行点对点通信,选择一个地址,发送消息,地址对应的客户端判断收到的是一个String类型的对象,也就是说这是一条消息,就把他打印在界面上。