C-Sprache zum Implementieren eines einfachen Webservers

Apropos Webserver: Das Protokoll, an das die meisten Leute zuerst denken, ist http, dann tcp unter http. In diesem Artikel wird ein einfacher Webserver über tcp implementiert.

Dieser Artikel konzentriert sich auf die Erläuterung der Implementierung. Die Konzepte von http und tcp werden in diesem Artikel nicht erläutert.

1. Verstehen Sie das Funktionsprinzip von Socket- und Webdiensten

Da es sich um einen auf TCP basierenden Webserver handelt, denken viele Freunde, die die Sprache C lernen, möglicherweise bald an Sockets. Socket ist ein relativ abstrakter Kommunikationsprozess oder eine Abstraktion der Informationsinteraktion zwischen dem Host und dem Host. Socket kann Datenströme in das Netzwerk senden und auch Datenströme empfangen.

Die Informationsinteraktion des Sockets ähnelt dem Lesen lokaler Dateiinformationen auf der Oberfläche, aber die Komplexität des Schreibens darin wird von lokalen E / A nicht erreicht, es gibt jedoch Ähnlichkeiten. Die interaktiven Schritte von Socket unter Win sind: WSAStartup-Initialisierung -> Socket-Erstellungs-Socket -> Bindungsbindung -> Listen Listen -> Verbindung verbinden -> Empfangsanforderung akzeptieren -> Senden / Empfangen senden Oder Daten empfangen -> Socket schließen - Socket schließen -> WSACleanup wird endgültig geschlossen.

                                                      

Nachdem Sie die grundlegenden Schritte eines Sockets verstanden haben, werfen wir einen Blick auf die allgemeine Funktionsweise einer grundlegenden Webanforderung durch den Benutzer. Die Operation ist unterteilt in: Öffnen Sie den Browser -> geben Sie die IP-Adresse der Ressourcenadresse ein -> rufen Sie die Ressource ab. Wenn der Zielserver die von der Operation generierte Anforderung empfängt, können wir die Antwortprozessschritte des Servers wie folgt betrachten: Anforderungsanforderung abrufen -> Anforderungsschlüsseldaten abrufen -> Schlüsseldaten abrufen -> Schlüsseldaten senden. Dieser Schritt des Serverprozesses besteht darin, nach dem Starten des Sockets zur Überwachung zu antworten. Wenn Sie wissen, dass die Anforderung durch Überwachung empfangen wird, verwenden Sie recv, um die Anforderungsdaten zu empfangen, um die Ressource gemäß dem Parameter zu erhalten, und geben Sie die Daten schließlich durch Senden zurück.

Zweitens: Erstellen Sie einen Sokect, um die Überwachung abzuschließen

2.1 WSAStartup-Initialisierung

Führen Sie zuerst die Abhängigkeit WinSock2.h in die Header-Datei der Sprache c ein:

#include <WinSock2.h>

Im ersten Punkt wurden die Schritte zur Socket-Erstellung erläutert. Zunächst muss der Socket-Initialisierungsvorgang abgeschlossen werden, und die Funktion WSAStartup wird verwendet. Der Prototyp der Funktion lautet:

int WSAStartup(
  WORD      wVersionRequired,
  LPWSADATA lpWSAData
);

Der Parameter wVersionRequired dieser Funktion stellt die Versionsnummer von WinSock2 dar, der Parameter lpWSAData ist ein Zeiger auf WSADATA, und die WSADATA-Struktur wird für die Informationen verwendet, die nach der Initialisierung von WSAStartup zurückgegeben werden.

wVersionRequired kann mit MAKEWORD generiert werden. Hier können Sie Version 1.1 oder Version 2.2 verwenden. 1.1 unterstützt nur TCP / IP. Version 2.1 bietet mehr Unterstützung. Hier wählen wir Version 1.1.

Deklarieren Sie zuerst eine WSADATA-Struktur:

WSADATA wsaData;

Übergeben Sie dann die Parameter an die Initialisierungsfunktion WSAStartup, um die Initialisierung abzuschließen:

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

WSAStartup gibt einen Wert ungleich Null zurück, wenn die Initialisierung fehlschlägt:

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

2.2 Erstellen Sie einen Socket-Socket

Nach Abschluss der Initialisierung wird der Socket erstellt. Der Socket wird erstellt und verwendet. Der Funktionsprototyp lautet:

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

Im Funktionsprototyp steht af für den IP-Adresstyp, PF_INET für IPV4 und type für den verwendeten Kommunikationstyp. Beispielsweise steht SOCK_STREAM für TCP und protocol für das Übertragungsprotokoll. Bei Verwendung von 0 wird der Standardwert basierend auf den ersten beiden Parametern verwendet.

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

Wenn der Socket nach dem Erstellen -1 ist, bedeutet dies, dass die Erstellung fehlgeschlagen ist. Das Urteil lautet wie folgt:

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

2.3 Bindungsserver

Nach dem Erstellen des Sockets müssen Sie den Server binden, Portinformationen, IP-Adresse usw. konfigurieren. Überprüfen Sie zunächst, welche Parameter die Bindungsfunktion benötigt. Der Funktionsprototyp lautet wie folgt:

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

Der Parameter Socket stellt den gebundenen Socket dar. Übergeben Sie einfach den Socket. Addr ist der Zeiger der Strukturvariablen sockaddr_in. Konfigurieren Sie einige Serverinformationen in der Strukturvariablen sockaddr_in. Addrlen ist der Größenwert von addr.

Kennen Sie die Daten, die wir benötigen, über den Prototyp der Bindungsfunktion und erstellen Sie dann eine Strukturvariable sockaddr_in, um die Serverinformationen zu konfigurieren:

struct sockaddr_in server_addr;

Konfigurieren Sie dann die Adressfamilie auf AF_INET entsprechend TCP / IP:

server_addr.sin_family = AF_INET;

Konfigurieren Sie dann die Portinformationen:

server_addr.sin_port = htons(8080);

Geben Sie dann die IP-Adresse an:

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

Wenn Sie sich über die IP-Adresse nicht sicher sind, können Sie sie manuell eingeben und schließlich das Artefakt-Memset verwenden, um den Speicher zu initialisieren. Der vollständige Code lautet wie folgt:

//配置服务器 
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);

Verwenden Sie dann die Bindungsfunktion, um zu binden und festzustellen, ob die Bindung erfolgreich ist:

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

2.4 auf Überwachung achten

Nach erfolgreicher Bindung wird der Port überwacht. Sehen Sie sich den Prototyp der Listen-Funktion an:

int listen(
 int sockfd, 
 int backlog
)

Im Funktionsprototyp stellt der Parameter sockfd den zu überwachenden Socket dar, und der Rückstand besteht darin, eine Verarbeitung im Kernel festzulegen (hier nicht ausführlich erläutert). Setzen Sie ihn einfach direkt auf 10, und die maximale Obergrenze beträgt 128. Verwenden Sie die Überwachung und stellen Sie fest, ob der Code erfolgreich ist:

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

Der vollständige Code in dieser Phase lautet wie folgt:

#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");
}

Führen Sie den Code aus, um festzustellen, dass der Code keine Fehler enthält, und geben Sie das Abhören aus:

Fügen Sie hier eine Bildbeschreibung ein

2.5 Anfrage abrufen

Nach Abschluss der Überwachung wird die Anforderung erhalten. Einschränkungen müssen Accept verwenden, um eine Verbindung zum Socket herzustellen. Der Prototyp der Accept-Funktion lautet wie folgt:

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

Der Parameter sockfd ist der angegebene Socket, addr ist ein Zeiger auf struct sockaddr, im Allgemeinen die Clientadresse, addrlen wird im Allgemeinen auf sizeof (struct sockaddr_in) gesetzt. Der Code lautet:

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

Beginnen Sie als Nächstes mit der Annahme von Clientanforderungen und verwenden Sie die Funktion recv. Der Funktionsprototyp lautet:

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

Der Parameter sockfd ist die durch accept hergestellte Kommunikation, buf ist der Cache, in dem die Daten gespeichert sind, len ist die Größe des Caches, Flags werden im Allgemeinen auf 0 gesetzt:

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

Zu diesem Zeitpunkt fügen wir der äußeren Ebene von accpt und recv eine Schleife hinzu, um den Prozess wiederholbar zu machen:

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);
  }
 } 

Sie können 127.0.0.1:8080 in den Browser eingeben und sehen, dass der Client das Abhören gedruckt und einen neuen Link erstellt hat:

Wir fügen eine printf-Anweisung hinzu, um Kundenanforderungen anzuzeigen:

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);
 } 

Als nächstes führen wir entsprechende Operationen am Anforderungsheader aus.

2.6 Schreiben der Anforderungsverarbeitungsschicht

Beginnen Sie nach Erhalt der Anforderung mit dem Schreiben der Verarbeitungsschicht. Schreiben Sie den Code ohne Hierarchie weiter auf, schreiben Sie eine Funktion mit dem Namen req, die Funktion empfängt die Anforderungsinformationen und eine hergestellte Verbindung als Parameter:

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

Übergeben Sie dann zuerst den erforderlichen Wert in der while-Schleife:

req(buf, access_skt);

Beginnen Sie dann mit dem Schreiben der Anforderungsfunktion und markieren Sie zuerst das aktuelle Verzeichnis in der Anforderungsfunktion:

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

Trennen Sie dann die Anforderung und die Parameter:

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

Dann markieren wir einige Kopfelemente:

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

Rufen Sie dann die Anforderungsparameter ab. Wenn Sie index.html erhalten, rufen Sie die Datei unter dem aktuellen Pfad ab:

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

Nachdem wir die Datei erhalten haben, ist die Anfrage in Ordnung. Wir geben zuerst den Status 200 zurück:

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

Dann schreibe eine Sendefunktion 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;         
}

Die Funktion der Sendefunktion ist hier nicht schwer zu wiederholen, sondern eine Logik des Durchlaufsendens. Senden Sie dann die http-Antwort und den Dateityp:

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

Nachdem Sie die Beschreibung der angeforderten Datei erhalten haben, müssen Sie die Header-Datei hinzufügen, #include <sys/stat.h>um fstat verwenden zu können, und die erforderlichen Informationen für die verbundene Kommunikation generieren:

//获取文件描述
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);

Zum Schluss Daten senden:

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

Besuchen Sie abschließend die Adresse http://127.0.0.1:8080/index.html, rufen Sie die Datei index.html im aktuellen Verzeichnis ab und rendern Sie sie im Browser:

Alle Codes lauten wie folgt:

#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);
 } 
 
}

Freunde können flexiblere Ressourcentypen, Fehlerbehandlung usw. schreiben, um diese Demo zu vervollständigen.

Ich denke du magst

Origin blog.csdn.net/weixin_41055260/article/details/109378263
Empfohlen
Rangfolge