构造ServerSocket :
在客户/服务器通信模式中,服务器需要创建创建监听特定端口的ServerSocket。负责接收客户连接请求。
如果在构造方法运行过程中 如果无法绑定到指定的端口上,会抛出BindException。
有如下原因:
1:端口号被占用。
2 :在某些OS中,如果没有以超级用户身份运行程序,不允许绑定到0-1023的端口。
设定客户连接请求的队列:
当服务器进程运行时,可能会同时监听多个客户的连接请求。SerVerSocket构造方法的backlog参数用来设置连接请求队列的长度。
ServerSocket的设置:
SO_TIMEOUT:表示等待客户连接的超时时间。
SO_Rcvbuf :表示接收数据缓冲区的大小。
SO_REUSEADDR: 表示是否重用服务器所绑定的端口号。
创建多线程的服务器:
可以用并发性能来衡量一个服务器同时响应多个客户的能力。一个具有好的并发性能的服务器,必须符合两个条件:
能同时接受并处理多个客户连接
对于每个客户,都会迅速给予响应。
创建具有多线程的服务器的方式:
1:为每一个客户分配一个工作线程。
2:创建一个线程池由其中的工作线程为客户服务。
3:利用idk的java类库中现成的线程池,由它的工作线程来为客户服务。
关闭服务器:
具有关闭自己功能的服务器除了监听普通客户程序的连接外,还会在其它端口监听管理程序AdminClient的连接,当服务器在8001 端口接收到AdminClient发送的“shutDown”命令时,就会开始关闭服务器,不会在接收任何客户端发送的进程连接请求,对于那些已经接收但还没有处理的客户连接,则会丢弃与该客户的通信任务,而不会吧通信任务加入到线程池的工作队列中,服务器会等待线程池把当前工作队列中的所有任务执行完成,才结束任务。
代码如下
public class EchoService {
private int port = 8000;// 服务端口号
private ServerSocket serverSocket;
private ExecutorService executorService; //线程池
private final int POOL_SIZE = 4 ; //单个CPU时线程池中工作线程的数目
private int portForShutDown = 8001; // 用于监听关闭服务器的端口
private ServerSocket serverSocketForShutdown;
private boolean isShutdown=false; //标志服务器是否关闭
private Thread shutdownThread = new Thread() { // 负责关闭服务器的线程
public void start() {
this.setDaemon(true); //设置为守护线程(也称为后台线程)
super.start();
}
public void run () {
while(!isShutdown) {
Socket socketForShutdown = null;
try {
socketForShutdown= serverSocketForShutdown .accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socketForShutdown.getInputStream()));
String command = br.readLine();
if (command .equals("shutdown")) {
long beginTime = System.currentTimeMillis();
socketForShutdown.getOutputStream().write("服务器正在关闭\r\n".getBytes());
isShutdown= true;
//请求关闭线程池,线程池不会接受新的任务,但是会继续执行工作队列中已有的任务
executorService.shutdown();
while (!executorService.isTerminated())
executorService.awaitTermination(30, TimeUnit.SECONDS);//等待关闭线程池,每次等待的超时时间为30秒
serverSocket.close();//关闭与客户端通信的ServerSocket
long endTime = System.currentTimeMillis();
socketForShutdown.getOutputStream().write(("服务器已经关闭"+"关闭服务器使用了"+(endTime-beginTime)+"毫秒\r\n").getBytes() );
socketForShutdown.close();
serverSocket.close();
}else {
socketForShutdown.getOutputStream().write("错误的命令".getBytes());
socketForShutdown.close();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
};
public EchoService() throws Exception{
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(600000);//设定等待客户连接的超时时间为60秒
serverSocketForShutdown = new ServerSocket(portForShutDown);
//创建线程池
executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*POOL_SIZE);
shutdownThread.start();//启动关闭服务器的线程
System.out.println("服务器启动中");
}
public void service () {
while (!isShutdown) {
Socket socket = null;
try {
socket = serverSocket.accept();
socket.setSoTimeout(60000);//等待客户发送数据超时时间设定为60秒
executorService.execute(new Handle ( socket));
} catch (SocketTimeoutException e) {
//不必处理等待客户连接时出现超时异常
} catch (RejectedExecutionException e) {
try {
if (socket!= null) {
socket.close();
}
}catch(IOException x) {
} return;
}catch (SocketException e) {
if (e.getMessage().indexOf("socket closed")!=-1) return;
}catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
new EchoService().service();
}
class Handle implements Runnable{
Socket s = null;
public Handle (Socket s) {
this.s=s;
}
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw = new PrintWriter(new BufferedOutputStream(s.getOutputStream()));
boolean flag = true;
while (flag) {
String info = br.readLine();
if ("".equals(info) || "bye".equals(info)) {
flag = false;
}else {
System.out.println(info);
pw.println("echo:"+info);
pw.flush();//保证缓冲区的数据都输出去
}
}
br.close();
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}