互联网编程之传输协议与套接字应用编程

2023-07-30 15:00:53 浏览数 (1)

需求

基于java编程实现一个HTTP服务器程序(20分)和HTTP客户端程序(15分),要求

  1. 采用多线程技术或线程池编程技术处理客户端请求,支持多客户端同时访问;(10分)
  2. 实现GET、HEAD和POST请求,对客户端发送的不同请求给予正确响应;(15分)
  3. 在服务器上放一个静态网站(由HTML文本、图片文件或JS文件等组成),能根据不同请求,返回包括文本和图像2种(及以上)类型的响应,客户端可以正确显示和访问。(10分)
  4. HTTP客户端程序能与该HTTP服务器连接并展示响应结果,正确发送不同类型的请求。(10分)
  5. 能使用cookie编程技术保存和传递会话状态信息,比如保存用户信息等,需要保存的信息可自行决定。(10分)
  6. 对服务器进行性能分析。对服务器进行压力测试,测试可支持多少个客户端同时访问,测试可支持多少个文件同时传输等。(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服务器程序,它使用线程池来处理客户端的请求。

具体分析如下:

  1. 定义了一个名为HttpServer的公共类。
  2. 在主函数main中,创建了一个固定大小为200的线程池ExecutorService,并将其赋值给变量executorService。线程池用于管理并执行多个任务。
  3. 使用try-catch-finally块进行异常处理。
  4. 在try块中,创建一个ServerSocket对象serverSocket,并指定端口号为8888,设置连接请求队列最大长度为10000。
  5. 进入无限循环while (true),表示服务器会一直运行。
  6. 调用serverSocket的accept()方法接受客户端的连接请求,并将返回的Socket对象赋值给变量client。
  7. 使用线程池executorService的execute()方法提交一个新的任务TheadPoolTask来处理客户端的请求。每接受一个客户端连接,就会启动一个新的线程去处理。
  8. 若出现IOException异常,则在catch块中打印异常信息,并抛出RuntimeException异常。
  9. 在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请求的方法。它发送响应头和文件内容给客户端。

具体分析如下:

  1. 这是一个私有方法,参数为path,表示请求的路径。
  2. 在方法中,通过socket获取输出流,创建PrintWriter对象writer,用于向客户端发送响应。
  3. 定义一个字符串response,设置HTTP的响应头,包括协议版本、状态码和Content-Type。
  4. 使用writer的println()方法将响应头和cookie等信息发送给客户端。
  5. 发送"Done!!!"给客户端,表示请求处理完成。
  6. 获取要发送的文件的完整路径,这里使用了固定的路径"C:UsersYeziDesktop互联网编程实验4传输协议与套接字应用编程web"。
  7. 创建File对象file,表示待发送的文件。
  8. 创建输入流InputStream,读取文件内容。
  9. 获取Socket的输出流OutputStream,用于向客户端发送数据。
  10. 使用DataOutputStream的writeLong()方法向客户端发送文件长度。
  11. 通过循环,从输入流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请求的方法。它发送响应头给客户端,但不发送实际数据。

具体分析如下:

  1. 这是一个私有方法,没有参数。
  2. 在方法中,定义了字符串response,设置HTTP的响应头,包括协议版本、状态码和Content-Type。
  3. 通过socket获取输出流,创建PrintWriter对象writer,用于向客户端发送响应。
  4. 使用writer的println()方法将响应头和cookie等信息发送给客户端。
  5. 发送"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请求的方法。它发送响应头给客户端,但不发送实际数据。

具体分析如下:

  1. 这是一个私有方法,没有参数。
  2. 在方法中,定义了字符串response,设置HTTP的响应头,包括协议版本和状态码。
  3. 通过socket获取输出流,创建PrintWriter对象writer,用于向客户端发送响应。
  4. 使用writer的println()方法将响应头和cookie等信息发送给客户端。
  5. 发送"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服务器进行交互。

具体分析如下:

  1. 在main方法中,首先创建了一个客户端Socket对象,并连接到本地主机上的指定端口。
  2. 创建了一个Scanner对象scanner,用于读取用户输入。
  3. 进入循环,直到用户输入"quit"为止。
  4. 用户可以输入"HEAD"、"POST"或其他任意方法进行交互。
    • 如果用户输入"HEAD",调用HEAD()方法处理HEAD请求。
    • 如果用户输入"POST",调用POST()方法处理POST请求。
    • 如果用户输入其他方法,以空格分割输入字符串,然后将第二个部分作为路径参数传递给GET()方法进行处理。
  5. 循环结束后,关闭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请求的方法。

具体分析如下:

  1. 在GET()方法中,接受一个参数path,表示要请求的资源路径。
  2. 构建了一个GET请求的字符串request,包括请求行和请求头。
    • 请求行:使用GET方法访问路径为 "/index.html/" path 的资源,使用HTTP/1.1协议版本。
    • 请求头:指定主机为localhost:8888,设置User-Agent、Accept、Accept-Encoding和Connection等请求头字段。
  3. 创建了一个PrintWriter对象writer,用于向服务器发送请求。
  4. 使用writer的println()方法将请求发送给服务器。
  5. 向服务器发送"Done!!!",表示请求发送完成。
  6. 创建一个BufferedReader对象reader,读取服务器的响应。
  7. 循环读取响应的每一行,直到读到"Done!!!"为止,并将响应打印到控制台。
  8. 创建一个InputStream对象input,用于接收文件内容。
  9. 调用DataInputStream读取输入流中的文件长度信息,保存在变量fileLength中。
  10. 指定文件保存路径savePath。
  11. 创建一个FileOutputStream对象output,用于将接收到的文件内容写入本地文件。
  12. 使用循环读取input中的字节,并通过output将字节写入本地文件。
  13. 关闭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请求的方法。

具体分析如下:

  1. 在HEAD()方法中,定义了一个字符串request,表示HTTP HEAD请求的内容。
    • 请求行:使用HEAD方法访问路径为/index.html的资源,使用HTTP/1.1协议版本。
    • 请求头:指定主机为localhost:8888,设置User-Agent、Accept、Accept-Encoding和Connection等请求头字段。
  2. 创建了一个PrintWriter对象writer,用于向服务器发送请求。
  3. 使用writer的println()方法将请求发送给服务器。
  4. 向服务器发送"Done!!!",表示请求发送完成。
  5. 创建一个BufferedReader对象reader,读取服务器的响应。
  6. 循环读取响应的每一行,直到读到"Done!!!"为止。
  7. 每读取一行响应,将其打印到控制台。

总结:该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请求的方法。

具体分析如下:

  1. 在POST()方法中,定义了一个字符串request,表示HTTP POST请求的内容。
    • 请求行:使用POST方法访问路径为/index.html的资源,使用HTTP/1.1协议版本。
    • 请求头:指定主机为localhost:8888,Content-Type为application/json。
    • 请求体:JSON格式的数据,包含name、email和age字段。
  2. 创建了一个PrintWriter对象writer,用于向服务器发送请求。
  3. 使用writer的println()方法将请求发送给服务器。
  4. 向服务器发送"Done!!!",表示请求发送完成。
  5. 创建一个BufferedReader对象reader,读取服务器的响应。
  6. 循环读取响应的每一行,直到读到"Done!!!"为止。
  7. 每读取一行响应,将其打印到控制台。

总结:该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异常为止。

具体分析如下:

  1. 导入了java.io.IOException和java.net包,用于处理输入输出和网络相关的操作。
  2. 定义了一个名为StressTest的公共类。
  3. 在主函数main中,初始化一个整型变量count为0,用来记录成功创建的Socket连接次数。
  4. 使用一个无限循环while(true),表示会一直执行以下操作。
  5. 在try块中,使用InetAddress.getLocalHost()获取本地主机地址,并创建一个Socket对象,通过指定本地主机和端口号8888进行连接。
  6. 若连接成功,则将count加1。
  7. 若出现IOException异常(连接失败),则在catch块中打印"Maximum capacity: "后跟上成功连接的次数count,并退出程序。
  8. 在catch块末尾使用throw语句抛出RuntimeException异常,但由于前面已经执行了System.exit(0),该语句实际上不会被执行到。

总结:这段代码的目的是测试能够同时创建多少个与本地主机的Socket连接,每创建成功一个连接,就将计数器count加1。当出现连接失败时,程序输出成功创建的最大连接数并退出。

0 人点赞