我们知道socket通信是分服务端和客户端两种,如何设计一个好的通信框架呢。本文从实战角度构建一个适合中小型项目开发的socket服务框架。
这里需要具有一定的java基础,比如线程知识,socket通信基础。
1.服务端设计与实现
这里我们将服务器端与客户端通信模型为per-connection per-thread,即一个连接一个线程。不过我们这里进行了部分优化,即建立线程池来管理
这些服务线程。
1)服务配置文件
public class Consts {
//服务ip
public static final String ServerIP="127.0.0.1";
//服务端口
public static final int ServerPort=10254;
}
2)socket处理工具类
public class SocketUtil {
//发送数据
public static void Send(String obj,Socket socket) throws Exception{
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), "UTF-8"));
writer.append(obj);
writer.newLine();
writer.flush();
}
//接受数据
public static String Accept(Socket socket) throws IOException{
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
String line = reader.readLine();
return line;
}
}
3)服务类
package Server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import utils.logging.Logger;
/*
* 单例设计模式设计服务类,将服务类设计为线程
* */
public class Listener extends Thread {
private static Listener mListener = null;
public static Listener getInstance() {
if (mListener == null) {
synchronized (Listener.class) {
if (mListener == null)
mListener = new Listener();
}
}
return mListener;
}
// 线程池,用于支持并发。
private ExecutorService mThreadPool;
// 关闭标志
private volatile boolean mStopFlag;
private ServerSocket mServerSocket;
/**
* 只能存在单实例
*/
private Listener() {
mThreadPool = Executors.newCachedThreadPool();
mStopFlag = false;
mServerSocket = null;
}
/**
* stop
*/
public void setStopFlag() {
mStopFlag = true;
}
@Override
public void run() {
try {
mServerSocket = new ServerSocket(Consts.ServerPort);
} catch (IOException e1) {
e1.printStackTrace();
return;
}
Logger.getLogger().log("服务器已经启动,等待连接");
while (!mStopFlag) {
try {
Socket socket = null;
socket = mServerSocket.accept();// which is connected to the
// socket from client
// break;
if (!mStopFlag) {
mThreadPool.execute(new ClientAcceptor(socket));
} else {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("已经跳出循环了");
try {
mThreadPool.shutdown();
mThreadPool.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Logger.getLogger().log("Listener准备退出");
return;
}
}
3) socket处理类,即处理连接的线程类
public class ClientAcceptor implements Runnable {
private Socket mSocket = null;
public ClientAcceptor(Socket socket) {
this.mSocket = socket;
}
@Override
public void run() {
try {
String resp= readSocket();
writeResponse("服务器返回....");
} catch (Exception e) {
} finally {
if (!mSocket.isClosed())
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String readSocket() {
User user=null;
if (null == mSocket)
return "";
String server_content="";
try {
server_content=SocketUtil.Accept(mSocket);
System.out.println("接收客户端数据为:"+server_content);
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return server_content;
}
private void writeResponse(String resp)
throws Exception {
SocketUtil.Send(resp,mSocket);
}
}
public class Client {
public static Socket socket =null;
public static void openSocket(){
try {
socket=new Socket(Consts.ServerIp, Consts.ServerPort);
} catch (Exception e) {
System.out.println("客户端未连接...");
}
}
public static void close(){
try {
socket.close();
} catch (IOException e) {
System.out.println("客户端关闭异常...");
}
}
public static void main(String []args) throws Exception{
Listener.getInstance().start();
System.out.println("客户端等待10秒");
#为了使服务进程先启动....
Thread.sleep(1000*10);
openSocket();
SocketUtil.Send("hello world",socket);
System.out.println("------------waitting for response------------------");
String respstr=SocketUtil.Accept(socket);
System.out.println(respstr);
close();
}
上面测试中,我们把服务和客户都放在了main函数里便于操作。结果如下:
客户端等待10秒
2017-03-20 01:43:19 : 服务器已经启动,等待连接
2017-03-20 01:43:29 : 接收到指令,Listener被唤醒
------------waitting for response------------------
接收客户端数据为:hello world
服务器返回....
这里我们针对每个连接,我们都创建了一个线程。在访问量很大时,这样的设计方案将会使得服务器的内存被迅速占用。最终会影响体验。下一博客中,我们讨论使用基于事件机制和观察者设计模式来改进设计方案。