欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux下用c语言实现一个简单的web服务器

程序员文章站 2022-05-08 23:51:55
...

github链接:c语言web服务器
这个服务器程序能够处理简单的GET请求,能够解析类似/%s或/./%s类的url
因为后期解析url时感觉c语言的原生字符串不太方便,所以自己简单封装了一个字符串库。
头文件:mystring.h

#ifndef MYSTRING_H
#define MYSTRING_H

typedef struct
{
	char *data;
	long length;
	long ptr;
} String;

String initString();
void addChar(String *str, char ch);
void addString(String *dec, char *src, long len);
int isEqual(String src, char *dec);
long getStringSize(String str);
void freeString(String str);
char *toCString(String str);

#endif //MY_STRING

对应的mystring.c

#include <stdlib.h>
#include <string.h>
#include "mystring.h"
#include "stringarray.h"

#define STRINGLENGTH 30

String initString()
{
	String string;
	string.length = STRINGLENGTH;
	string.ptr = 0;
	string.data = (char *)malloc(sizeof(char) * string.length);
	return string;
}

void addChar(String *str, char ch)
{
	if (str->ptr >= str->length)
	{
		str->length *= 2;
		str->data = (char *)realloc(str->data, str->length);
	}
	str->data[str->ptr++] = ch;
}

void addString(String *dec, char *src, long len)
{
	while (len--)
	{
		addChar(dec, *src);
		src++;
	}
}

int isEqual(String src, char *dec)
{
	for (long i = 0; i < src.ptr; i++)
	{
		if (src.data[i] != dec[i])
		{
			return 0;
		}
	}
	return 1;
}

long getStringSize(String str)
{
	return str.ptr;
}

void freeString(String str)
{
	free(str.data);
}

char *toCString(String str)
{
	str.data[str.ptr] = 0;
	return str.data;
}

然后封装了对mystring类型的列表操作
头文件stringarray.h

#ifndef STRINGARRAY_H
#define STRINGARRAY_H

#include "mystring.h"

typedef struct
{
	String *data;
	long length;
	long ptr;
} StringArray;

StringArray initArray();
void pushBack(StringArray *array, String str);
long getArraySize(StringArray array);
String getString(StringArray array, long ptr);
StringArray split(String str, const char *pat);
void freeArray(StringArray array);

#endif //STRINGARRAY_H

对应的stringarray.c

#include <stdlib.h>
#include <stdio.h>
#include "stringarray.h"

#define ARRAYLENGTH 30

StringArray initArray()
{
	StringArray array;
	array.length = ARRAYLENGTH;
	array.data = (String *)malloc(sizeof(String) * array.length);
	array.ptr = 0;
	return array;
}

void pushBack(StringArray *array, String str)
{
	if (array->ptr >= array->length)
	{
		array->length *= 2;
		array->data = (String *)realloc(array->data, sizeof(String) * array->length);
	}
	array->data[array->ptr++] = str;
}

long getArraySize(StringArray array)
{
	return array.ptr;
}

String getString(StringArray array, long ptr)
{
	if (array.ptr <= ptr)
	{
		fprintf(stderr, "StringArray out of index\n");
		exit(1);
	}
	return array.data[ptr];
}

StringArray split(String str, const char *pat)
{
	StringArray array = initArray();
	char *temp = str.data;
	char *ptr;
	long p = 0;

	while ((ptr = strstr(temp, pat)) != NULL)
	{
		String x = initString();
		while (temp != ptr)
		{
			addChar(&x, *temp);
			temp++;
			p++;
		}
		temp += strlen(pat);
		p++;
		pushBack(&array, x);
	}
	String x = initString();
	while (p < str.ptr)
	{
		addChar(&x, *temp);
		temp++;
		p++;
	}
	pushBack(&array, x);
	return array;
}

void freeArray(StringArray array)
{
	free(array.data);
}

最后是main.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include "mystring.h"
#include "stringarray.h"

int server;

void interrupt(int x) //当输入CTRL-C时关闭服务器
{
	if (x == SIGINT)
	{
		shutdown(server, SHUT_RDWR);
		close(server);
		exit(0);
	}
}

String numToStr(long x) //获得将要传输的文件大小并转为字符串类型,以便于写入到http头中
{
	String ans = initString();
	int ptr = 0;
	int data[1000];
	while (x)
	{
		data[ptr++] = x % 10;
		x /= 10;
	}
	for(ptr--; ptr >= 0; ptr--)
	{
		addChar(&ans, (char) (data[ptr] + 0x30));
	}
	return ans;
}

void sender(int client, char *fileName)
{
	String str = initString();
	FILE *fp = fopen(fileName, "rb");
	if (fp) //如果打开文件成功,发送200文件头,否则说明要传输的文件不存在,发送404文件头和404页面
	{
		printf("200\n", fileName);
		char *t = "HTTP/1.0 200 OK\r\n";
		addString(&str, t, strlen(t));
		addString(&str, "Content-Length: ", 16);
		int x;
		long len = 0;
		String temp = initString();
		while ((x = fgetc(fp)) != EOF) //将文件中读到的字符加到temp中并记录文件大小
		{
			len++;
			addChar(&temp, (char) x);
		}
		String l = numToStr(len); //获得将要传输的文件大小并转为字符串类型,以便于写入到http头中
		addString(&str, toCString(l), getStringSize(l)); //向http头中添加文件大小信息
		addString(&str, "\r\n\r\n", 4);
		addString(&str, toCString(temp), getStringSize(temp)); //将要传输的内容写在http头后面
		freeString(l);
		freeString(temp);
		fclose(fp);
	}
	else
	{
		printf("404\n", fileName);
		addString(&str, "HTTP/1.1 404 Not Found\r\n\r\n", 26);
		fp = fopen("404.html", "rb");
		int x;
		while ((x = fgetc(fp)) != EOF)
		{
			addChar(&str, (char) x);
		}
		fclose(fp);
	}
	write(client, toCString(str), str.ptr);
	freeString(str);
}

void *handle(void *arg) //处理请求
{
	int client = *(int *)arg; //获得客户端socket
	String str = initString();
	char buffer[1024];
	read(client, buffer, sizeof(buffer));
	/* 开始解析url信息
	在这里只解析GET信息
	只判断url为/%s和/./%s时的情况,%s为请求的文件
	*/
	addString(&str, buffer, strlen(buffer));
	StringArray array = split(str, "\r\n"); //将http请求头按行分割
	for (long i = 0; i < getArraySize(array); i++)
	{
		if (strstr(toCString(getString(array, i)), "GET")) //请求GET
		{
			String url = getString(split(getString(array, i), " "), 1); //得到url信息
			StringArray arr = split(url, "/");
			printf("get %s ", toCString(url));
			if (getArraySize(arr) == 2) //处理url为/%s的情况
			{
				String fileName = getString(arr, 1);
				sender(client, toCString(fileName));
				freeString(fileName);
			}
			else if (getArraySize(arr) == 3 && isEqual(getString(arr, 1), ".")) //处理url为/./%s的情况
			{
				String fileName = getString(arr, 2);
				sender(client, toCString(fileName));
				freeString(fileName);
			}
			freeString(url);
			freeArray(arr);
		}
	}
	freeArray(array);
	freeString(str);
	shutdown(client, SHUT_RDWR);
	close(client);
	return NULL;
}

int main()
{
	//服务器地址为127.0.0.1:8080
	char *addr = "127.0.0.1";
	int port = 8080;
	signal(SIGINT, interrupt); //接收CTRL-C信号
	server = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in serverAddr;
	memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(port);
	serverAddr.sin_addr.s_addr = inet_addr(addr);
	int x = bind(server, (struct sockaddr *) &serverAddr, sizeof(serverAddr));
	if (x != -1)
	{
		printf("Listen at %s:%d\n", addr, port);
		listen(server, 1000);
		struct sockaddr_in clientAddr;
		socklen_t len = sizeof(clientAddr);
		while (1)
		{
			int client = accept(server, (struct sockaddr *) &clientAddr, &len);
			printf("connected by %s\n", inet_ntoa(clientAddr.sin_addr)); //打印客户端连接
			/* 多线程处理 */
			pthread_t th;
			pthread_create(&th, NULL, handle, &client);
		}
	}
	else
	{
		printf("%s:%d cannot be used\n", addr, port);
	}
	close(server);
	return 0;
}