需求
基于java编程实现一个HTTP服务器程序(20分)和HTTP客户端程序(15分),要求
- 采用多线程技术或线程池编程技术处理客户端请求,支持多客户端同时访问;(10分)
- 实现GET、HEAD和POST请求,对客户端发送的不同请求给予正确响应;(15分)
- 在服务器上放一个静态网站(由HTML文本、图片文件或JS文件等组成),能根据不同请求,返回包括文本和图像2种(及以上)类型的响应,客户端可以正确显示和访问。(10分)
- HTTP客户端程序能与该HTTP服务器连接并展示响应结果,正确发送不同类型的请求。(10分)
- 能使用cookie编程技术保存和传递会话状态信息,比如保存用户信息等,需要保存的信息可自行决定。(10分)
- 对服务器进行性能分析。对服务器进行压力测试,测试可支持多少个客户端同时访问,测试可支持多少个文件同时传输等。(10分)
HTTP服务端
代码语言:javascript复制public class HttpServer {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(200);
try {
ServerSocket serverSocket = new ServerSocket(8888, 10000);
while (true) {
Socket client = serverSocket.accept();
executorService.execute(new TheadPoolTask(client));
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
executorService.shutdown();
}
}
}
这段代码是一个简单的HTTP服务器程序,它使用线程池来处理客户端的请求。
具体分析如下:
- 定义了一个名为HttpServer的公共类。
- 在主函数main中,创建了一个固定大小为200的线程池ExecutorService,并将其赋值给变量executorService。线程池用于管理并执行多个任务。
- 使用try-catch-finally块进行异常处理。
- 在try块中,创建一个ServerSocket对象serverSocket,并指定端口号为8888,设置连接请求队列最大长度为10000。
- 进入无限循环while (true),表示服务器会一直运行。
- 调用serverSocket的accept()方法接受客户端的连接请求,并将返回的Socket对象赋值给变量client。
- 使用线程池executorService的execute()方法提交一个新的任务TheadPoolTask来处理客户端的请求。每接受一个客户端连接,就会启动一个新的线程去处理。
- 若出现IOException异常,则在catch块中打印异常信息,并抛出RuntimeException异常。
- 在finally块中,调用executorService的shutdown()方法关闭线程池。
总结:这段代码创建了一个HTTP服务器,它通过监听指定端口接受客户端的连接请求,并使用线程池来并发处理客户端的请求,实现了多线程的服务端处理机制。
处理GET请求
代码语言:javascript复制 private void GET(String path) throws IOException {
//发送响应
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
String response = "HTTP/1.1 200 OKrn"
"Content-Type: text/html";
writer.println(response);
writer.println(cookie);
writer.println("Done!!!");
//发送文件
String mainPath = "C:\Users\Yezi\Desktop\互联网编程\实验4传输协议与套接字应用编程\web\";
File file = new File(mainPath path.substring(path.lastIndexOf('/') 1));
InputStream input = new FileInputStream(file);
OutputStream output = socket.getOutputStream();
new DataOutputStream(output).writeLong(file.length());
int one;
while ((one = input.read()) != -1) {
output.write(one);
}
}
这段代码是一个处理HTTP GET请求的方法。它发送响应头和文件内容给客户端。
具体分析如下:
- 这是一个私有方法,参数为path,表示请求的路径。
- 在方法中,通过socket获取输出流,创建PrintWriter对象writer,用于向客户端发送响应。
- 定义一个字符串response,设置HTTP的响应头,包括协议版本、状态码和Content-Type。
- 使用writer的println()方法将响应头和cookie等信息发送给客户端。
- 发送"Done!!!"给客户端,表示请求处理完成。
- 获取要发送的文件的完整路径,这里使用了固定的路径"C:UsersYeziDesktop互联网编程实验4传输协议与套接字应用编程web"。
- 创建File对象file,表示待发送的文件。
- 创建输入流InputStream,读取文件内容。
- 获取Socket的输出流OutputStream,用于向客户端发送数据。
- 使用DataOutputStream的writeLong()方法向客户端发送文件长度。
- 通过循环,从输入流input中读取文件内容,并通过输出流output写入到客户端。
总结:这段代码实现了一个简单的HTTP服务器的GET请求处理,它根据客户端请求的路径,发送对应的响应头和文件内容给客户端。
处理HEAD请求
代码语言:javascript复制 private void HEAD() throws IOException {
//发送响应
String response = "HTTP/1.1 200 OKrn"
"Content-Type: text/html";
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.println(response);
writer.println(cookie);
writer.println("Done!!!");
}
这段代码是一个处理HTTP HEAD请求的方法。它发送响应头给客户端,但不发送实际数据。
具体分析如下:
- 这是一个私有方法,没有参数。
- 在方法中,定义了字符串response,设置HTTP的响应头,包括协议版本、状态码和Content-Type。
- 通过socket获取输出流,创建PrintWriter对象writer,用于向客户端发送响应。
- 使用writer的println()方法将响应头和cookie等信息发送给客户端。
- 发送"Done!!!"给客户端,表示请求处理完成。
总结:这段代码实现了一个简单的HTTP服务器的HEAD请求处理,在接收到HEAD请求后,只发送响应头给客户端,不发送实际内容。
处理POST请求
代码语言:javascript复制 private void POST() throws IOException {
String response = "HTTP/1.1 200 OK";
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.println(response);
writer.println(cookie);
writer.println("Done!!!");
}
这段代码是一个处理HTTP POST请求的方法。它发送响应头给客户端,但不发送实际数据。
具体分析如下:
- 这是一个私有方法,没有参数。
- 在方法中,定义了字符串response,设置HTTP的响应头,包括协议版本和状态码。
- 通过socket获取输出流,创建PrintWriter对象writer,用于向客户端发送响应。
- 使用writer的println()方法将响应头和cookie等信息发送给客户端。
- 发送"Done!!!"给客户端,表示请求处理完成。
总结:这段代码实现了一个简单的HTTP服务器的POST请求处理,在接收到POST请求后,只发送响应头给客户端,不发送实际内容。
HTTP客户端
代码语言:javascript复制 public static void main(String[] args) throws IOException {
socket = new Socket(InetAddress.getLocalHost(), port);
Scanner scanner = new Scanner(System.in);
String method;
while (!Objects.equals(method = scanner.nextLine(), "quit")) {
if (Objects.equals(method, "HEAD")) {
HEAD();
} else if (Objects.equals(method, "POST")) {
POST();
} else {
GET(method.split(" ")[1]);
}
}
socket.close();
}
这段代码是一个简单的命令行程序,用于与HTTP服务器进行交互。
具体分析如下:
- 在main方法中,首先创建了一个客户端Socket对象,并连接到本地主机上的指定端口。
- 创建了一个Scanner对象scanner,用于读取用户输入。
- 进入循环,直到用户输入"quit"为止。
- 用户可以输入"HEAD"、"POST"或其他任意方法进行交互。
- 如果用户输入"HEAD",调用HEAD()方法处理HEAD请求。
- 如果用户输入"POST",调用POST()方法处理POST请求。
- 如果用户输入其他方法,以空格分割输入字符串,然后将第二个部分作为路径参数传递给GET()方法进行处理。
- 循环结束后,关闭Socket连接。
总结:这段代码实现了一个简单的命令行HTTP客户端,可以通过输入不同的方法和参数与服务器进行交互。用户可以执行HEAD、POST请求或者自定义其他请求方法。
GET请求
代码语言:javascript复制 private static void GET(String path) throws IOException {
//发送GET请求
String request = "GET /index.html/" path " HTTP/1.1rn"
"Host: localhost:8888rn"
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36rn"
"Accept: text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9rn"
"Accept-Encoding: gzip, deflate, brrn"
"Connection: keep-alivern";
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.println(request);
writer.println("Done!!!");
//接受服务器响应,并打印在控制台
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while (!Objects.equals(line = reader.readLine(), "Done!!!")) {
System.out.println(line);
}
//接受文件,并保存在本地
InputStream input = socket.getInputStream();
long fileLength=new DataInputStream(input).readLong();
String savePath = "C:\Users\Yezi\Desktop\互联网编程\实验4传输协议与套接字应用编程\";
OutputStream output = new FileOutputStream(new File(savePath path));
for(long i=0;i<fileLength;i ){
output.write(input.read());
}
output.close();
}
这段代码是用于发送HTTP GET请求的方法。
具体分析如下:
- 在GET()方法中,接受一个参数path,表示要请求的资源路径。
- 构建了一个GET请求的字符串request,包括请求行和请求头。
- 请求行:使用GET方法访问路径为 "/index.html/" path 的资源,使用HTTP/1.1协议版本。
- 请求头:指定主机为localhost:8888,设置User-Agent、Accept、Accept-Encoding和Connection等请求头字段。
- 创建了一个PrintWriter对象writer,用于向服务器发送请求。
- 使用writer的println()方法将请求发送给服务器。
- 向服务器发送"Done!!!",表示请求发送完成。
- 创建一个BufferedReader对象reader,读取服务器的响应。
- 循环读取响应的每一行,直到读到"Done!!!"为止,并将响应打印到控制台。
- 创建一个InputStream对象input,用于接收文件内容。
- 调用DataInputStream读取输入流中的文件长度信息,保存在变量fileLength中。
- 指定文件保存路径savePath。
- 创建一个FileOutputStream对象output,用于将接收到的文件内容写入本地文件。
- 使用循环读取input中的字节,并通过output将字节写入本地文件。
- 关闭output流。
总结:该GET()方法发送了一个HTTP GET请求到服务器,包括请求行和请求头,并接收服务器的响应。然后根据响应中的文件长度信息,接收文件内容,并将其保存在本地文件中。
HEAD请求
代码语言:javascript复制 private static void HEAD() throws IOException {
//发送HEAD请求
String request = "HEAD /index.html HTTP/1.1rn"
"Host: localhost:8888rn"
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36rn"
"Accept: text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9rn"
"Accept-Encoding: gzip, deflate, brrn"
"Connection: keep-alivern";
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.println(request);
writer.println("Done!!!");
//接受服务器响应,并打印在控制台
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while (!Objects.equals(line = reader.readLine(), "Done!!!")) {
System.out.println(line);
}
}
这段代码是用于发送HTTP HEAD请求的方法。
具体分析如下:
- 在HEAD()方法中,定义了一个字符串request,表示HTTP HEAD请求的内容。
- 请求行:使用HEAD方法访问路径为/index.html的资源,使用HTTP/1.1协议版本。
- 请求头:指定主机为localhost:8888,设置User-Agent、Accept、Accept-Encoding和Connection等请求头字段。
- 创建了一个PrintWriter对象writer,用于向服务器发送请求。
- 使用writer的println()方法将请求发送给服务器。
- 向服务器发送"Done!!!",表示请求发送完成。
- 创建一个BufferedReader对象reader,读取服务器的响应。
- 循环读取响应的每一行,直到读到"Done!!!"为止。
- 每读取一行响应,将其打印到控制台。
总结:该HEAD()方法发送了一个HTTP HEAD请求到服务器,包括请求行和请求头。通过PrintWriter发送请求给服务器,并使用BufferedReader接收并打印服务器的响应。
POST请求
代码语言:javascript复制 private static void POST() throws IOException {
//发送POST请求
String request = "POST /index.html HTTP/1.1rn"
"Host: localhost:8888rn"
"Content-Type: application/jsonrn"
"{n"
" "name": "YeMaolin",n"
" "email": "2021155015@email.szu.edu.cn",n"
" "age": 20n"
"}rn";
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.println(request);
writer.println("Done!!!");
//接受服务器响应,并打印在控制台
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while (!Objects.equals(line = reader.readLine(), "Done!!!")) {
System.out.println(line);
}
}
这段代码是用于发送HTTP POST请求的方法。
具体分析如下:
- 在POST()方法中,定义了一个字符串request,表示HTTP POST请求的内容。
- 请求行:使用POST方法访问路径为/index.html的资源,使用HTTP/1.1协议版本。
- 请求头:指定主机为localhost:8888,Content-Type为application/json。
- 请求体:JSON格式的数据,包含name、email和age字段。
- 创建了一个PrintWriter对象writer,用于向服务器发送请求。
- 使用writer的println()方法将请求发送给服务器。
- 向服务器发送"Done!!!",表示请求发送完成。
- 创建一个BufferedReader对象reader,读取服务器的响应。
- 循环读取响应的每一行,直到读到"Done!!!"为止。
- 每读取一行响应,将其打印到控制台。
总结:该POST()方法发送了一个HTTP POST请求到服务器,包括请求行、请求头和请求体。通过PrintWriter发送请求给服务器,并使用BufferedReader接收并打印服务器的响应。
压力测试
代码语言:javascript复制import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
public class StressTest {
public static void main(String[] args) {
int count=0;
while(true){
try{
new Socket(InetAddress.getLocalHost(),8888);
count ;
} catch (IOException e) {
System.out.println("Maximum capacity: " count);
System.exit(0);
throw new RuntimeException(e);
}
}
}
}
这段代码是一个用于进行压力测试的简单Java程序。它通过创建一个本地主机的Socket连接,循环不断地创建新的Socket连接,直到遇到IOException异常为止。
具体分析如下:
- 导入了java.io.IOException和java.net包,用于处理输入输出和网络相关的操作。
- 定义了一个名为StressTest的公共类。
- 在主函数main中,初始化一个整型变量count为0,用来记录成功创建的Socket连接次数。
- 使用一个无限循环while(true),表示会一直执行以下操作。
- 在try块中,使用InetAddress.getLocalHost()获取本地主机地址,并创建一个Socket对象,通过指定本地主机和端口号8888进行连接。
- 若连接成功,则将count加1。
- 若出现IOException异常(连接失败),则在catch块中打印"Maximum capacity: "后跟上成功连接的次数count,并退出程序。
- 在catch块末尾使用throw语句抛出RuntimeException异常,但由于前面已经执行了System.exit(0),该语句实际上不会被执行到。
总结:这段代码的目的是测试能够同时创建多少个与本地主机的Socket连接,每创建成功一个连接,就将计数器count加1。当出现连接失败时,程序输出成功创建的最大连接数并退出。