UDP 简易聊天程序示例

 UDP发送接收数据类:

 

 

/**
 * udp 数据报的发送和接收 
 * @author yanlei
 *
 */
public class UDPPort  implements Runnable{
	private static Logger logger = Logger.getLogger(UDPPort.class);
	/**
	 * 本地端口,未指定,则是随机端口
	 */
	int localPort = 0;

	/**
	 * 接收缓冲区大小
	 */
	int bufferSize= 1024;
	public int getLocalPort() {
		return localPort;
	}

	

	public void setLocalPort(int localPort) {
		this.localPort = localPort;
	}
	
	
	public int getBufferSize() {
		return bufferSize;
	}


	public void setBufferSize(int bufferSize) {
		this.bufferSize = bufferSize;
	}


	ExecutorService executorService = null;
	DatagramChannel datagramChannel = null;
	Selector selector = null;
	ByteBuffer byteBuffer = null;

	public boolean open() throws IOException{
		datagramChannel  = DatagramChannel.open();//打开通道
		if(this.localPort>0){
			datagramChannel.socket().bind(new InetSocketAddress(this.localPort));//绑定本地端口
		}
		byteBuffer = ByteBuffer.allocate(this.bufferSize);
		if(this.updHandler != null){
			datagramChannel.configureBlocking(false);//配置成非阻塞模式
			selector = Selector.open();//打开选择器
			datagramChannel.register(selector, SelectionKey.OP_READ);//注册监听可读取事件
			executorService = Executors.newCachedThreadPool();
			executorService.execute(this);
		}
		return true;
	}
	 UDPHandler updHandler;
	public void registerUdpHanndler(UDPHandler updHandler ){
		this.updHandler = updHandler;
	}
	
 

	Queue<SendData> sendDataQueue  = new ConcurrentLinkedQueue<SendData>();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try{
			while(true){
				
					selector.select();//阻塞直至收到数据包,返回值可能为零,但有事件发生,因此不以返回值判断事件数
					if(this.stop){
						break;
					}
					
					Set<SelectionKey> keys = selector.selectedKeys();//获取发生读取事件的注册键 
					Iterator<SelectionKey> iterator = keys.iterator();
					while(iterator.hasNext()){//遍历
						 SelectionKey key=iterator.next();
						 iterator.remove();//需要手工移除注册键,否则下次selectedKeys里来包括它(虽然该selectionKey对应的通道上没有事件)
						// DatagramChannel dc = (DatagramChannel)key.channel();//获取接收数据通道==datagramChannel
						 if(key.isWritable()){//通道可以写入了
							 if(!sendDataQueue.isEmpty()){//重发队列不为空
								 SendData sendData = null;
								 while((sendData = sendDataQueue.peek()) != null){
									 if(!this.send(sendData.getTarget(), sendData.getByteBuffer())){//重发失败
										 break;
									 }else{
										 sendDataQueue.poll();//重发成功
									 }
								 }
								
							 }else{//重发队列空了,不再监控可写入事件,只监控接收数据事件
								 key.interestOps(SelectionKey.OP_READ);
							 }
							
						 }else if(key.isReadable()){//接收数据事件,有数据被接收了
							 byteBuffer.clear();
							 final SocketAddress from = this.receive(byteBuffer);//接收数据包,返回数据来源
							 if(from != null){
								 byteBuffer.flip();
								 byte [] data = new byte [byteBuffer.remaining()];//实际数据大小的数组
								 byteBuffer.get(data);
								 final ByteBuffer receiveBuffer = ByteBuffer.wrap(data);
								 ReceiveData receiveData = new ReceiveData();
								 receiveData.setFrom(from);
								 receiveData.setByteBuffer(receiveBuffer);
								 ReceiveHandler receiveHandler = new ReceiveHandler(receiveData);//处理类逻辑线程
								 this.executorService.execute(receiveHandler);
							  }
						}
						 
				  }
			  
			
			}
		 }catch(IOException e){
			 	this.ioException = e;
			   logger.error("",e);
			   close();
			   
		 }	
	}
	 IOException ioException= null;
	private boolean isOccurException (){
		return ioException !=null;
	}
	
	
	private List<SendData> getUnSendDataList(){
		List<SendData> sendDataList = new ArrayList<SendData>();
		while(!this.sendDataQueue.isEmpty()){
			sendDataList.add(this.sendDataQueue.poll());
		}
		return sendDataList;
	}
	private volatile boolean  stop;
	private volatile boolean  closed;
		public synchronized void close() {
		if(closed ){
			return ;
		}
		this.stop = true;
		this.selector.wakeup();//不接收数据
		try{
			if(this.isOccurException()){//发生IO异常
				List<ReceiveData> receiveDataList = new ArrayList<ReceiveData>();
				
				List<Runnable> runnableList = executorService.shutdownNow();//停止线程池,获取等待队列中的任务
				if(runnableList != null && runnableList.size()>0){
					for(Runnable runnable:runnableList){
						ReceiveHandler receiveHandler = (ReceiveHandler)runnable;
						ReceiveData receiveData = receiveHandler.getReceiveData();
						receiveDataList.add(receiveData);
					}
				}
				try{
					this.updHandler.onError(ioException, receiveDataList, getUnSendDataList());
				}catch(Exception e){
					logger.error("",e);
				}
				
			}else{//正常被调用关闭
				
				try{
					
					this.updHandler.onClose(getUnSendDataList());//未发送出去的数据包
				}catch(Exception e){
					logger.error("",e);
				}
				
				
			}
			
		}catch(Exception e){
			logger.error("",e);
		}finally{
			try{
				executorService.shutdown();
				while(!executorService.awaitTermination(1, TimeUnit.SECONDS)){}//阻塞直至所有任务都完成,线程退出,关闭连接
				this.selector.close();
				this.datagramChannel.close();
			}catch(Exception e){}
		}
		closed = true;
	}
	private boolean send(SocketAddress target,ByteBuffer src) throws IOException{
		int length = src.remaining();
		int sendNum = this.datagramChannel.send(src, target);
		return sendNum==length;
	}
	public boolean send(SocketAddress target,ByteBuffer ... srcs) {
		try{
			if(closed ){
				return false;
			}
			int length=0; 
			ByteBuffer allByteBuffer = null;
			if(srcs.length>1){
				for(ByteBuffer buffer:srcs){
					length += buffer.remaining();
				}
				allByteBuffer = ByteBuffer.allocate(length);
				for(ByteBuffer buffer:srcs){
					allByteBuffer.put(buffer);
				}
				
				allByteBuffer.flip();
			}else{
				allByteBuffer = srcs[0];
				length = allByteBuffer.remaining();
			}
			
			int sendNum =  this.datagramChannel.send(allByteBuffer, target);
			//返回值 sendNum==length ,说明数据全被发送出去了,返回值sendNum=0,说明底层输出缓冲区中没有足够的空间供数据报,则未发送任何字节数据
			//与socketChannel 不同,socketChannel在底层输出缓冲区中没有足够的空间时,能发送几个字节就发几个字节。返回值为发送字节个数。
			if(sendNum ==0){
				 SendData sendData = new SendData();
				 sendData.setByteBuffer(allByteBuffer);
				 sendData.setTarget(target);
				 this.sendDataQueue.add(sendData);
				 SelectionKey key = datagramChannel.keyFor(this.selector);
				 key.interestOps(SelectionKey.OP_READ |SelectionKey.OP_WRITE);
				 this.selector.wakeup();
			}
			return true;
		}catch(IOException e){
			this.ioException = e;
			logger.error("",e);
			close();
			return false;
		}
	}
	
	public SocketAddress receive(ByteBuffer dest) throws IOException{
		SocketAddress sa = this.datagramChannel.receive(dest);//如果数据包的数据大于缓冲区大小,则大出部分被丢弃,因此需要根据实际情况配置缓冲区大小
		return sa;
	}

	class ReceiveHandler implements Runnable{
		ReceiveData receiveData = null;
		public ReceiveHandler(ReceiveData receiveData){
			this.receiveData = receiveData;
		}
		
		public ReceiveData getReceiveData() {
			return receiveData;
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			updHandler.onReceive(receiveData.getFrom(), receiveData.getByteBuffer(), UDPPort.this);
		}
		
	}
	
}
 

 

接收数据报的接口,需子类实现具体的逻辑

 

/**
 * 
 * 用于处理UDP数据报的接口
 * 由子类实现业务逻辑,并在UDPPort类中注册
 * 当接收到数据报时,就触发onReceive方法 
 * @author yanlei
 *
 */
public interface UDPHandler {
	public void onReceive(SocketAddress from,ByteBuffer receiveBuffer,UDPPort udpPort);
	public void onError(IOException e,List<ReceiveData> receiveDataList,List<SendData> unSendDataList);
	public void onClose(List<SendData> unSendDataList);
}
 

 

 

Server 端:

 

 

/**
 * 聊天室服务端
 * @author yanlei
 *
 */
public class ChatServer extends DefaultUDPHandler{
	private Map <SocketAddress,byte [] > userMap= new ConcurrentHashMap<SocketAddress,byte []>();//地址与用户名对应关系
	public void addUser(SocketAddress sa,byte [] nameByteBuffer){
		userMap.put(sa, nameByteBuffer);
	}
	Charset charset =Charset.forName("GBK");
	public byte [] getNameBytes(SocketAddress sa){
		return userMap.get(sa);
	}
	public byte [] removeUser(SocketAddress sa){
		return userMap.remove(sa);
	}
	
	
	@Override
	public void onReceive(SocketAddress from, ByteBuffer messageByteBuffer,
			UDPPort udpPort) {
		// TODO Auto-generated method stub
		
		byte command = messageByteBuffer.get();
		ByteBuffer messageToAllBuffer = null;
		switch(command){
			case 1: //login
				messageToAllBuffer = welcomeUser(messageByteBuffer,from);
				
				break;
			case 2: //exit;
				messageToAllBuffer = userExit(from);
				break;
			case 3:	//talk;
				messageToAllBuffer = userTalk(messageByteBuffer,from);
				break;
		}
		if(messageToAllBuffer != null){
			talkToAll(messageToAllBuffer,udpPort);//talk to all
		}
	}
	
	byte [] talkCommand = new byte []{3};
	public ByteBuffer welcomeUser(ByteBuffer messageByteBuffer,SocketAddress socketAddress) {
		byte []name = new byte [messageByteBuffer.remaining()];
		messageByteBuffer.get(name);
		addUser(socketAddress,name);
		ByteBuffer loginBytes = unionAllBytes(talkCommand," welcome ".getBytes(),name," to login in".getBytes());
		System.out.println("user <"+new String(name,charset)+"> login");
		return loginBytes;
	}
	
	public ByteBuffer userExit(SocketAddress socketAddress ) {
		byte []name = removeUser(socketAddress);
		ByteBuffer exitBytes = unionAllBytes(talkCommand,name,"to exit!".getBytes());
		System.out.println("user <"+new String(name,charset)+"> to exit");
		return exitBytes;
	}
	public ByteBuffer userTalk(ByteBuffer messageByteBuffer,SocketAddress socketAddress){
		byte []name = getNameBytes(socketAddress);
		ByteBuffer taskToAllBytes = unionAllBytes(talkCommand,name,":".getBytes(),messageByteBuffer);
		System.out.println("user <"+new String(name,charset)+">  talk:"+new String(Arrays.copyOfRange(messageByteBuffer.array(), 1, messageByteBuffer.limit()),charset));
		return taskToAllBytes;
	}
	List<SendData> sendDataList = new ArrayList<SendData>();
	public void talkToAll(ByteBuffer messageByteBuffer ,UDPPort udpPort)  {
		Iterator<SocketAddress> iterator = userMap.keySet().iterator();
		while(iterator.hasNext()){
			SocketAddress sa = iterator.next();
			if(!udpPort.send(sa, messageByteBuffer)){//发生异常
				while(iterator.hasNext()){
					SendData sendData = new SendData();
					sendData.setTarget(sa);
					sendData.setByteBuffer(messageByteBuffer);
					sendDataList.add(sendData);
				}
				break;
			}
			messageByteBuffer.flip();
		}
	}
	private ByteBuffer unionAllBytes(Object  ... objs){
		int length = 0;
		for(Object obj  :objs){
			if(obj.getClass().isArray()){
				byte [] bytes = (byte [])obj;
				length +=bytes.length;
			}else if(ByteBuffer.class.isInstance(obj)){
				ByteBuffer byteBuffer = (ByteBuffer)obj;
				length +=byteBuffer.remaining();
			}
		}
		ByteBuffer byteBuffer = ByteBuffer.allocate(length);
		for(Object obj  :objs){
			if(obj.getClass().isArray()){
				byteBuffer.put((byte [])obj);
			}else if(ByteBuffer.class.isInstance(obj)){
				byteBuffer.put((ByteBuffer)obj);
			}
		}
		byteBuffer.flip();
		return byteBuffer;
	}
	
	

	public static void main(String args []){
		try{
			System.out.print("please input server port:");
			Scanner scanner  = new Scanner(System.in);
			String serverPort = scanner.nextLine();
			ChatServer server = new ChatServer();
			UDPPort udpPort = new UDPPort();
			udpPort.setLocalPort(Integer.parseInt(serverPort));
			udpPort.registerUdpHanndler(server);
			udpPort.open();
			System.out.println("server monitor on port:"+serverPort);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
 

 

聊天室客户端:

 

/**
 * 聊天室客户端
 * @author yanlei
 *
 */
public class ChatClient extends DefaultUDPHandler implements Runnable{
	SocketAddress serverAddress = null;
	PrintWriter pw = null;
	Charset charset =Charset.forName("GBK");
	UDPPort udpPort = null;
	String name=null; 
	public ChatClient (String serverIP,String serverPort,UDPPort udpPort,String name) throws Exception{
		InetAddress serverIpAddress = InetAddress.getByAddress(ipToByteArray(serverIP));
		serverAddress = new InetSocketAddress(serverIpAddress,Integer.parseInt(serverPort));
		pw = new PrintWriter(new OutputStreamWriter(System.out));
		this.udpPort = udpPort;
		this.name = name;
	}
	@Override
	public void onReceive(SocketAddress from, ByteBuffer receiveBuffer,
			UDPPort udpPort) {
		// TODO Auto-generated method stub
		byte command = receiveBuffer.get();
		String message = charset.decode(receiveBuffer).toString();
		printToConsole(message);
		
	}
	public void printToConsole(String str){
		if(pw != null){
			pw.println();
			pw.println("==> "+str);
			pw.print("<input>:");
			pw.flush();
		}
	}
	private ByteBuffer loginCommand = ByteBuffer.wrap(new byte []{1});
	private ByteBuffer exitCommand = ByteBuffer.wrap(new byte []{2});
	private ByteBuffer talkCommand = ByteBuffer.wrap(new byte []{3});

	private ByteBuffer getLoginCommand(){
		loginCommand.clear();
		return loginCommand;
	}
	
	private ByteBuffer getExitCommand(){
		exitCommand.clear();
		return exitCommand;
	}
	private ByteBuffer getTalkCommand(){
		talkCommand.clear();
		return talkCommand;
	} 

	
	public boolean toTalk(String message)  {
		return udpPort.send(serverAddress,getTalkCommand(),ByteBuffer.wrap(message.getBytes()));//login
	}
	public void login(){
		udpPort.send(serverAddress,getLoginCommand(),ByteBuffer.wrap(this.name.getBytes()));//login
	}
	public void toExit() throws IOException{
		udpPort.send(serverAddress,getExitCommand());
	}
	volatile boolean  exceptionFlag = false;
	public void setOccurException() {
		exceptionFlag = true;
	}
	public boolean isOccurException(){
		return exceptionFlag;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try{
			
			login();
			Scanner scanner  = new Scanner(System.in);
			System.out.println();
			System.out.print("input:");
			while(true){
				
				String line = scanner.nextLine();
				if(isOccurException()){//异常退出
					break;
				}
				if(line != null){
					if(line.trim().equals("exit")){
						toExit();
						break;
					}else{
						if(!toTalk(line)){
							System.out.println(" occur IOException to exit input thread");
							break;
						}
					}
				}
			}
		}catch(IOException e){
			e.printStackTrace();
			
		}finally{
			udpPort.close();
		}
	}
	
	
	@Override
	public void onError(IOException e, List<ReceiveData> receiveDataList,
			List<SendData> unSendDataList) {
		// TODO Auto-generated method stub
		this.setOccurException();
	}
	
	
	private static byte [] ipToByteArray(String ip){
		String [] temp = ip.split("\\.");
		byte [] ips = new byte [4];
		for(int i=0;i<ips.length;i++){
			ips[i]= (byte)Integer.parseInt(temp[i]);
		}
		return ips;
	}
	
	
	public static void main(String [] args){
		try{
			System.out.print("please input server ip:");
			Scanner scanner  = new Scanner(System.in);
			String serverIp = scanner.nextLine();
			System.out.print("please input server port:");
			String serverPort = scanner.nextLine();
			System.out.print("please input your name:");
			String name = scanner.nextLine();
			
			
			
			 
			UDPPort udpPort =new UDPPort();
			ChatClient chatClient = new ChatClient(serverIp,serverPort,udpPort,name);
			udpPort.registerUdpHanndler(chatClient);
			udpPort.open();
			
			ExecutorService executorService =Executors.newFixedThreadPool(1);
			executorService.execute(chatClient);
			executorService.shutdown();
			while(!executorService.awaitTermination(2, TimeUnit.SECONDS)){}//阻塞直至所有任务都完成,线程退出,关闭连接
				
			 
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

 

执行截图:

 


 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值