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();
}
}
14My2.0版本聊天系统(服务器selector移植)
最新推荐文章于 2020-07-24 16:57:06 发布