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(); } } }
执行截图: