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