【计网实验——prj3】Socket应用编程实验

【计网实验——prj3】Socket应用编程实验

实验要求

实现

使用C语言分别实现最简单的HTTP服务器和HTTP客户端
 服务器监听80端口,收到HTTP请求,解析请求内容,回复HTTP应答

  1. 所请求的文件在服务器程序当前目录中,返回HTTP 200 OK和相应文件
  2. 所请求的文件不在服务器程序当前目录中,返回HTTP 404 File not found

 服务器、客户端只需要支持HTTP Get方法,不需要支持Post等方法
 服务器使用多线程支持多路并发

测试

所实现的HTTP服务器、客户端可以完成上述功能
 使用客户端连续多次获取文件
 同时启动多个客户端进程分别获取文件
 使用客户端请求不存在的文件

实现方案

  给出的代码框架中,echo-server和echo-client已经利用socket API实现了服务器与客户端的绑定、监听、建立连接和数据传输的过程,因此只需要改进原有的代码使得服务器与客户端能够支持HTTP协议的GET方法即可。

GET方法实现设计

客户端给服务器发送一个HTTP请求

  一个HTTP请求报文由请求行(request line)、请求头部(headers)、空行(blank line)和请求数据(request body)4个部分组成。其中请求行中的请求方法为GET。在这里插入图片描述
  因此,客户端程序http-client.c的较echo-client.c需要添加生成请求报文的代码段,通过scanf函数获得用户输入的请求文件路径(URL),将其按照格式包装为请求报文,再利用socket API工具将该报文发送给服务器即可。相关代码如下:

	char *filepath;
        filepath = (char*) malloc(sizeof(char) * 100);
        char recv_file[105] = "recv_";
        printf("enter message : ");
        scanf("%s", filepath);
        strcat(recv_file, filepath);

        bzero(message, sizeof(message));
        bzero(server_reply, sizeof(server_reply));

        //add request head
        strcat(message, "GET /");
        strcat(message, filepath);
        strcat(message, request_head);
        free(filepath);

        printf("send message: %s\n", message);

        // send the request message
        if (send(sock, message, strlen(message), 0) < 0) {
            printf("send failed\n");
            return 1;
        }

  其中,request_head的定义如下:

	char request_head[] = 
	    " HTTP/1.1\r\n"
	    "Accept: */*\r\n"
	    "Accept-Encoding: identity\r\n"
	    "Host: 10.0.0.1\r\n"
	    "Connection: Keep-Alive\r\n\r\n";
服务器端响应HTTP请求

  报文格式中的请求数据不在GET方法中使用,而在POST方法中使用。GET方法中,服务器只需要利用URL定位资源并获取,再返回给客户端即可。返回时使用的格式为HTTP响应报文格式。
在这里插入图片描述
  服务器在获取客户端的请求报文后的第一步是,对请求报文进行解析,获取资源路径,代码中的handle_req_msg函数实现了这一功能:

void *handle_req_msg(char *msg, char *path){
    int i, j;
    //根据请求报文格式特点可知URL在两个空格之间
    for(i = 0; i < strlen(msg) && msg[i] != ' '; i++);
    i++;
    if(msg[i] == '/') i++;
    for(j = 0; i < strlen(msg) && msg[i] != ' '; i++, j++) path[j] = msg[i];
    path[j] = '\0';
}

  获取目标文件路径后,服务器读取文件内容,并将该内容按照响应报文的格式进行包装,返回给客户端,代码设计如下:

void *handle_request(void *cs_in){
    
    char msg[2000];
    int msg_len = 0;
    char *path, *buffer;
    int cs = *(int *)cs_in;

    // receive a message from client
    while ((msg_len = recv(cs, msg, sizeof(msg), 0)) > 0){

        // send the message back to client
        path = (char*) malloc(sizeof(char) * 100);
        handle_req_msg(msg, path);
        printf("%s\npath: %s\n", msg, path);
        FILE *fd = fopen(path, "r");

        char return_msg[1000];
        bzero(return_msg, sizeof(return_msg));
        strcat(return_msg, "HTTP/1.1 ");

        if(fd == NULL){
            strcat(return_msg, "404 File not found\r\n"); 
        }
        else{
            //read target file
            fseek(fd, 0, SEEK_END);
            int flength = ftell(fd);
            char flen_str[20];
            buffer = (char*) malloc(sizeof(char) * flength);
            fseek(fd, 0, 0);
            fread(buffer, flength + 1, sizeof(char), fd);

            //add response head
            strcat(return_msg, "200 OK\r\nContent-Length: ");
            sprintf(flen_str, "%d", flength);
            strcat(return_msg, flen_str);
            strcat(return_msg, "\r\nConnection: Keep-Alive\r\n\r\n");
            strcat(return_msg, buffer);
            strcat(return_msg, "\r\n");
            free(buffer);

            fclose(fd);
        }
        free(path);
        printf("%s", return_msg);
        write(cs, return_msg, strlen(return_msg));
    }
     
    if (msg_len == 0) {
        printf("client disconnected\n");
    }
    else { // msg_len < 0
        perror("recv failed\n");
		return;
    }
    return;
}
一台服务器同时处理多个服务请求

  使用多线程技术实现一台服务器同时处理多个服务请求的功能:

	// use pthread to support multi-request
    struct sockaddr_in client;
    c = sizeof(struct sockaddr_in);
    while(1) {
        if ((cs = accept(s, (struct sockaddr *)&client, (socklen_t *)&c)) < 0) {
            perror("accept failed\n");
            return -1;
        }
        printf("connection accepted\n");
        pthread_t thread;
        if((pthread_create(&thread, NULL, handle_request, &cs)) != 0){
            perror("pthread create failed\n");
            return -1;
        }
        pthread_detach(thread);
    }
客户端得到并保存资源

  获取服务器的响应报文后,对其同样按照响应报文格式进行解析,并将资源内容保存在本地,接收的文件名在原文件名前增加 recv_ ,如下图:
在这里插入图片描述

  这个过程主要通过函数handle_recv_msg实现:

void handle_recv_msg(char *server_reply, FILE *fd){
    int i, flag = 0;
    for(i = 0; i < strlen(server_reply) - 3; i++){
        if(server_reply[i] == '\r' && server_reply[i+1] == '\n' && server_reply[i+2] == '\r' && server_reply[i+3] == '\n'){
            i += 4;
            flag = 1;
            break;
        }
    }
    if(i < strlen(server_reply) && flag){
        fwrite(&server_reply[i], 1, strlen(&server_reply[i]), fd);
    }
    else printf("Saving file failed.\n");
    char *result;
    if ((result = strtok(server_reply, "\r\n")) != NULL) {
        printf("%s\n", result);
    }
}

运行结果

1. 所实现的HTTP服务器、客户端可以完成上述功能

使用客户端连续多次获取文件以及请求不存在的文件

在这里插入图片描述
在这里插入图片描述
  客户端连续两次请求文件1.txt和2.txt,打印报文如上图,其中1.txt的内容是"This is a http server created by zzq.",而文件2.txt不存在,打印出"404 Not Found",符合预期。

同时启动多个客户端进程分别获取文件

  同时打开两个终端启动两个客户端,服务器打印报文如下,运行正常。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 使用“python -m SimpleHTTPServer 80”和wget分别替代服务器和客户端,对端可以正常工作

SimpleHTTPServer替代服务器

  用SimpleHTTPServer替代服务器,客户端连续获取1.txt和2.txt,打印正常,说明客户端可以正常工作。
在这里插入图片描述
在这里插入图片描述

wget替代客户端

  用wget替代客户端,获取1.txt或2.txt,打印正常,说明服务器可以正常工作。
在这里插入图片描述
在这里插入图片描述

遇到的问题及其解决方法

  在实验过程中,通过抓包发现,客户端向 SimpleHTTPServer 发送请求信号后, 如果多次发送请求, SimpleHTTPServer 在第⼀次正常返回响应后,TCP会发出RST信号,导致客户端如果不重新建立连接就会发生异常。
在这里插入图片描述
  因此,如果客户端向 SimpleHTTPServer 连续发包的话,需要在每次发包后重连。在每一次handle_recv_msg之后,添加如下代码:

	close(sock);
        sock = socket(AF_INET, SOCK_STREAM, 0);
        connect(sock, (struct sockaddr *)&server, sizeof(server));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值