Web客户/服务器程序

简介: Web客户/服务器程序
Web客户/服务器程序
简介

WWW(World Wide Web)是存储信息的数据库,它遍布世界各地,并通过超链接连接在一起。

Web是互联网上最重要的TCP/IP应用程序

Web是一个分布式的客户/服务器模型,客户使用浏览器访问Web服务器上的资源,它们之间建立一个或多个TCP连接进行通信,Web服务器的知名端口是80

客户浏览信息时与服务器之间交互所使用的应用层协议是超文本传输协议HTTP(Hypertext Transfer Protocol)

web的组成

最初的Web只提供文本文档,现在可以包含多种格式的内容

Web的主要功能组成:

  • HTML(HyperText Markup Language)是创建Web页面的标准语言,它是标准通用标记语言SGML(Standard Generalized Markup Language)的具体应用。HTML定义了一些特殊的标记(tag),描述了应该如何显示文档的信息。并定义了超链接指令,指定一个文档如何链接到另一个文档或者Web服务器。
  • HTTP(Hypertext Transfer Protocol)是基于TCP/IP的应用层协议,用于处理分布式的超媒体系统,在客户和服务器之间传送HTML和多种不同类型的文档。支持在一个连接上传送多个文件,并且支持鉴权、状态管理(Cookie)、高速缓存等许多高级特征。
  • URI(Uniform Resource Identifier)统一资源标识符,对Web中对象的名字和地址进行统一编码,以便容易地引用和访问这些资源。HTTP协议使用URL来定位网络上的资源,完整的URL格式为:http_URL = “http:” “//” host [ “:” port ] [ abs_path [ "?"query ]]

HTML

HTML允许文档的创建者在该文档中包含到其他文档或资源的链接。

HTML把分散在世界各地的文档和资源有效地组织在一起,大大方便了用户的浏览、访问和使用。

HTML是一个只包含ASCII的文本文件,文本和格式指令都是ASCII。接收到HTML文件的计算机按照格式指令的要求显示文件中的文本。它与普通文本的主要区别是:HTML文档是结构化的,整个文件由一系列的元素组成,按照指定的语言规则在逻辑上组织在一起。

元素是用文本标记描述的,标记以符号<开始,紧接元素名,元素名不区分大小写,后跟与这个元素相关的一些属性,属性是可选的,标记以符号>结束。

< 元素 属性1=“值1” 属性2=“值2” ...>
  内容    
</元素>

HTML文档都在和之间包含着,在中文档又分为两部分:HEAD和BODY。HEAD中是一些描述信息,告诉程序要如何处理这个文档。通常包含文档的标题TITLE,浏览器会把它显示在窗口的顶部。BODY中是文档的实际内容。

HTML常用标记:

HTTP历史

HTTP是HyperText Transfer Protocol的缩写,翻译为“超文本传输协议”。

HTTP/1.0对每次请求-响应,建立并关闭一条连接,定义了完整的消息格式,并规定了在请求和响应中如何使用它们。支持POST方法向服务器提交数据,改变了只能从服务器获取数据的单向操作,从而实现了与服务器的双向交互。

HTTP/1.0的局限性,每次会话都创建一个新的连接,当网页上有多个文档要请求时,频繁地建立和关闭TCP连接,浪费了网络带宽,导致了延迟。HTTP/1.0也缺少缓存、代理机制等。

HTTP1.1新增了很多引人注目的新特性,针对1.0遇到的问题做了重要的改进:

  • 默认是持久连接,客户端可以在一个连接上发送多个请求,减少了TCP连接建立和关闭的时间。
  • 支持请求部分内容,当请求的文件较大时,客户可以发送多次请求,每次获取文件的一部分内容,这可以方便地实现流媒体,或者传输中断后从文件中断处继续传输
  • 支持单主机多个域名,请求中使用的Host可以指定服务器主机,这样一个Web服务器可以有多个域名,而不必每个域名都有独立的IP地址
  • 高速缓存,HTTP1.1包含了许多新的机制使缓存工作得更好,使用缓存减少了服务器的负载,并且客户能得到更快的响应,提高了性能
HTTP通信模型

HTTP协议是无状态的,每次会话都独立处理,开始会话时创建一个TCP连接,结束时释放连接。

通信模型非常简单,是基于客户/服务器架构的请求/响应协议。客户与服务器间的交互全部由请求和响应组成

  • 请求
    由客户端,通常是浏览器软件发起,指定了要从服务器获取的资源(GET方法)或者提交给服务器的资源,如表单数据或上传的文件(POST方法)。请求中的信息包含了协议首部,指明客户端的一些能力信息,如可以处理的媒体类型、支持的语言、编码等。
  • 响应
    收到请求后,服务器解析这个请求,按照客户端的要求,根据服务器上的资源配置,构造响应,并发送给客户端,在响应中带有状态码,告诉客户端本次操作是否成功。

HTTP是一个基于客户/服务器架构的请求/响应协议,它不维护状态信息,简单,易于管理。基本的通信过程是:客户端发送请求,服务器返回应答。

HTTP消息格式

HTTP为客户端的请求和服务器的响应定义了自己的消息格式

HTTP的消息类型只有两种:请求和响应

符合一个通用的消息结构,以起始行开始,然后是各种消息首部,后跟空行和可选的消息体。

消息首部必须是文本,消息体可以是各种编码数据,HTTP中消息体也被称为实体。

  • 请求消息格式
    客户端与服务器通信时,它按照用户的要求及本地软件的处理能力,构造出请求消息。请求消息符合通用消息格式。
  • 响应消息格式
    对每个请求消息,服务器都会发回一个响应消息,把请求的结果告诉客户端。响应消息中通常会带有消息体,是客户端请求的文档或资源
HTTP方法

方法是客户端要求服务器所执行的操作,服务器的行为依赖于请求的对象。

URL是一个文档,服务器返回文件内容;URL为可执行程序时,服务器会执行程序,并返回其执行结果。

方法名称区分大小写,HTTP的方法必须是大写字母。

HTTP/1.1中定义了8个方法,其中GET和HEAD是必须被支持的,其他是可选的

HTTP状态码

对每个请求,服务器都会返回一个响应,响应的第一行是状态行。

状态行包含了支持的协议版本,状态码和原因短语。

状态码可以让机器上的程序自动处理,原因短语是为了方便用户理解。

状态码是由3个数字组成的结果码,第一个数字定义了响应的种类,最后两位没有分类的作用,它们一起提供了更具体信息的说明:

HTTP示例

请求消息中包含了许多首部,大部分都是可选的,只有Host是必须有的

  1. GET请求,只有消息首部,没有实体
    Accept中是客户端能够处理的文档类型
    Accept-Language是支持的语言
    User-Agent是客户端软件的名称及版本说明
    Accept-Encoding是可以处理的编码类型
    Connection: Keep-Alive表示客户端希望使用持久连接。

  2. 响应消息
    第一行是状态行HTTP/1.1200 OK, 说明使用的HTTP协议的版本号,状态码200表示这个请求是成功的,OK起说明的作用。

demo
HTTP服务器程序
  1. 默认的侦听端口是80,可以在命令行中更改为其他的,如果机器上的80端口已经被占用,服务器在bind时会失败
  2. 是一个循环服务器程序,每次处理一个客户的请求,当有多个客户同时到达时,其他的在队列中等待,当前连接处理完后才能处理其他的。
  3. 解析请求的命令行,得到客户端请求的文档,确定该文档的内容类型。然后在当前目录中查找是否有对应的文档,如果有就把文档内容发送给客户端;如果客户端没有指定文档名,如“GET / HTTP1.1”,则默认的文档名为“index.html”

  4. 只支持GET方法,把请求的文档发送给客户端
  5. .即使客户端发送了通用首部“Connection: Keep-Alive”,也不对其进行解析,因此不支持持久连接,处理完客户的请求就关闭与客户端的连接
  6. .服务器程序的命令行参数最多只接受一个,即端口号,由用户指定要侦听的端口,如果没有参数,默认在80端口上侦听。
#include <stdio.h>
#include <string.h> 
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")  /* WinSock使用的库函数 */
#define HTTP_DEF_PORT        8090     /* 连接的默认端口 */
#define HTTP_BUF_SIZE      1024     /* 缓冲区的大小  */
#define HTTP_FILENAME_LEN   256     /* 文件名长度 */
struct doc_type
{
  char *suffix; /* 文件后缀 */
  char *type;   /* Content-Type */
}; 
struct doc_type file_type[] =
{
  {"html",    "text/html"  },
  {"gif",     "image/gif"  },
  {"jpeg",    "image/jpeg" },
  { NULL,      NULL        }  // 结束标志 --> 可以不用知道长度信息 
};
char *http_res_hdr_tmpl = "HTTP/1.1200 OK\r\nServer: Huiyong's Server <0.1>\r\n"
  "Accept-Ranges: bytes\r\nContent-Length: %d\r\nConnection: close\r\n"
  "Content-Type: %s\r\n\r\n";
  
char *http_get_type_by_suffix(const char *suffix)
{
    struct doc_type *type;
    for (type = file_type; type->suffix; type++)
    {
        if (strcmp(type->suffix, suffix) == 0)
            return type->type;
    }
    return NULL;
}
//GET / HTTP/1.1
void http_parse_request_cmd(char *buf, int buflen, char *file_name, char *suffix)
{
    int length = 0;
    char *begin, *end, *bias;
    begin = strchr(buf, ' '); // 查找' '字符 
    begin += 1;  // --> 指向 /
    end = strchr(begin, ' ');
    *end = 0; // 字符串结束标志 --> 使用string.h中的函数,end后面的内容都会被忽视 
    // 此时 begin到end的内容为
  // http://jsuacm.cn/
    bias = strrchr(begin, '/');
    length = end - bias;
    if ((*bias == '/') || (*bias == '\\'))  
    {
        bias++;  // 去掉/
        length = 0;
    }
    /* 得到文件名 */
    if (length > 0)
    {
        memcpy(file_name, bias, length);
        file_name[length] = 0; // 字符串结束标志 
        begin = strchr(file_name, '.');
        if (begin)
            strcpy(suffix, begin + 1);
    }
}
int http_send_response(SOCKET soc, char *buf, int buf_len)
{
    int read_len, file_len, hdr_len, send_len;
    char *type;
    char read_buf[HTTP_BUF_SIZE];
    char http_header[HTTP_BUF_SIZE];
    char file_name[HTTP_FILENAME_LEN] = "index.html", suffix[16] = "html";
    FILE *res_file;
    /* 得到文件名和后缀 */
    http_parse_request_cmd(buf, buf_len, file_name, suffix);
    res_file = fopen(file_name, "rb+"); /* 用二进制格式打开文件 */
    //以二进制模式打开文件,这样才能得到文件的准确长度,如果用“r+”打开,
  //默认为文本方式,文件中的回车换行,不同的系统有不同的处理,
  //计算文件长度时会把换行去掉,用fseek得到文件大小时,长度会不正确。
    if (res_file == NULL)
    {
        printf("[Web] The file [%s] is not existed\n", file_name);
        return 0;
    }
    fseek(res_file, 0, SEEK_END);
    file_len = ftell(res_file);
    fseek(res_file, 0, SEEK_SET);
    type = http_get_type_by_suffix(suffix); /*文件对应的Content-Type */
    if (type == NULL)
    {
        printf("[Web] There is not the related content type\n");
        return 0;
    }
    /* 构造HTTP首部,并发送 */
    hdr_len = sprintf(http_header, http_res_hdr_tmpl, file_len, type);
    send_len = send(soc, http_header, hdr_len, 0);
    if (send_len == SOCKET_ERROR)
    {
        fclose(res_file);
        printf("[Web] Fail to send, error = %d\n", WSAGetLastError());
        return 0;
    }
    do /* 发送文件,HTTP的消息体 */
    {
        read_len = fread(read_buf, sizeof(char), HTTP_BUF_SIZE, res_file);
        if (read_len > 0)
        {
            send_len = send(soc, read_buf, read_len, 0);
            file_len -= read_len;
        }
    } while ((read_len > 0) && (file_len > 0));
    fclose(res_file);
    return 1;
}
int main(int argc, char **argv)
{
    WSADATA wsa_data;
    SOCKET  srv_soc = 0, acpt_soc;  /* socket句柄 */
    struct sockaddr_in serv_addr;   /* 服务器地址  */
    struct sockaddr_in from_addr;   /* 客户端地址  */
    char recv_buf[HTTP_BUF_SIZE];
    unsigned short port = HTTP_DEF_PORT;
    int from_len = sizeof(from_addr);
    int   result = 0, recv_len;
    if (argc == 2) /* 端口号 */
        port = atoi(argv[1]);
    WSAStartup(MAKEWORD(2,0), &wsa_data); /* 初始化WinSock资源 */
    srv_soc = socket(AF_INET, SOCK_STREAM, 0); /* 创建socket */
    if (srv_soc == INVALID_SOCKET)
    {
        printf("[Web] socket() Fails, error = %d\n", WSAGetLastError());
        return -1;
    }
  /* 服务器地址 */
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    result = bind(srv_soc, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
    if (result == SOCKET_ERROR) /* 绑定失败 */
    {
        closesocket(srv_soc);
        printf("[Web] Fail to bind, error = %d\n", WSAGetLastError());
        return -1;
    }
    result = listen(srv_soc, SOMAXCONN);
    printf("[Web] The server is running ... ...\n");
    while (1)
    {
        acpt_soc = accept(srv_soc, (struct sockaddr *)&from_addr, &from_len);
        if (acpt_soc == INVALID_SOCKET) /* 接受失败 */
        {
            printf("[Web] Fail to accept, error = %d\n", WSAGetLastError());
            break;
        }
        printf("[Web] Accepted address:[%s], port:[%d]\n",
            inet_ntoa(from_addr.sin_addr), ntohs(from_addr.sin_port));
        recv_len = recv(acpt_soc, recv_buf, HTTP_BUF_SIZE, 0);
        if (recv_len == SOCKET_ERROR) /* 接收失败 */
        {
            closesocket(acpt_soc);
            printf("[Web] Fail to recv, error = %d\n", WSAGetLastError());
            break;
        }
        recv_buf[recv_len] = 0;
        /* 向客户端发送响应数据 */
        result = http_send_response(acpt_soc, recv_buf, recv_len);
        closesocket(acpt_soc);
    }
    closesocket(srv_soc);
    WSACleanup();
    printf("[Web] The server is stopped.\n");
    return 0;
}
HTTP客户端程序

只实现了GET方法,向用户输入的URL发送请求,把接收到的数据保存在文件中,同时显示在屏幕上。

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")  /* WinSock使用的库函数 */
#define HTTP_DEF_PORT     80  /* 连接的默认端口 */
#define HTTP_BUF_SIZE   1024  /* 缓冲区的大小   */
#define HTTP_HOST_LEN    256  /* 主机名长度 */
char *http_req_hdr_tmpl = "GET %s HTTP/1.1\r\n"
    "Accept: image/gif, image/jpeg, */*\r\nAccept-Language: zh-cn\r\n"
    "Accept-Encoding: gzip, deflate\r\nHost: %s:%d\r\n"
    "User-Agent: Huiyong's Browser <0.1>\r\nConnection: Keep-Alive\r\n\r\n";
    
// http://www.baidu.com:8080/index.html
// 或者 www.baidu.com:8080/index.html 
void http_parse_request_url(char *buf, char *host,
                        unsigned short *port, char *file_name)
{
    int length = 0;
    char port_buf[8];
    char *buf_end = (char *)(buf + strlen(buf));
    char *begin, *host_end, *colon, *file;
    /* 查找主机的开始位置 */
    begin = strstr(buf, "//");
    // 有http:// 和没有http:// 两种情况 
    begin = (begin ? begin + 2 : buf);
    // 端口号 
    colon = strchr(begin, ':');  
    host_end = strchr(begin, '/');
    if (host_end == NULL)
    {
        host_end = buf_end;
    }else
    {   /* 得到文件名 */
        file = strrchr(host_end, '/');
        if (file && (file + 1) != buf_end)
            strcpy(file_name, file + 1);
    }
    if (colon) /* 得到端口号 */
    {
        colon++;
        length = host_end - colon;
        memcpy(port_buf, colon, length);
        port_buf[length] = 0;
        *port = atoi(port_buf);
        host_end = colon -1;
    }
    /* 得到主机信息 */
    length = host_end - begin;
    memcpy(host, begin, length);
    host[length] = 0;
}
int main(int argc, char **argv)
{
    WSADATA wsa_data;
    SOCKET  http_sock = 0;         /* socket句柄 */
    struct sockaddr_in serv_addr;  /* 服务器地址 */
    struct hostent *host_ent;
    int result = 0, send_len;
    char data_buf[HTTP_BUF_SIZE];
    char host[HTTP_HOST_LEN] = "127.0.0.1";
    unsigned short port = HTTP_DEF_PORT;
    unsigned long addr;
    char file_name[HTTP_HOST_LEN] = "index.html";
    FILE *file_web;
    if (argc != 2)
    {
        printf("[Web] input : %s http://www.test.com[:80]/index.html", argv[0]);
        return -1;
    }
    http_parse_request_url(argv[1], host, &port, file_name);
    WSAStartup(MAKEWORD(2,0), &wsa_data); /* 初始化WinSock资源 */
    addr = inet_addr(host);
    if (addr == INADDR_NONE)
    {
        host_ent = gethostbyname(host);
        if (!host_ent)
        {
            printf("[Web] invalid host\n");
            return -1;
        }
        memcpy(&addr, host_ent->h_addr_list[0], host_ent->h_length);
    }
    /* 服务器地址 */
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = addr;
    http_sock = socket(AF_INET, SOCK_STREAM, 0);
    result=connect(http_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if (result == SOCKET_ERROR) /* 连接失败 */
    {
        closesocket(http_sock);
        printf("[Web] fail to connect, error = %d\n", WSAGetLastError());
        return -1;
    }
    /* 发送HTTP请求 */
    send_len = sprintf(data_buf, http_req_hdr_tmpl, argv[1], host, port);
    result = send(http_sock, data_buf, send_len, 0);
    if (result == SOCKET_ERROR) /* 发送失败 */
    {
        printf("[Web] fail to send, error = %d\n", WSAGetLastError());
        return -1;
    }
    file_web = fopen(file_name, "w+");
    do /* 接收响应并保存到文件中 */
    {
        result = recv(http_sock, data_buf, HTTP_BUF_SIZE, 0);
        if (result > 0)
        {
            fwrite(data_buf, 1, result, file_web);
            /* 在屏幕上输出 */
            data_buf[result] = 0;
            printf("%s", data_buf);
        }
    } while(result > 0);
    fclose(file_web);
    closesocket(http_sock);
    WSACleanup();
    return 0;
}
相关文章
|
22天前
|
设计模式 前端开发 数据库
深入理解MVC设计模式:构建高效Web应用程序的基石
【7月更文挑战第4天】在软件工程领域,设计模式是解决常见问题的一系列经过验证的方法。其中,Model-View-Controller(MVC)设计模式自诞生以来,便成为了构建用户界面,特别是Web应用程序的黄金标准。MVC通过将应用程序逻辑分离为三个核心组件,提高了代码的可维护性、可扩展性和重用性。本文将深入探讨MVC设计模式的原理,并通过一个简单的代码示例展示其应用。
45 0
|
7天前
|
弹性计算 数据库 数据安全/隐私保护
阿里云服务器真香宝典之Calibre-Web个人图书馆云端部署
在阿里云ECS(2核2G,SSD40G,3M带宽)上,安装Ubuntu 22.04,然后配置Docker和FTP。创建 `/config` 和 `/books` 目录,设置权限,开放端口,拉取 `johngong/calibre-web` Docker镜像,以`calibre-web`命名容器,映射端口,配置环境变量,挂载卷,确保重启策略。本地安装Calibre客户端,上传metadata.db到服务器。在Calibre-web服务端配置数据库,启用上传权限,修改管理员账户信息。完成配置后,开始上传电子书并进行阅读。
83 2
阿里云服务器真香宝典之Calibre-Web个人图书馆云端部署
|
2天前
|
监控 安全 应用服务中间件
如何搭建高效的Web服务器:技术指南与实践
【7月更文挑战第24天】搭建一个高效的Web服务器需要综合考虑多个方面,包括选择合适的操作系统、安装合适的Web服务器软件、进行配置优化、加强安全防护以及实施性能监控。通过不断地优化和调整,可以确保Web服务器在高负载下仍能保持稳定和高效的运行,为用户提供优质的访问体验。
|
7天前
|
Java Linux 应用服务中间件
Windows和Linux的最佳Web服务器
【7月更文挑战第20天】Windows和Linux的最佳Web服务器
19 3
|
8天前
|
存储 开发框架 安全
如何选择合适的Web服务器?
【7月更文挑战第19天】如何选择合适的Web服务器?
20 2
|
8天前
|
JavaScript Java 应用服务中间件
Web服务器的发展历程?
【7月更文挑战第19天】Web服务器的发展历程?
16 2
|
8天前
|
存储 缓存 监控
Web服务器
【7月更文挑战第19天】Web服务器
15 2
|
18天前
|
缓存 弹性计算 数据库
阿里云2核4G服务器支持多少人在线?程序效率、并发数、内存CPU性能、公网带宽多因素
2核4G云服务器支持的在线人数取决于多种因素:应用效率、并发数、内存、CPU、带宽、数据库性能、缓存策略、CDN和OSS使用,以及用户行为和系统优化。阿里云的ECS u1实例2核4G配置,适合轻量级应用,实际并发量需结合具体业务测试。
5 0
阿里云2核4G服务器支持多少人在线?程序效率、并发数、内存CPU性能、公网带宽多因素
|
13天前
|
网络协议 安全 Python
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
|
21天前
|
前端开发 Java 应用服务中间件
C/S和B/S架构以及Web服务器
C/S和B/S架构以及Web服务器
21 0