【MyDB】7-客户端服务端通信之2-Server的实现
前言
Server的实现
Server的start方法启动一个ServerSocket监听端口,有客户端请求到来的时候,会把请求交给一个新线程HandleSocket
处理。
HandleSocket
线程建立连接后初始化Packager
,随后循环接收来自客户端的sql语句,调用client.excute
解析并返回结果
executor
类是调用Parser获取对应语句的结构化信息对象,之后根据语句类型,调用TBM的不同方法处理。
Launcher
类是服务器的启动入口,解析命令行参数,根据-open/-create来决定是创建数据库文件还是连接一个已有的数据库
start()启动ServerSocket
public void start() {
ServerSocket ss = null;
try {
ss = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
return;
}
System.out.println("Server listen to port: " + port);
// 使用线程池来处理客户端请求
ThreadPoolExecutor tpe = new ThreadPoolExecutor(10, 20, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.CallerRunsPolicy());
try {
while(true) {
Socket socket = ss.accept(); // 阻塞,直到有客户端连接上来
Runnable worker = new HandleSocket(socket, tbm); //创建线程任务
tpe.execute(worker); // 封装到线程池
}
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
ss.close();
} catch (IOException ignored) {}
}
}
HandleSocket处理单个客户端请求
/**
* 处理单个客户端请求的线程,循环接收来自客户端的数据进行处理。
*/
class HandleSocket implements Runnable {
private Socket socket;
private TableManager tbm;
public HandleSocket(Socket socket, TableManager tbm) {
this.socket = socket;
this.tbm = tbm;
}
@Override
public void run() {
// 1. 打印连接信息
InetSocketAddress address = (InetSocketAddress)socket.getRemoteSocketAddress();
System.out.println("Establish connection: " + address.getAddress().getHostAddress()+":"+address.getPort());
Packager packager = null;
try {
Transporter t = new Transporter(socket);
Encoder e = new Encoder();
packager = new Packager(t, e);
} catch(IOException e) {
e.printStackTrace();
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
return;
}
Executor exe = new Executor(tbm);
// 2. 循环接收数据并处理
while(true) {
Package pkg = null;
try {
pkg = packager.receive();
} catch(Exception e) {
break;
}
byte[] sql = pkg.getData();
byte[] result = null;
Exception e = null;
try {
result = exe.execute(sql);
} catch (Exception e1) {
e = e1;
e.printStackTrace();
}
pkg = new Package(result, e);
try {
packager.send(pkg);
} catch (Exception e1) {
e1.printStackTrace();
break;
}
}
exe.close();
try {
packager.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
execute()执行SQL语句
处理的核心是 Executor 类,Executor 调用 Parser 获取到对应语句的结构化信息对象,并根据对象的类型,调用 TBM 的不同方法进行处理。具体不再赘述。
/**
* 执行SQL语句
* @param sql
* @return
* @throws Exception
*/
public byte[] execute(byte[] sql) throws Exception {
System.out.println("Execute: " + new String(sql));
// 1.解析SQL语句
Object stat = Parser.Parse(sql);
// 2.根据SQL类型执行SQL语句
if(Begin.class.isInstance(stat)) {
if(xid != 0) {
throw Error.NestedTransactionException;
}
BeginRes r = tbm.begin((Begin)stat); // 调用TableManager开启事务
xid = r.xid;
return r.result;
} else if(Commit.class.isInstance(stat)) {
if(xid == 0) {
throw Error.NoTransactionException;
}
byte[] res = tbm.commit(xid); // 调用TableManager提交事务
xid = 0;
return res;
} else if(Abort.class.isInstance(stat)) {
if(xid == 0) {
throw Error.NoTransactionException;
}
byte[] res = tbm.abort(xid); // 调用TableManager回滚事务
xid = 0;
return res;
} else {
return execute2(stat);
}
}
Launcher 启动入口
top/xianghua/mydb/server/Launcher.java类,则是服务器的启动入口。这个类解析了命令行参数。很重要的参数就是 -open 或者 -create。Launcher 根据两个参数,来决定是创建数据库文件,还是启动一个已有的数据库。