作为整个网络编程系列的第一篇,在这篇文章里,使用JAVA语言编写了一个小例子,实现了一个可以提供时间查询服务的Server端,和一个客户端。
服务端代码结构如下:
TimeServer是启动类,启动后监听请求,每当收到一个socket请求时,TimeServer类就新建一个Handler线程来处理该请求,具体的就是由TimeServerHandler来处理这个请求。
TimeServer类代码如下:
package bio.sample;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TimeServer {
public static void main(String[] args) {
int port = 8070;
if(args != null && args.length > 0)
{
port = Integer.valueOf(args[0]);
}
ServerSocket server = null;
try{
server = new ServerSocket(port);
System.out.println("The time Server is start in port :"+ port);
Socket socket = null;
while(true)
{
socket = server.accept();
new Thread(new TimeServerHandler(socket)).start();
}
}
catch(Exception e)
{
e.printStackTrace();
}
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
TimeServerHandler类代码如下:
package bio.sample;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;
public class TimeServerHandler implements Runnable {
private Socket socket;
public TimeServerHandler(Socket socket)
{
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
//PrintWriter构造方法的第二个参数,如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区
out = new PrintWriter(this.socket.getOutputStream(),true);
String currentTime = null;
String body = null;
while(true)
{
body = in.readLine();
if(body != null && body.length() != 0)
break;
}
System.out.println("the time server receive order:"+body);
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ?
new Date(System.currentTimeMillis()).toString():"BAD ORDER";
out.println(currentTime);
} catch (IOException e) {
e.printStackTrace();
}
try {
in.close();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端代码结构如下:
客户端代码分为两个类,启动类为TimeClient,这个类会不断的新建线程,并让新建的线程去请求TimeServer,同时记录开始时间与结束时间。
TimeClient代码如下:
package bio.sample;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;
public class TimeClient {
private static int PORT = 8070;
private static String inataddress = "192.168.192.151";
public static void main(String[] args) {
for(int i = 100;i <= 2000;i+=100)
{
test(i);
System.gc();
}
}
private static void test(int THREAD_SIZE) {
CountDownLatch startSignal = new CountDownLatch(THREAD_SIZE);
CountDownLatch doneSignal = new CountDownLatch(THREAD_SIZE);
Thread[] trs = new Thread[THREAD_SIZE];
for(int i = 0;i < THREAD_SIZE;i++)
{
trs[i] = new Thread(new TimeClientThread(inataddress,PORT,startSignal,doneSignal));
}
long start_ms = System.currentTimeMillis();
for(int i = 0;i < THREAD_SIZE;i++)
trs[i].start();
try {
doneSignal.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end_ms = System.currentTimeMillis();
System.out.println("|"+THREAD_SIZE+"|"+ (end_ms - start_ms)+"|");
}
}
TimeClientThread代码如下
package bio.sample;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;
public class TimeClientThread implements Runnable {
private int port;
private String serverAddress;
private CountDownLatch startSignal;
private CountDownLatch doneSignal;
public TimeClientThread(String inatAddres,int serverPort, CountDownLatch sSignal, CountDownLatch dSignal)
{
port = serverPort;
serverAddress = inatAddres;
startSignal = sSignal;
doneSignal = dSignal;
}
@Override
public void run() {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try{
startSignal.countDown();
startSignal.await();
socket = new Socket(serverAddress,port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
out.println("QUERY TIME ORDER");
//System.out.println("Send order to server");
String resp = in.readLine();
//System.out.print("Now is" + resp);
doneSignal.countDown();
}
catch(Exception e)
{
e.printStackTrace();
}
try {
in.close();
out.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
我们可以看到,在TimeClient端我们可以开启大量的线程去同时访问服务端,然后记录时间,就可以得到对服务端性能的一个简单评估。
然后我们将代码部署在两个主机上,两台主机都是百兆网卡,位于同一局域网,RTT<1ms;
其中服务端运行参数如下:-Xmx2G -Xms1000M
客户端运行参数如下:-Xmx1000m -Xms200M
然后先启动服务端,再启动客户端,结果如下:
线程数 | 耗费时间(ms) |
---|---|
100 | 52 |
200 | 54 |
300 | 80 |
400 | 87 |
500 | 107 |
600 | 146 |
700 | 130 |
800 | 142 |
900 | 168 |
1000 | 184 |
1200 | 230 |
1400 | 259 |
1600 | 302 |
1800 | 332 |
2000 | 380 |
然后我们对代码进行修改,使得每个线程在创建后,就立马开始发出请求,不再等待其他请求一起开始。即将TimeClientThread中的如下代码注释掉。
// startSignal.countDown();
// startSignal.await();
实验结果如下:
线程数 | 耗费时间(ms) |
---|---|
100 | 42 |
200 | 53 |
300 | 65 |
400 | 75 |
500 | 91 |
600 | 120 |
700 | 109 |
800 | 123 |
900 | 135 |
1000 | 149 |
1200 | 183 |
1400 | 209 |
1600 | 239 |
1800 | 264 |
2000 | 299 |
2500 | 370 |
3000 | 444 |
3500 | 521 |
4000 | 596 |
4500 | 858 |
5000 | 743 |
6000 | 887 |
7000 | 1036 |
8000 | 1181 |
9000 | 1328 |
10000 | 1472 |
由上图可以看到由于不必所有线程同时开始,所以我们在实验室中可以启动的最大线程数就变多了,可以看到我们可以启动大约10000个线程,但是,在时间上和同时开始,并没有过大的区别。
所以我们可以估计整个程序的性能瓶颈并不是在客户端的请求方式,而是在服务端接受并处理请求的方式上。