14My2.0版本聊天系统(服务器selector移植)

package nioserver;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import db.DbConnector;

/**
 * 
 * 高并发的基础模型 =》I/O复用+多线程
 * 
 * 线程数量过多的缺点:
 * 1.线程的创建和销毁都是一些重量级的系统函数,调用开销大,影响系统性能.
 * 2.线程占用内存大,JVM一个线程栈512K-1M的空间,线程数量过多,JVM内存消耗过大
 * 3.线程的上下文切换时间大于线程本身执行的时间,系统负载过高
 * 4.容易造成锯齿状系统负载,服务器工作线程可能被客户端大量的并发请求同时唤醒,
 * 	 这一瞬间造成系统负载特别高,几近崩溃
 * 
 * */

class WorkTask implements Runnable{
	
	private Selector selector;
	private List<SocketChannel> list;
	
	public WorkTask() throws IOException{
		selector = Selector.open();
		list = Collections.synchronizedList(new ArrayList<SocketChannel>());
	}
	
	public List<SocketChannel> getList(){
		return list;
	}
	
	public Selector getSelector(){
		return selector;
	}
	
	public String getCurTime(){
		Date date = new Date(System.currentTimeMillis());
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(date);
	}
	
	public void login(SocketChannel cChannel, JsonObject jobject){
		//访问数据库,鉴定 name password
		String name = jobject.get("name").getAsString();
		String pwd = jobject.get("pwd").getAsString();
		boolean bloginstate = false;
		
		DbConnector db = DbConnector.getInstance();
		ResultSet rset = db.select("select * from chatuser");
		
		try {
			while (rset.next()){
				String curname = rset.getString("username");
				if (curname.compareTo(name) == 0){
					if (rset.getString("password").compareTo(pwd) == 0){
						//登陆成功
						NioServer.map.put(name, cChannel);
						bloginstate = true;
						break;
					}
				}
			}
			rset.close();
			
			//给客户端回复
			JsonObject json = new JsonObject();
			json.addProperty("msgtype", 20);  //20表示服务器的响应登陆消息
			if (bloginstate){
				json.addProperty("ack", "loginok");
			}else{
				json.addProperty("ack", "loginfail");
				json.addProperty("reason", "username or password is wrong!!!");
			}
			cChannel.write(ByteBuffer.wrap((json.toString()+"\n").getBytes()));
		
			if (!bloginstate){  //登陆失败,直接返回
				return;
			}
		
			ResultSet offmsg = db.checkOffMsg(name);//检查是否存在离线消息

			while (offmsg.next()){
				JsonObject offjson = new JsonObject();
				offjson.addProperty("msgtype", 16);  //16表示离线消息
				String userfrom = offmsg.getString("userfrom");
				String sendtime = offmsg.getString("sendtime");
				String message = offmsg.getString("message");
				
				if (userfrom.compareTo("SuperUser") == 0){
					offjson.addProperty("ack", "servermsg");  //系统管理员发送的离线消息
				}
				else{
					offjson.addProperty("ack", "usermsg");  //普通用户发送的离线消息
					offjson.addProperty("userfrom", userfrom);
				}
				offjson.addProperty("sendtime", sendtime);
				offjson.addProperty("message", message);
				
				cChannel.write(ByteBuffer.wrap((offjson.toString()+"\n").getBytes()));
			}
			offmsg.close();
			
			db.delOffMsg(name);  //删除该用户的离线消息
			
			//在线登陆提醒服务
			userLoginCall(name);
			
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void register(SocketChannel cChannel, JsonObject jobject) throws IOException{
		//访问数据库,完成用户注册
		String name = jobject.get("name").getAsString();
		String pwd = jobject.get("pwd").getAsString();
		String pnumber = jobject.get("pnumber").getAsString();
		String addr = jobject.get("addr").getAsString();
		
		DbConnector db = DbConnector.getInstance();
		String recv = db.register(name, pwd, pnumber, addr);
		
		JsonObject json = new JsonObject();
		json.addProperty("msgtype", 19);   //19表示服务器响应用户信息注册
		if (recv.compareTo("reg_ok") == 0){
			json.addProperty("ack", "reg_ok");
		}
		else if (recv.compareTo("pri_error") == 0){
			json.addProperty("ack", "pri_error");
			json.addProperty("reason", "username has been userd!!!");
		}
		else if (recv.compareTo("data_long_error") == 0){
			json.addProperty("ack", "data_long_error");
			json.addProperty("reason", "input data too long!!!");
		}
		else{
			json.addProperty("ack", "reg_error");
		}
		
		cChannel.write(ByteBuffer.wrap((json.toString()+"\n").getBytes()));
	}

	public void chat(SocketChannel cChannel, JsonObject jobject){
		//完成向目标用户聊天消息的转发
		try{
			String userfrom = jobject.get("userfrom").getAsString();
			String sendto = jobject.get("sendto").getAsString();
			String msg = jobject.get("msg").getAsString();
			
			JsonObject json = new JsonObject();  //回复给发送发的json字符串
			json.addProperty("msgtype", 18);   //18表示服务器响应用户发送聊天消息的发送方
			
			boolean userexist = NioServer.checkExist(sendto);  //访问数据库检查用户是否存在
			if (!userexist){
				json.addProperty("ack", "exist_error");
				json.addProperty("reason", "your send user not exist!!!");
				cChannel.write(ByteBuffer.wrap((json.toString()+"\n").getBytes()));
				return;
			}
			
			SocketChannel sendToClient = NioServer.checkOnline(sendto);   //访问ConcurrentHashMap,检查用户是否在线
			if (sendToClient == null){
				json.addProperty("ack", "online_error");
				json.addProperty("reason", "your send user offline,but " + sendto + " will receive the message when " + sendto + " online next time!");
				cChannel.write(ByteBuffer.wrap((json.toString()+"\n").getBytes()));
				//将离线消息存储进数据库
				DbConnector db = DbConnector.getInstance();
				db.saveMsg(userfrom,sendto, msg);
				return;
			}
			
			//回复给发送方,表示用户在线,并且成功发送
			json.addProperty("ack", "send_ok");
			cChannel.write(ByteBuffer.wrap((json.toString()+"\n").getBytes()));
			
			JsonObject sendjson = new JsonObject();  //回复给接收方的json字符串
			
			String time = getCurTime();
			
			sendjson.addProperty("msgtype", 17);  //17表示服务器转发聊天消息给接收方
			sendjson.addProperty("ack", "usermsg");  //usermsg  servermsg
			sendjson.addProperty("userfrom", userfrom);
			sendjson.addProperty("sendtime", time);
			sendjson.addProperty("msg", msg);
			
			sendToClient.write(ByteBuffer.wrap((sendjson.toString()+"\n").getBytes()));
		}catch (IOException e){
			e.printStackTrace();
		}
		
	}
	
	//用户上线提醒
	public void userLoginCall(String name) throws IOException{
		String time = getCurTime();
		
		Set<Entry<String, SocketChannel>> users = NioServer.map.entrySet();
		for (Entry<String, SocketChannel> user : users){
			JsonObject onlineCall = new JsonObject();
			onlineCall.addProperty("msgtype", 17);
			onlineCall.addProperty("ack", "servermsg");
			onlineCall.addProperty("sendtime", time);
			onlineCall.addProperty("message", "user " + name + " has logged in!");
			
			String recvUserName = user.getKey();
			if (recvUserName.compareTo(name) != 0){
				user.getValue().write(ByteBuffer.wrap((onlineCall.toString()+"\n").getBytes()));
			}
				
		}
	}
	
	//用户下线提醒
	public void userExitCall(String name){
		String time = getCurTime();
		try{
			Set<Entry<String, SocketChannel>> users = NioServer.map.entrySet();
			for (Entry<String, SocketChannel> user : users){
				JsonObject offlineCall = new JsonObject();
				offlineCall.addProperty("msgtype", 17);  //用户接收其他用户发送的消息
				offlineCall.addProperty("ack", "servermsg");
				offlineCall.addProperty("sendtime", time);
				offlineCall.addProperty("message", "user " + name + " has quit out!");
				 
				user.getValue().write(ByteBuffer.wrap((offlineCall.toString()+"\n").getBytes()));
			}
		}catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	//删除map中指定SocketChannel并返回username
	private String removeUser(SocketChannel cChannel){
		if (cChannel != null){
			Iterator<Entry<String, SocketChannel>> it = NioServer.map.entrySet().iterator();
			while (it.hasNext()){
				Entry<String, SocketChannel> entry = it.next();
				if (entry.getValue().equals(cChannel)){
					String username = entry.getKey();
					NioServer.map.remove(username);
					return username;
				}
			}
		}
		return null;
	}
	
	public void userExit(SocketChannel cChannel, SelectionKey key) throws IOException{
		//删除map表中用户
		String exitusername = removeUser(cChannel);
		
		//下线提醒
		if (exitusername != null){
			userExitCall(exitusername);
		}
		
		cChannel.close();
		key.cancel();
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try{
			while (!Thread.currentThread().isInterrupted()){
				int num = selector.select();
				
				if (num <= 0){
					Iterator<SocketChannel> it = list.iterator();
					while (it.hasNext()){
						SocketChannel cChannel = it.next();
						cChannel.register(selector, SelectionKey.OP_READ);
						it.remove();
					}
					continue;
				}
				
				Iterator<SelectionKey> it = selector.selectedKeys().iterator();
				
				while (it.hasNext()){
					SelectionKey key = it.next();
					it.remove();
					
					if (key.isValid() && key.isReadable()){
						SocketChannel cChannel = (SocketChannel) key.channel();
						
						try{
							
							ByteBuffer buffer = ByteBuffer.allocate(1024);
							
							int readcnt = cChannel.read(buffer);
							
							//客户端正常关闭
							if (readcnt <= 0){
								userExit(cChannel, key);
								continue;
							}
							
							String recvMsg = new String(buffer.array()).trim();
							System.out.println("recvMsg:" + recvMsg);
							
							JsonParser parser = new JsonParser();
							JsonElement element = parser.parse(recvMsg);
							JsonObject jobject = element.getAsJsonObject();
							
							int msgtype = jobject.get("msgtype").getAsInt();
							
							switch(msgtype){
							
							case 1:  //处理登陆消息
								login(cChannel, jobject);
								break;
								
							case 2:  //处理注册消息
								register(cChannel, jobject);
								break;
								
							case 3:  //处理聊天消息
								chat(cChannel, jobject);
								break;
								
							}
						}catch(IOException e){
							
							//处理客户端异常关闭
							userExit(cChannel, key);	
						}
					}
				}
			}
		}catch (IOException e){
			e.printStackTrace();
		}
	}
	
}

class WorkMenu implements Runnable{
	private Scanner scan;
	
	public WorkMenu(){
		scan = new Scanner(System.in);
	}
	
	public void menu(){
		System.out.println("--------------");
		System.out.println("1.广播消息");
		System.out.println("0.关闭服务器");
		System.out.println("--------------");
	}
	
	public String getCurTime(){
		Date date = new Date(System.currentTimeMillis());
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(date);
	}
	
	//给所有在线或者不在线用户发送广播消息
		public void broadcastMsg(String broadMsg){
			DbConnector db = DbConnector.getInstance();
			String sql = "select * from chatuser";
			ResultSet user = db.select(sql);
			try {
				while (user.next()){					
					String time = getCurTime();					
					JsonObject jobject = new JsonObject();
					
					String sendto = user.getString("username");
					SocketChannel client = NioServer.checkOnline(sendto); //如果该用户在线
					if (client != null){  //将该消息发送给在线用户
						jobject.addProperty("msgtype", 17);  //17表示客户接收在线消息
						jobject.addProperty("ack", "servermsg");
						jobject.addProperty("sendtime", time);
						jobject.addProperty("message", broadMsg);
						
						try {
							client.write(ByteBuffer.wrap((jobject.toString()+"\n").getBytes()));
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					else{  //将该消息储存进数据库,离线客户下次上线自动接收
						db.saveMsg("SuperUser", sendto, broadMsg);
					}
				}
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		menu();
		int select = 0;
		
		while ((select = Integer.parseInt(scan.nextLine())) != 0){
			
			switch(select){
			
			case 1:  //发送广播消息
				System.out.print("input broadcast message: ");
				String broadMsg = scan.nextLine();
				broadcastMsg(broadMsg);
				break;
				
			case 0:  //关闭服务器
				break;
			}
			menu();
		}
	}
	
}

public class NioServer {
	
	private Selector selector;
	private ServerSocketChannel sschannel;
	private WorkTask worktask;
	private static ExecutorService threadpool;
	public static ConcurrentHashMap<String, SocketChannel> map;   //储存在线客户的<name, socketchannel>
	
	static{
		threadpool = Executors.newFixedThreadPool(2);
		map = new ConcurrentHashMap<String, SocketChannel>();
	}
	
	public NioServer() throws IOException{
		selector = Selector.open();
		sschannel = ServerSocketChannel.open();
		sschannel.bind(new InetSocketAddress("127.0.0.1", 6000));
		sschannel.configureBlocking(false);
		sschannel.register(selector, SelectionKey.OP_ACCEPT);
		
		worktask = new WorkTask();
		threadpool.submit(worktask);
		threadpool.submit(new WorkMenu());
	}
	
	public void startServer() throws IOException{
		System.out.println("server supply service on 6000...");
		
		while (!Thread.currentThread().isInterrupted()){
			int num = selector.select();
			
			if (num <= 0){
				continue;
			}
			
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			
			while (it.hasNext()){
				SelectionKey key = it.next();
				it.remove();
				
				if (key.isValid() && key.isAcceptable()){
					SocketChannel cChannel = sschannel.accept();
					cChannel.configureBlocking(false);
					
					worktask.getList().add(cChannel);
					worktask.getSelector().wakeup();
				}
			}
		}
	}
	
	//检查该用户是否在线
	public static SocketChannel checkOnline(String sendto){
		Set<Entry<String, SocketChannel>> entryset = NioServer.map.entrySet();
		for (Entry<String, SocketChannel> entry : entryset){
			if (entry.getKey().compareTo(sendto) == 0){
				return entry.getValue();
			}
		}
		return null;
	}
		
	//检查该用户是否存在
	public static boolean checkExist(String sendto){
		DbConnector db = DbConnector.getInstance();
		ResultSet rset = db.exist(sendto);
		
		try {
			while (rset.next()){
				if (rset.getString("username").compareTo(sendto) == 0){
					return true;
				}
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return false;
	}

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		NioServer server = new NioServer();
		server.startServer();
	}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值