最简单的Web服务器C/C++实现

一、什么是Web服务器
Web服务器是处理客户端发来的各种请求,然后根据服务器的逻辑结果返回一个结果给客户端。在web服务器和客户端之前的通信是基于http协议的,客户端可以是浏览器也可以是支持http协议的APP。
二、浏览器如何连接web服务器
简单来说需要通过以下3个步骤:
1.浏览器先通过键入URL(统一资源定位符,也叫网络地址)去请求服务器的连接。
2.浏览器和服务器通过三次握手建立连接。
3.服务器响应返回一个结果(html文本)给浏览器,释放TCP连接。
三、代码

1.socket创建一个套接字,函数原型:int socket(int domain, int type, int protocol).

#include <sys/socket.h>

int socket(int domain, int type, int protocol);
// 创建成功返回文件描述符,创建失败返回-1

domain : 套接字中使用的协议族
    PF_INET : IPv4互联网协议族,常用的是这个
    PF_INET6 : IPv6互联网协议族
    PF_LOCAL : 本地通信的UNIX协议族
    PF_PACKET : 底层套接字的协议族
    PF_IPX : IPX Novell协议族

type : 套接字数据传输类型信息,套接字的数据传输方式。
    SOCK_STREAM : 面向连接的套接字,TCP
                可靠的,按序传递的,基于字节的面向连接的数据传输方式的套接字。
    SOCK_DGRAM : UDP
                不可靠的,不按序传递的,以数据的告诉传输为目的的套接字。


protocol : 计算机间通信使用的协议信息
    前两个参数基本确定了协议类型,第三个参数一般传0.
    IPPROTO_TCP : TCP
    IPPROTO_UDP : UDP

2."将sin_addr设置为INADDR_ANY"的含义是什么?
INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
比如一台电脑有3块网卡,分别连接三个网络,那么这台电脑就有3个ip地址了,如果某个应用程序需要监听某个端口,那他要监听哪个网卡地址的端口呢?
如果绑定某个具体的ip地址,你只能监听你所设置的ip地址所在的网卡的端口,其它两块网卡无法监听端口,如果我需要三个网卡都监听,那就需要绑定3个ip,也就等于需要管理3个套接字进行数据交换,这样岂不是很繁琐?
所以出现INADDR_ANY,你只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。

3.完整代码

#include<unistd.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int createlistenfd
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用线程池实现简单Web服务器的代码示例。本示例使用C语言和POSIX线程库。 首先,我们需要创建一个结构体来存储线程池: ```c typedef struct { int num_threads; int max_requests; int listenfd; char* root; threadpool_t* threadpool; } server_t; ``` 其中,`num_threads`表示线程池中的线程数,`max_requests`表示线程池允许的最大请求数,`listenfd`表示服务器监听的套接字,`root`表示Web服务器的根目录,`threadpool`表示线程池。 接下来,我们需要创建一个函数来初始化线程池: ```c void threadpool_init(server_t* server) { server->threadpool = threadpool_create(server->num_threads, server->max_requests, 0); if (server->threadpool == NULL) { fprintf(stderr, "Error creating thread pool\n"); exit(EXIT_FAILURE); } } ``` 该函数使用`threadpool_create`函数创建线程池,并将其存储在`server->threadpool`中。 接下来,我们需要创建一个函数来处理客户端请求: ```c void handle_request(void* arg) { int sockfd = *(int*)arg; free(arg); char buf[1024]; int n = recv(sockfd, buf, sizeof(buf), 0); if (n < 0) { perror("Error receiving request"); close(sockfd); return; } buf[n] = '\0'; char method[32], path[256]; sscanf(buf, "%s %s", method, path); if (strcasecmp(method, "GET") != 0) { perror("Error: unsupported method"); close(sockfd); return; } char full_path[512]; sprintf(full_path, "%s%s", server->root, path); FILE* fp = fopen(full_path, "r"); if (fp == NULL) { perror("Error opening file"); close(sockfd); return; } char response[1024]; sprintf(response, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"); send(sockfd, response, strlen(response), 0); char filebuf[1024]; while ((n = fread(filebuf, 1, sizeof(filebuf), fp)) > 0) { send(sockfd, filebuf, n, 0); } fclose(fp); close(sockfd); } ``` 该函数使用`recv`函数接收客户端的请求,并解析出请求的方法和路径。如果请求的方法不是GET方法,则返回错误。然后,该函数将请求的路径与服务器的根目录拼接成完整的路径,并尝试打开该文件。如果文件打开失败,则返回错误。否则,该函数将HTTP响应头发送给客户端,并将文件的内容发送给客户端。最后,该函数关闭文件和套接字。 接下来,我们需要创建一个函数来接受客户端连接并将其分配到线程池中: ```c void accept_client(server_t* server) { struct sockaddr_in clientaddr; socklen_t clientlen = sizeof(clientaddr); int* connfd = malloc(sizeof(int)); *connfd = accept(server->listenfd, (struct sockaddr*)&clientaddr, &clientlen); if (*connfd < 0) { perror("Error accepting client"); free(connfd); return; } threadpool_add(server->threadpool, handle_request, connfd, 0); } ``` 该函数使用`accept`函数接受客户端连接,并将连接的套接字存储在一个新的整数指针中。然后,该函数使用`threadpool_add`函数将连接分配到线程池中,并指定`handle_request`函数来处理连接。最后,该函数释放分配的整数指针。 最后,我们需要创建一个函数来启动Web服务器: ```c void start_server(server_t* server) { struct sockaddr_in serveraddr; server->listenfd = socket(AF_INET, SOCK_STREAM, 0); if (server->listenfd < 0) { perror("Error creating socket"); exit(EXIT_FAILURE); } bzero((char*)&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(PORT); if (bind(server->listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) { perror("Error binding socket"); exit(EXIT_FAILURE); } if (listen(server->listenfd, LISTENQ) < 0) { perror("Error listening"); exit(EXIT_FAILURE); } for (;;) { accept_client(server); } } ``` 该函数使用`socket`函数创建一个监听套接字,并使用`bind`函数将其绑定到服务器地址。然后,该函数使用`listen`函数开始监听客户端连接。最后,该函数使用一个无限循环来接受客户端连接并将其分配到线程池中。 完整的代码示例可以在下面找到: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include "threadpool.h" #define PORT 8080 #define LISTENQ 1024 typedef struct { int num_threads; int max_requests; int listenfd; char* root; threadpool_t* threadpool; } server_t; void threadpool_init(server_t* server) { server->threadpool = threadpool_create(server->num_threads, server->max_requests, 0); if (server->threadpool == NULL) { fprintf(stderr, "Error creating thread pool\n"); exit(EXIT_FAILURE); } } void handle_request(void* arg) { int sockfd = *(int*)arg; free(arg); char buf[1024]; int n = recv(sockfd, buf, sizeof(buf), 0); if (n < 0) { perror("Error receiving request"); close(sockfd); return; } buf[n] = '\0'; char method[32], path[256]; sscanf(buf, "%s %s", method, path); if (strcasecmp(method, "GET") != 0) { perror("Error: unsupported method"); close(sockfd); return; } char full_path[512]; sprintf(full_path, "%s%s", server->root, path); FILE* fp = fopen(full_path, "r"); if (fp == NULL) { perror("Error opening file"); close(sockfd); return; } char response[1024]; sprintf(response, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"); send(sockfd, response, strlen(response), 0); char filebuf[1024]; while ((n = fread(filebuf, 1, sizeof(filebuf), fp)) > 0) { send(sockfd, filebuf, n, 0); } fclose(fp); close(sockfd); } void accept_client(server_t* server) { struct sockaddr_in clientaddr; socklen_t clientlen = sizeof(clientaddr); int* connfd = malloc(sizeof(int)); *connfd = accept(server->listenfd, (struct sockaddr*)&clientaddr, &clientlen); if (*connfd < 0) { perror("Error accepting client"); free(connfd); return; } threadpool_add(server->threadpool, handle_request, connfd, 0); } void start_server(server_t* server) { struct sockaddr_in serveraddr; server->listenfd = socket(AF_INET, SOCK_STREAM, 0); if (server->listenfd < 0) { perror("Error creating socket"); exit(EXIT_FAILURE); } bzero((char*)&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(PORT); if (bind(server->listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) { perror("Error binding socket"); exit(EXIT_FAILURE); } if (listen(server->listenfd, LISTENQ) < 0) { perror("Error listening"); exit(EXIT_FAILURE); } for (;;) { accept_client(server); } } int main(int argc, char** argv) { server_t server; server.num_threads = 4; server.max_requests = 1000; server.root = "/var/www/html"; threadpool_init(&server); start_server(&server); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值