本文根据https://github.com/jasonGeng88/blog整理
1.在java.net包中,提供了客户端与服务器通信的方法,单线程的通信代码实现如下:
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import com.test.network.util.HttpUtil;
public class SocketHttpClient {
public void start(String host, int port) {
// 初始化 socket
Socket socket = new Socket();
try {
// 设置 socket 连接
SocketAddress remote = new InetSocketAddress(host, port);
socket.setSoTimeout(5000);
socket.connect(remote);
// 发起请求
PrintWriter writer = getWriter(socket);
System.out.println(compositeRequest(host));
writer.write(HttpUtil.compositeRequest(host));
writer.flush();
// 读取响应
String msg;
BufferedReader reader = getReader(socket);
while ((msg = reader.readLine()) != null){
System.out.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private BufferedReader getReader(Socket socket) throws IOException {
InputStream in = socket.getInputStream();
return new BufferedReader(new InputStreamReader(in));
}
private PrintWriter getWriter(Socket socket) throws IOException {
OutputStream out = socket.getOutputStream();
return new PrintWriter(new OutputStreamWriter(out));
}
private String compositeRequest(String host){
return "GET / HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: curl/7.43.0\r\n" +
"Accept: */*\r\n\r\n";
}
}
实例化一个客户端,启动通信:
public class Application {
public static void main(String[] args) {
new SocketHttpClient().start("www.baidu.com", 80);
}
}
2.由于上述操作会涉及到io的读写,而io的读写是同步阻塞的,所以在高并发下性能会非常低下,如果要同时请求10个不同的站点,它一定是第一个请求响应结束后,才会发起下一个站点处理。代码如下:
public class SingleThreadApplication {
public static void main(String[] args) {
// HttpConstant.HOSTS 为 站点集合
for (String host: HttpConstant.HOSTS) {
new SocketHttpClient().start(host, HttpConstant.PORT);
}
}
}
针对以上情况,可以采用多线程方式:
public class MultiThreadApplication {
public static void main(String[] args) {
for (final String host: HttpConstant.HOSTS) {
Thread t = new Thread(new Runnable() {
public void run() {
new SocketHttpClient().start(host, HttpConstant.PORT);
}
});
t.start();
}
}
}
这种方式起初看起来挺有用的,但并发量一大,应用会起很多的线程。都知道,在服务器上,每一个线程实际都会占据一个文件句柄。而服务器上的句柄数是有限的,而且大量的线程,造成的线程间切换的消耗也会相当的大。所以这种方式在并发量大的场景下,一定是承载不住的。
考虑采用线程池来解决:
public class MultiThreadApplication {
public static void main(String[] args) {
ExecutorService executors = Executors.newFixedThreadPool(10);
for (final String host: HttpConstant.HOSTS) {
Runnable t = new Runnable() {
public void run() {
new SocketHttpClient().start(host, HttpConstant.PORT);
}
};
executors.execute(t);
}
}
}
这种方式,看起来是最优的了。那有没有更好的呢,如果一个线程能同时处理多个 socket 连接,并且在每个 socket 输入输出数据没有准备好的情况下,不进行阻塞,那是不是更优呢。这种技术叫做“NIO多路复用”。在 JAVA 的 nio 包中,提供了相应的实现。
下一章节会讲到。