Java实现UDP通信

JavaSocket实现通信

  • UDP实现可靠传输

要让 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就简单了,只要开启一个收到数据包后响应的线程即可

  • UDP组播通信

组播通信是指一点发送消息,多点可接收消息的模式;在 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类型的对象,也就是说这是一条消息,就把他打印在界面上。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值