Lenguaje C para implementar un servidor web simple

Hablando de servidores web, el protocolo en el que la mayoría de la gente piensa primero es http, luego tcp está debajo de http. Este artículo implementará un servidor web simple a través de tcp.

Este artículo se centrará en explicar cómo implementarlo. Los conceptos de http y tcp no se explicarán en este artículo.

1. Comprender el principio de funcionamiento de Socket y servicios web

Dado que es un servidor web basado en tcp, muchos amigos que aprenden el lenguaje C pronto pensarán en sockets. Socket es un proceso de comunicación relativamente abstracto, o una abstracción de la interacción de información entre el anfitrión y el anfitrión. Socket puede enviar flujos de datos a la red y también puede recibir flujos de datos.

La interacción de información de socket es similar a la lectura de información de archivo local en la superficie, pero la complejidad de escribir en ella no tiene comparación con IO local, pero existen similitudes. Los pasos interactivos de socket en win son: WSAStartup inicialización -> socket de creación de socket -> vincular enlace -> escuchar escuchar -> conectar conexión -> aceptar solicitud de recepción -> enviar / recibir envío O recibir datos -> closesocket cerrar socket -> WSACleanup finalmente se cierra.

                                                      

Después de comprender los pasos básicos de un socket, echemos un vistazo al funcionamiento general del usuario de una solicitud web básica. La operación se divide en: abrir el navegador -> ingresar la dirección IP del recurso -> obtener el recurso. Cuando el servidor de destino recibe la solicitud generada por la operación, podemos considerar los pasos del proceso de respuesta del servidor como: obtener solicitud de solicitud -> obtener datos de clave de solicitud -> obtener datos de clave -> enviar datos de clave. Este paso del proceso del servidor es responder después de iniciar el socket para monitorear. Sabiendo que la solicitud se recibe a través de monitoreo, use recv para recibir los datos de la solicitud, de modo que obtenga el recurso de acuerdo con el parámetro, y finalmente devuelva los datos a través de enviar.

Dos, crea sokect para completar el seguimiento

2.1 Inicialización de WSAStartup

Primero introduzca la dependencia WinSock2.h en el archivo de encabezado del lenguaje c:

#include <WinSock2.h>

En el primer punto, se explicaron los pasos de creación del socket. Primero, se debe completar la operación de inicialización del socket, y se usa la función WSAStartup. El prototipo de la función es:

int WSAStartup(
  WORD      wVersionRequired,
  LPWSADATA lpWSAData
);

El parámetro wVersionRequired de esta función representa el número de versión de WinSock2; el parámetro lpWSAData es un puntero a WSADATA y la estructura WSADATA se utiliza para la información devuelta después de que se inicializa WSAStartup.

wVersionRequired se puede generar usando MAKEWORD, aquí puedes usar la versión 1.1 o la versión 2.2, 1.1 solo es compatible con TCP / IP, la versión 2.1 tendrá más soporte, aquí elegimos la versión 1.1.

Primero declare una estructura WSADATA:

WSADATA wsaData;

Luego, pase los parámetros a la función de inicialización WSAStartup para completar la inicialización:

WSAStartup(MAKEWORD(1, 1), &wsaData)

WSAStartup devolverá un valor distinto de cero si falla la inicialización:

if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) 
{
 exit(1);
}

2.2 Crear un socket

Una vez completada la inicialización, se crea el socket. El socket se crea y se utiliza. El prototipo de función es:

SOCKET WSAAPI socket(
  int af,
  int type,
  int protocol
);

En el prototipo de función, af representa el tipo de dirección IP, PF_INET representa IPV4 y type representa qué tipo de comunicación se utiliza. Por ejemplo, SOCK_STREAM representa TCP y protocolo representa el protocolo de transmisión. Si se utiliza 0, se utilizará el valor predeterminado basado en los dos primeros parámetros.

int skt = socket(PF_INET, SOCK_STREAM, 0);

Después de que se crea el socket, si es -1, significa que la creación falló. El juicio es el siguiente:

if (skt == -1) 
{         
 return -1;
}

2.3 Servidor de enlace

Después de crear el socket, debe vincular el servidor, configurar la información del puerto, la dirección IP, etc. Primero verifique qué parámetros son requeridos por la función de vinculación, el prototipo de la función es el siguiente:

int bind(
  SOCKET         socket,
  const sockaddr *addr,
  int            addrlen
);

El parámetro socket representa el socket vinculado, simplemente pase en el socket; addr es el puntero de la variable de estructura sockaddr_in, configure alguna información del servidor en la variable de estructura sockaddr_in; addrlen es el valor de tamaño de addr.

Conozca los datos que necesitamos a través del prototipo de función de vinculación, luego cree una variable de estructura sockaddr_in para configurar la información del servidor:

struct sockaddr_in server_addr;

Luego configure la familia de direcciones en AF_INET correspondiente a TCP / IP:

server_addr.sin_family = AF_INET;

Luego configure la información del puerto:

server_addr.sin_port = htons(8080);

Luego especifique la dirección IP:

server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

Si no está seguro de la dirección IP, puede ingresarla manualmente y finalmente usar el artefacto memset para inicializar la memoria. El código completo es el siguiente:

//配置服务器 
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
memset(&(server_addr.sin_zero), '\0', 8);

Luego, use la función de vinculación para vincular y determinar si la vinculación es exitosa:

//绑定
if (bind(skt, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1) {       
 return -1; 
} 

2.4 escuchar el monitoreo

Una vez que el enlace sea exitoso, se monitoreará el puerto. Ver el prototipo de la función de escucha:

int listen(
 int sockfd, 
 int backlog
)

En el prototipo de la función, el parámetro sockfd representa el socket que se va a monitorear, y el backlog es establecer algún procesamiento en el kernel (no se explica en profundidad aquí). Simplemente configúrelo directamente en 10, y el límite superior máximo es 128. Utilice la supervisión y determine si el código es correcto:

if (listen(skt, 10) == -1 ) {    
 return -1;
}

El código completo en esta etapa es el siguiente:

#include <WinSock2.h>
#include<stdio.h> 
int main(){
 //初始化 
 WSADATA wsaData;
 if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
  exit(1);
 }
 //socket创建 
 int skt = socket(PF_INET, SOCK_STREAM, 0);
 if (skt == -1) {         
  return -1;
 }
 //配置服务器 
 struct sockaddr_in server_addr;
 server_addr.sin_family = AF_INET;
 server_addr.sin_port = htons(8080);
 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
 memset(&(server_addr.sin_zero), '\0', 8);
 //绑定
 if (bind(skt, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1){       
  return -1; 
 } 
 //监听 
 if (listen(skt, 10) == -1 ) {    
  return -1;
 }
 
 printf("Listening ... ...\n");
}

Ejecute el código para saber que el código no tiene errores y escuche la salida:

Inserte la descripción de la imagen aquí

2.5 Obtener solicitud

Una vez finalizada la supervisión, se obtiene la solicitud. Las restricciones deben usar accept para conectarse al socket. El prototipo de la función accept es el siguiente:

int accept(
 int sockfd,
 struct sockaddr *addr,
 socklen_t *addrlen
 );

El parámetro sockfd es el socket especificado; addr es un puntero a struct sockaddr, generalmente la dirección del cliente; addrlen generalmente se establece en sizeof (struct sockaddr_in). El codigo es:

struct sockaddr_in c_skt; 
int s_size=sizeof(struct   sockaddr_in);
int access_skt = accept(skt, (struct sockaddr *)&c_skt, &s_size);

A continuación, comience a aceptar solicitudes de clientes y use la función recv. El prototipo de la función es:

ssize_t recv(
 int sockfd, 
 void *buf, 
 size_t len, 
 int flags
)

El parámetro sockfd es la comunicación establecida por accept; buf es la caché, donde se almacenan los datos; len es el tamaño de la caché; las banderas generalmente se establecen en 0:

//获取数据 
char buf[1024];
if (recv(access_skt, buf, 1024, 0) == -1) {
 exit(1);
}

En este punto, agregamos un bucle a la capa exterior de accpt y recv para que el proceso sea repetible:

while(1){
  //建立连接 
  printf("Listening ... ...\n");
  struct sockaddr_in c_skt; 
  int s_size=sizeof(struct   sockaddr_in);
  int access_skt = accept(skt, (struct sockaddr *)&c_skt, &s_size);
  
  //获取数据 
  char buf[1024];
  if (recv(access_skt, buf, 1024, 0) == -1) {
   exit(1);
  }
 } 

Y puede ingresar 127.0.0.1:8080 en el navegador y verá que el cliente imprimió escuchando y creó un nuevo enlace:

Agregamos una declaración printf para ver las solicitudes de los clientes:

while(1){
  //建立连接 
  printf("Listening ... ...\n");
  struct sockaddr_in c_skt; 
  int s_size=sizeof(struct   sockaddr_in);
  int access_skt = accept(skt, (struct sockaddr *)&c_skt, &s_size);
  
  //获取数据 
  char buf[1024];
  if (recv(access_skt, buf, 1024, 0) == -1) {
   exit(1);
  }
  
  printf("%s",buf);
 } 

A continuación, realizamos las operaciones correspondientes en el encabezado de la solicitud.

2.6 Escribir la capa de procesamiento de solicitudes

Después de recibir la solicitud, comience a escribir la capa de procesamiento. Continúe escribiendo el código sin jerarquía, escriba una función llamada req, la función recibe la información de la solicitud y una conexión establecida como parámetros:

void req(char* buf, int access_socket) 
{
}

Luego, primero pase el valor requerido en el ciclo while:

req(buf, access_skt);

Luego comience a escribir la función req, primero marque el directorio actual en la función req:

char arguments[BUFSIZ];  
strcpy(arguments, "./");

Luego, separe la solicitud y los parámetros:

char command[BUFSIZ];     
sscanf(request, "%s%s", command, arguments+2);

Luego marcamos algunos elementos de la cabeza:

char* extension = "text/html";   
char* content_type = "text/plain";     
char* body_length = "Content-Length: ";

Luego obtenga los parámetros de la solicitud, si obtiene index.html, obtenga el archivo en la ruta actual:

FILE* rfile= fopen(arguments, "rb");

Después de obtener el archivo, la solicitud está bien, primero devolvemos un estado 200:

char* head = "HTTP/1.1 200 OK\r\n";    
int len; 
char ctype[30] = "Content-type:text/html\r\n";   
len = strlen(head);

Luego escribe una función de envío send_:

int send_(int s, char *buf, int *len) 
{
 int total;          
 int bytesleft;                                
 int n;
 total=0;
 bytesleft=*len;
 while(total < *len) 
 {
  n = send(s, buf+total, bytesleft, 0);
  if (n == -1) 
  {
   break;
  }
  total += n;
  bytesleft -= n;
 }
 *len = total;          
 return n==-1?-1:0;         
}

La función de la función de envío no es difícil de repetir aquí, es una lógica de envío transversal. Luego envíe la respuesta http y el tipo de archivo:

send_(send_to, head, &len);
len = strlen(ctype);
send_(send_to, ctype, &len);

Después de obtener la descripción del archivo solicitado, debe agregar el archivo de encabezado para #include <sys/stat.h>usar fstat y generar la información necesaria para la comunicación conectada:

//获取文件描述
struct stat statbuf;
char read_buf[1024];       
char length_buf[20];
fstat(fileno(rfile), &statbuf);
itoa( statbuf.st_size, length_buf, 10 );
send(client_sock, body_length, strlen(body_length), 0);
send(client_sock, length_buf, strlen(length_buf), 0);

send(client_sock, "\n", 1, 0);
send(client_sock, "\r\n", 2, 0);

Finalmente envía datos:

//·数据发送
char read_buf[1024]; 
len = fread(read_buf ,1 , statbuf.st_size, rfile);
if (send_(client_sock, read_buf, &len) == -1) { 
 printf("error!");   
}

Finalmente, visite la dirección http://127.0.0.1:8080/index.html para obtener los datos del archivo index.html en el directorio actual y renderícelo en el navegador:

Todos los códigos son los siguientes:

#include <WinSock2.h>
#include<stdio.h> 
#include <sys/stat.h> 

int send_(int s, char *buf, int *len) {
 int total;          
 int bytesleft;                                
 int n;
 total=0;
 bytesleft=*len;
 while(total < *len) 
 {
  n = send(s, buf+total, bytesleft, 0);
  if (n == -1) 
  {
   break;
  }
  total += n;
  bytesleft -= n;
 }
 *len = total;          
 return n==-1?-1:0;         
}

void req(char* request, int client_sock) {   
 char arguments[BUFSIZ];  
 strcpy(arguments, "./");
 
 char command[BUFSIZ];     
 sscanf(request, "%s%s", command, arguments+2);
 
 char* extension = "text/html";   
 char* content_type = "text/plain";     
 char* body_length = "Content-Length: ";
 
 FILE* rfile= fopen(arguments, "rb");
 

 char* head = "HTTP/1.1 200 OK\r\n";    
 int len; 
 char ctype[30] = "Content-type:text/html\r\n";   
 len = strlen(head);
  
 send_(client_sock, head, &len);
 len = strlen(ctype);
 send_(client_sock, ctype, &len);
 

 struct stat statbuf;
       
 char length_buf[20];
 fstat(fileno(rfile), &statbuf);
 itoa( statbuf.st_size, length_buf, 10 );
 send(client_sock, body_length, strlen(body_length), 0);
 send(client_sock, length_buf, strlen(length_buf), 0);

 send(client_sock, "\n", 1, 0);
 send(client_sock, "\r\n", 2, 0);
 

 char read_buf[1024]; 
 len = fread(read_buf ,1 , statbuf.st_size, rfile);
 if (send_(client_sock, read_buf, &len) == -1) { 
  printf("error!");   
 }
 
 return;
}


int main(){
 WSADATA wsaData;
 if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
  exit(1);
 }

 int skt = socket(PF_INET, SOCK_STREAM, 0);
 if (skt == -1) {         
  return -1;
 }

 struct sockaddr_in server_addr;
 server_addr.sin_family = AF_INET;
 server_addr.sin_port = htons(8080);
 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
 memset(&(server_addr.sin_zero), '\0', 8);

 if (bind(skt, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1) {       
  return -1; 
 } 

 if (listen(skt, 10) == -1 ) {    
  return -1;
 }
 
 while(1){

  printf("Listening ... ...\n");
  struct sockaddr_in c_skt; 
  int s_size=sizeof(struct   sockaddr_in);
  int access_skt = accept(skt, (struct sockaddr *)&c_skt, &s_size);

  char buf[1024];
  if (recv(access_skt, buf, 1024, 0) == -1) {
   exit(1);
  }
  
  req(buf, access_skt);
 } 
 
}

Los amigos pueden escribir tipos de recursos designados más flexibles, manejo de errores, etc. para completar esta demostración.

Supongo que te gusta

Origin blog.csdn.net/weixin_41055260/article/details/109378263
Recomendado
Clasificación