一步一步写简易版飞鸽传书(二)

通信类 ComUtil 的设计

一、前言

在本篇中,我们将介绍通信类的设计。这个类实现了发送、接收字符消息的方法,分别分为私聊消息和公聊消息。公聊就类似于QQ群聊一样,所有登录用户都能看到,而私聊则是点开一个好友聊天窗口,只能聊天双方才能看到。我们暂时把这个类叫做 ComUtil 


二、UDP协议

首先,我们来介绍下UDP协议(这部分内容摘自李刚《疯狂java讲义》第三版P798  17.4基于UDP协议的网络编程)。

 

UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket ,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象。Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据。

 

下面来看看DatagramPacket的构造器:

DatagramPacket(byte[] buf, int length) : 以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据。

DatagramPacket(byte[] buf, int length, InetAddress addr, int port): 以一个包含数据的数组来创建DatagramPacket 对象,并且指定了IP地址和端口—决定了该数据报的目的地。

DatagramPacket(byte[] buf, int offset, int length): 以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中时从offset开始,最多放length个字节。

DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port): 创建一个用于发送的DatagramPacket对象,指定发送buf数组中从offset开始,总共length个字节。

 

从以上的描述中我们可以总结出这么几点:第一,DatagramPacket 用来表示发送、接收的数据。第二,如果我们要发送数据,那么需要指定数据报的地址(IP和端口),那么就应该使用上面的②号和④号构造器。第三,如果我们要接收数据,那么无需指定地址,那么就可以使用上面的①号和③号构造器。

 

另外,我们用 DatagramSocket 的 send(DatagramPacket p) 和 receive(DatagramPacket p) 方法来发送和接收数据报。

 

三、多点广播和MulticastSocket

现在,我们还有一个待解决的问题是:如何实现公聊?可以想象,任何一个用户发送了一条公聊消息后,我们必须要把这条消息发送给所有的用户。通常的思路是,用一个集合来保存所有的登录用户,每当一个用户发送了一条公聊消息,就将该消息发送给这个集合中的所有用户。但是,我们要注意去及时刷新这个用户集合,因为不断会有用户下线。幸好,现在有更好的解决方案。JavaUDP协议提供了 MulticastSocket 类,这个类可以将数据报以广播的方式发送到多个客户端。这就是所谓的多点广播。

 

若要使用多点广播,则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有主机都能收到该数据报。IP多点广播(或多点发送)实现了将单一信息发送到多个接收者的广播,其思想是设置一组特殊网络地址作为多点广播地址,每一个多点广播地址都被看做一个组,当客户端需要发送、接收广播信息时,加入该组即可。IP协议为多点广播提供了这批特殊的IP地址,这些IP地址的范围是 224.0.0.0 至 239.255.255.255 。(《疯狂java讲义》)

MulticastSocket  有三个构造器:

MulticastSocket(): 使用本机默认地址、随机端口来创建

MulticastSocket(int portNumber): 使用本机默认地址、指定端口来创建

MulticastSocket(SocketAddress bindaddr): 使用本机指定IP地址、指定端口来创建

 

MulticastSocket  使用joinGroup(InetAddress multicastAddr) 方法加入指定组,使用leaveGroup(InetAddress multicastAddr) 方法脱离一个组。

 

四、ComUtil设计

ComUtil的源码如下,有几个地方稍微解释下。我们定义了一个multicastSocket, 同时定义了DatagramPacket broadOutPacket 用于发送广播消息,DatagramPacket broaInPacket 用于接收广播消息。在ComUtil的构造器中,实例化broadOutPacket 指定了IP和端口号,而broaInPacket 则没有。这符合我们之前总结的结论。

 

同样的,我们定义了DatagramSocket singleDatagramSocket ,用DatagramPacket singleOutPacket 发送私聊消息,用DatagramPacket singleInPacket 接收私聊消息。如果你细心你就会发现,在ComUtil的构造器中,我们实例化singleOutPacket 时并没有指定IP和端口,可是它是用来发送消息的啊,怎么没指定“目的地”呢。原来,我们在发送私聊消息方法 public void sendMsg(String msg, SocketAddress socketAddress)中,才指定了它的目的地。因为我们实际上是提前在ComUtil的构造器中实例化了singleOutPacket ,而这时,我们并不知道谁会使用这个singleOutPacket 发送消息给谁。

 

另外,我们定义了两个线程分别不断读取公聊消息和私聊消息。Main测试方法说明这个类可以工作。

package com.myipmsg.comutil;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.SocketAddress;
import java.net.UnknownHostException;

/**
 * 通信类
 * @author ThinkPad
 *
 */
public class ComUtil {

	//字符编码
	private static final String CHARSET = "utf-8";
	//多点广播ip
	private static final String BROADCAST_IP = "230.0.0.1";
	//多点广播端口号
	private static final int    BROADCAST_PORT = 30000;
	//一次发送、接收数据包的长度
	private static final int DATA_LEN = 4096;
	
	//多点广播socket
	private MulticastSocket multicastSocket;
	//发送多点广播消息的DatagramPacket
	private DatagramPacket broadOutPacket;	
	//接收多点广播消息的DatagramPacket
	private DatagramPacket broaInPacket;
	
	
	//私聊socket
	private DatagramSocket singleDatagramSocket;
	//发送私聊消息的DatagramPacket
	private DatagramPacket singleOutPacket;
	//接收私聊消息的DatagramPacket
	private DatagramPacket singleInPacket;
	
	//构造器
	public ComUtil(){
		
		try {
			//多点广播InetAddress
			InetAddress broadcastAddress = InetAddress.getByName(BROADCAST_IP);
			// 初始化多点广播multicastSocket
			multicastSocket = new MulticastSocket(BROADCAST_PORT);
			//将multicastSocket加入指定的多点广播地址
			multicastSocket.joinGroup(broadcastAddress);
			//设置本multicastSocket发送的数据包被回送到自身
			multicastSocket.setLoopbackMode(false);
			
			//初始化broadOutPacket
			broadOutPacket = new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT);
			//初始化 broaInPacket
			broaInPacket = new DatagramPacket(new byte[DATA_LEN], DATA_LEN);
			
			
			//初始化私聊socket
			singleDatagramSocket = new DatagramSocket(BROADCAST_PORT+1);  //占用端口是多点广播端口号加1
			//初始化singleOutPacket
			singleOutPacket = new DatagramPacket(new byte[0], 0);
			//初始化singleInPacket
			singleInPacket = new DatagramPacket(new byte[DATA_LEN], DATA_LEN);
			
		
			//开启读取多点广播消息的线程
			new ReadBroard().start();
		    //开启读取私聊消息的线程
			new ReadSingle().start();
			
			
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	
	
	//广播消息
	public void broadMsg(String msg){
		
		byte[] msgByte;
		try {
			msgByte = msg.getBytes(CHARSET);
			broadOutPacket.setData(msg.getBytes(CHARSET));
			multicastSocket.send(broadOutPacket);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
	
	
	//不断读取多点广播消息的线程
	class ReadBroard extends Thread{
		
		public void run(){
		
			while(true){
				
				try {
					multicastSocket.receive(broaInPacket); 
					System.out.println("接收到公聊消息="+ new String(broaInPacket.getData(), 0, broaInPacket.getLength(), CHARSET));
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 发送私聊消息
	 * @param msg
	 */
	public void sendMsg(String msg, SocketAddress socketAddress){
		
		try {
			
			singleOutPacket.setSocketAddress(socketAddress);
			singleOutPacket.setData(msg.getBytes(CHARSET));
			singleDatagramSocket.send(singleOutPacket);
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
	
	//不断读取私聊消息的线程
	class ReadSingle extends Thread{
		
		public void run(){
			while(true){
				
				try {
					singleDatagramSocket.receive(singleInPacket);
					System.out.println("接收到私聊信息="+new String(singleInPacket.getData(), 0, singleInPacket.getLength(), CHARSET));
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		ComUtil comUtil = new ComUtil();
		comUtil.broadMsg("loves");
		comUtil.sendMsg("this is a single msg1", new InetSocketAddress("127.0.0.1", BROADCAST_PORT+1));
		comUtil.sendMsg("this is a single msg2", new InetSocketAddress("127.0.0.1", BROADCAST_PORT+1));
	}

}

所有代码可在此处下载: http://download.csdn.net/detail/zhutulang/9207885


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值