基于Socket的服务端多线程模式——服务端和客户端代码
本文代码来源于《实战java高并发程序设计》葛一鸣 郭超 著
学习这本书的过程中,感觉这一部分比较重要,自己做下总结,也希望能给大家提供些帮助。
本代码模拟简单的Echo服务器,对于Echo服务器,他会读取客户端的一个输入,并将这个输入原封不动地返回给客户端。虽然实例很简单,但麻雀虽小五脏俱全,需要一套完整的Socket处理机制。适合拿来学习。下面贴出本例代码。
1、服务端代码:
服务器会为每一个客户端连接启动一个线程,这个新的线程会全心全意为这个客户端服务。同时,为了接受客户端连接,服务器还会额外使用一个派发线程,如上图:
package socket.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
* @author FHY
* 多线程应用的服务端代码
*
*/
public class MultiThreadEchoServer {
//创建一个线程池,不限制连接的线程数量
public static ExecutorService tp = Executors.newCachedThreadPool();
static class HandleMsg implements Runnable{
Socket clientSocket;
public HandleMsg(Socket clientSocket){
this.clientSocket = clientSocket;
}
@Override
public void run() {
BufferedReader is = null;
PrintWriter os = null;
try{
//输入流 (使用了装饰模式)
is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
//输出流
os = new PrintWriter(clientSocket.getOutputStream(), true);
String inputLine = null;
long b = System.currentTimeMillis();
while ((inputLine = is.readLine())!= null ){
os.println(inputLine);
}
long e = System.currentTimeMillis();
System.out.println("Spend: "+(e-b));
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(is != null) is.close();
if(os != null) os.close();
clientSocket.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ServerSocket echoSocket = null;
Socket clientSocket = null;
try{
//设置服务端的端口号
echoSocket = new ServerSocket(8000);
}catch(IOException e){
System.out.println(e);
}
while(true){
try{
//接受客户端
clientSocket = echoSocket.accept();
System.out.println(clientSocket.getRemoteSocketAddress() + " connet!");
tp.execute(new HandleMsg(clientSocket));
}catch(IOException e){
System.out.println(e);
}
}
}
}
这就是一个支持多线程的服务端的核心内容。它的特点是,在相同可支持的线程范围内,可以尽量多地支持客户端的数量,同时和单线程服务器相比,它可以更好的使用多核CPU。
2、客户端代码:
package socket.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
*
* @author FHY
* 多线程应用的客户端代码
*
*/
public class ClientSocketDemo {
public static void main(String[] args) {
Socket client = null;
PrintWriter writer = null;
BufferedReader reader = null;
try{
client = new Socket();
//客户端连接到服务端
client.connect(new InetSocketAddress("localhost", 8000), 1000*6);
writer = new PrintWriter(client.getOutputStream(), true);
writer.println("Hello!");
writer.flush();
reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
System.out.println("from server:" +reader.readLine());
}catch(UnknownHostException e){
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally {
try{
if(writer != null) writer.close();
if(reader != null) reader.close();
if(client != null) client.close();
}catch(IOException e){
System.out.println(e);
}
}
}
}
总结:这种多线程的服务器开发模式是及其常用的,对于绝大多数应用来说,这种模式可以很好地工作,但是如果你想让程序工作地更加有效,就必须知道这种模式的一个重大弱点——那就是它倾向于让CPU进行IO等待。
使用java的NIO就可以将上面的网络IO等待时间从业务处理线程中抽取出来。