TinyHttp学习
TinyHttp学习
纠结做项目中,太难的做不出来,太简单的也不想做(大废物了属于是)。然后找到了这个很经典的小项目,记录学习情况。因为之前基础很差,所以可能会很细,所以有很多比较蠢的知识也会记录下来。
原地址:(https://www.cnblogs.com/Paranoid5/p/16215852.html)
Day1
先看下需要用到的头文件吧。
#include <stdio.h>
#include <sys/socket.h>//提供socket函数以及数据结构
#include <sys/types.h>//数据类型定义
#include <netinet/in.h>//定义数据结构sockaddr_in
#include <arpa/inet.h>//提供IP地址转换函数
#include <unistd.h>//提供通用的文件、目录程序以及进程的函数
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>//多线程操作的函数
#include <sys/wait.h>//进程等待的函数
#include <stdlib.h>
sockaddr_in和sockaddr
中间提到了两个数据结构sockaddr_in和sockaddr。
这是用来处理网络通信地址的
struct sockaddr{
_SOCKADDR_COMMON(sa_);//协议族
char sa_data[14]; //地址+端口号
};
上面是sockaddr,那么sockaddr_in有什么区别呢?
typedef uint32_t in_addr_t;
struct in_addr{
in_addr_t s_addr;//32位IPV4地址,当然能也有IPV6的
};
struct sockaddr_in{
_SOCKADDR_COMMON(sin_);//协议族
in_port_t sin_port;//端口号
struct in_addr sin_addr;//IP地址
//下面是用于填充的0字节(暂时没懂)
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
}
主要来说就是sockaddr_in将地址和端口分开了,这样地址和端口就没有混合在一起。
然后再看看函数内容:
#define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"//定义server名称
void accept_request(int);//处理从套接字上监听到的一个 HTTP 请求
void bad_request(int);//返回给客户端这是个错误请求,400响应码
void cat(int, FILE *);//读取服务器上某个文件写到 socket 套接字
void cannot_execute(int);//处理发生在执行 cgi 程序时出现的错误
void error_die(const char *);//把错误信息写到 perror
void execute_cgi(int, const char *, const char *, const char *);//运行cgi脚本,这个非常重要,涉及动态解析
int get_line(int, char *, int);//读取一行HTTP报文
void headers(int, const char *);//返回HTTP响应头
void not_found(int);//返回找不到请求文件
void serve_file(int, const char *);//调用 cat 把服务器文件内容返回给浏览器。
int startup(u_short *);//开启http服务,包括绑定端口,监听,开启线程处理链接
void unimplemented(int);//返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持
main函数:
int main(){
int server_sock = -1;//服务端socket接口
u_short port = 0;
int client_sock = -1;//已连接socket描述符
struct sockaddr_in = -1;
int client_name_len = sizeof client_name;
pthread_t newthread;
//start server socket
server_sock = startup(&port);
printf("http running on port %d\n",port);
while(true){
//accept client socket
client_sock = accept( server_sock,
(struct sockaddr *)&client_name,
&client_name_len);
if(client_sock == -1) error_die("accept");
if(pthread_create(&newthread,NULL,accept_request,client_sock) != 0) perror("pthread_create");
}
//close server socket
close(server_sock);
return 0;
}
这一部分复习一下http的连接,然后就是学一下C语言的网络编程的函数。
accept函数
函数声明:
int accept(int sockfd,
struct sockaddr* addr,
socklen_t *addrlen);
参数:
sockfd:socket描述字,在listen()后监听连接。
addr(缺省):指向缓冲区,其中接受为连接实体的地址。
addrlen(缺省):指向存有addr地址长度的整型数。
返回值:
成功:返回一个socket描述符,专门用于连接成功的客户端进行通信。
失败:返回-1
功能:
从sockfd等待队列中抽取第一个连接,创建一个与sockfd同类的新的socket。
pthread_create函数
函数声明
int pthread_create(pthread_t *tidp,
const pthread_attr_t *attr,
void *(*start_rtn)(void*),
void *arg);
返回值:
成功:返回0
失败:返回出错编号
参数:
pthread_t *tidp:指向线程标识符的指针
const pthread_attr_t *attr:设置线程属性
void *( * start_rtn)(void *):线程运行函数的起始地址
void *arg:运行函数的参数
功能:
创建线程
day2
实现startup
int startup(u_short *port){
int httpd = 0;
struct sockaddr_in name;
//setting http socket
httpd = socket(PF_INET,SOCK_STREAM,0);
if(httpd == -1) error_die("socket");
memset(&name,0,sizeof (name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
//binding port
if(bind(httpd,(struct sockaddr *)&name,sizeof(name)) < 0) error_die("bind");
//动态随机分配一个端口
if(*port == 0){
int namelen = sizeof (name);
if(getsockname(httpd,(struct sockaddr*)&name,&namelen) == -1) error_die("getsockname");
*port = ntohs(name.sin_port);
}
//listen
if(listen(httpd,5) < 0) error_die("listen");
return httpd;
}
1、在服务端创建一个socket
2、将socket绑定到对应的端口上
3、如果当前指定的端口是0,则动态分配一个端口
4、监听请求
sokcet函数
函数声明
int socket(int domain, int type, int protocol);
参数:
domain:
协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。
AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合
在windows中AF_INET与PF_INET完全一样.
在Unix/Linux系统中,在不同的版本中这两者有微小差别.对于BSD,是AF,对于POSIX是PF.(没看懂)
type:
用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字,tcp)、SOCK_DGRAM(数据包套接字,udp)等。
protocol:
制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
返回值:
成功则返回一个socket,失败返回-1,错误原因存于errno 中。
ps:一个程序最多打开1024个socket。
功能:
创建一个新的socket,也就是向系统申请一个socket资源。
htons(), ntohl(), ntohs(),htons() 函数
这是字节的网络顺序和主机顺序的问题。
下面四种网络字节顺序(NBO)与本地字节顺序(HBO)之间的转换:
htons:"Host to Network Short"
ntohl:"Network to Host Long"
ntohs:"Network to Host Short"
htonl:"Host to Network Long"
bind函数
功能
服务端把用于通信的地址和端口绑定到socket上。
函数声明:
int bind(int sockfd,
const struct sockaddr *addr,socklen_t addrlen);
参数:
sockfd:需要绑定的socket。
addr:存放了服务端用于通信的地址和端口。
addrlen:表示addr结构体的大小。
返回值:
成功则返回0,失败返回-1,错误原因存于errno 中。
如果绑定的地址错误,或端口已被占用,bind函数一定会报错,否则一般不会返回错误。
listen函数
功能
listen函数把主动连接socket变为被动连接的socket,使得这个socket可以接受其它socket的连接请求,从而成为一个服务端的socket。
函数声明
int listen(int sockfd, int backlog);
返回:
0-成功, -1-失败
参数
sockfd:
是已经被bind过的socket。socket函数返回的socket是一个主动连接的socket,在服务端的编程中,程序员希望这个socket可以接受外来的连接请求,也就是被动等待客户端来连接。由于系统默认时认为一个socket是主动连接的,所以需要通过某种方式来告诉系统,程序员通过调用listen函数来完成这件事。
backlog:
这个参数涉及到一些网络的细节,比较麻烦,填5、10都行,一般不超过30。
当调用listen之后,服务端的socket就可以调用accept来接受客户端的连接请求。
返回值:
成功则返回0,失败返回-1,错误原因存于errno 中。
accept_request函数
void accpet_request(int client){
char buf[1024];
int numchars;
char method[255];//存储请求方法
char url[255];
char path[512];
size_t i,j;
struct stat st;
int cgi = 0;
char *query_string = NULL;
//得到请求行的第一行
numchars = get_line(client,buf,sizeof(buf));
i = 0,j = 0;
//得到method数组
while(!ISspace(buf[j]) && (i < sizeof(method) - 1)){
method[i] = buf[i];
i++,j++;
}
method[i] = '\0';
//我们只处理post和get方法
if(strcasecmp(method,"GET") && strcasecmp(method,"GET")){
unimplemented(client);
return ;
}
//post的时候开启cgi
if(strcasecmp(method,"POST") == 0)
cgi = 1;
//读取url
i = 0;
while(ISspace(buf[j]) && (j < sizeof(buf))) j++;
while(ISspace(buf[j]) && (j < sizeof(buf)) && (i < sizeof (url)-1)){
url[i] = buf[j];
i++;j++;
}
url[i] = '\0';
//处理get方法
if(strcasecmp(method,"GET") == 0){
query_string = url;
while((*query_string != '?') && (*query_string != '\0')) query_string++;
//get方法中有'?',开启cgi
if(*query_string == '?'){
cgi = 1;
*query_string = '\0';
query_stirng++;
}
}
sprintf(path,"htdocs%s",url);
if(path[strlen(path)-1] == '/') strcat(path,"index.html");
if(stat(path,&st) == -1) {
//webpage not found
while(numchars > 0 && strcmp("\n",buf)){
numchars = get_line(client,buf,sizeof(buf));
}
not_found(client);
}else{
if((st.st_mode & S_IFMT) == S_IFDIR) strcat(path,"/index.html");
if((st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXOYH)){
cgi = 0;
}
if(!cgi) serve_file(client,path);
else execute_cgi(client,path,method,query_string);
}
close(client);
}
1、调用get_line()函数得到http请求的请求行
2、get_line完后,就是开始解析第一行,判断是GET方法还是POST方法,目前只支持这两种。
如果是POST,还是把cgi置1,表明要运行CGI程序;
如果是GET方法且附带以?开头的参数时,也认为是执行CGI程序
3、拼接获取要访问的url,可以是很常见的/,/index.html等等。
该程序默认为根目录是在htdocs下的,且默认文件是index.html。
另外还判断了给定文件是否有可执权限,如果有,则认为是CGI程序。最后根据变量cgi的值来进行相应选择:读取静态文件或者执行CGI程序返回结果。
4、返回文件内容(serve_file)或执行cgi程序(execute_cgi)
day3
http头部
HTTP的头域包括通用头,请求头,响应头和实体头四个部分。
通用头域(即通用头)
通用头域包含请求和响应消息都支持的头域,通用头域包含Cache-Control、 Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。对通用头域的扩展要求通讯双方都支持此扩展,如果存在不支持的通用头域,一般将会作为实体头域处理。
请求消息(请求头)
请求消息的第一行为下面的格式:
Method(方法) Request-URI(请求的url) HTTP-Version(http版本)
响应消息(响应头)
响应消息的第一行为下面的格式:
HTTP-Version(http版本) Status-Code(响应代码,例如100、200) Reason-Phrase(对于响应代码的描述)
实体消息(实体头和实体)
请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。
实体头域包含关于实体的原信息。
GET和POST方法
GET请求,请求的数据会附加在URL之后,以?分割URL和传输数据,多个参数用&连接。URL的编码格式采用的是ASCII编码,而不是uniclde,即是说所有的非ASCII字符都要编码之后再传输。
POST请求:POST请求会把请求的数据放置在HTTP请求包的包体中。
cgi
一个web服务器主机提供信息服务的标准接口。这是一种根据请求信息动态产生回应内容的技术。服务器可以通过cgi将不同请求启动不同的外部程序,并在程序结束后返回结果给客户端。
数据结构stat和函数stat
struct stat{
dev_t st_dev; /* 文件所在设备的标识 */
ino_t st_ino; /* 文件结点号 */
mode_t st_mode; /* 文件保护模式 */
nlink_t st_nlink; /* 硬连接数 */
uid_t st_uid; /* 文件用户标识 */
gid_t st_gid; /* 文件用户组标识 */
dev_t st_rdev; /* 文件所表示的特殊设备文件的设备标识 */
off_t st_size; /* 总大小,字节为单位 */
blksize_t st_blksize; /* 文件系统的块大小 */
blkcnt_t st_blocks; /* 分配给文件的块的数量,512字节为单元 */
time_t st_atime; /* 最后访问时间 */
time_t st_mtime; /* 最后修改时间 */
time_t st_ctime; /* 最后状态改变时间 */
};
对于数据类型st_mode我们需要一些其他的宏定义来配合。
文件类型标志包括:
S_IFBLK:文件是一个特殊的块设备
S_IFDIR:文件是一个目录
S_IFCHR:文件是一个特殊的字符设备
S_IFIFO:文件是一个FIFO设备
S_IFREG:文件是一个普通文件
S_IFLNK:文件是一个符号链接
其他模式标志包括:
S_ISUID:文件设置了SUID位
S_ISGID:文件设置了SGID位
S_ISVTX:文件设置了sticky位
用于解释st_mode标志的掩码包括:
S_IFMT:文件类型
S_IRWXU:属主的读/写/执行权限,可以分成S_IXUSR, S_IRUSR, S_IWUSR
S_IRWXG:属组的读/写/执行权限,可以分成S_IXGRP, S_IRGRP, S_IWGRP
S_IRWXO:其他用户的读/写/执行权限,可以分为S_IXOTH, S_IROTH, S_IWOTH
还有一些用于帮助确定文件类型的宏定义,这些和上面的宏不一样,这些是带有参数的宏,类似与函数的使用方法:
S_ISBLK:测试是否是特殊的块设备文件
S_ISCHR:测试是否是特殊的字符设备文件
S_ISDIR:测试是否是目录
S_ISFIFO:测试是否是FIFO设备
S_ISREG:测试是否是普通文件
S_ISLNK:测试是否是符号链接
S_ISSOCK:测试是否是socket
stat()函数:获取文件状态
定义函数
int stat(const char * file_name, struct stat *buf);
函数说明:stat()用来将参数file_name 所指的文件状态, 复制到参数buf 所指的结构中。
day 4
execut_cgi函数
void execute_cgi(int client,const char *path,const char *method,const char *query_string){
char buf[1024];
int cgi_output[2];//0,1分别表示读取和写入,下同
int cgi_input[2];
pid_t pid;
int status;
int i;
char c;
int numchars = 1;
int content_length = -1;
buf[0] = 'A';buf[1] = '\0';
//判断是post还是get协议
if(strcasecmp(method,"GET") == 0){//GET
while((numchars > 0) && strcmp("\n",buf)){
numchars = get_line(client,buf,sizeof(buf));
}
}else{//POST,读取请求长度
numchars = getline(client,buf,sizeof(buf));
while((numchars>0 && strcmp("\n",buf))){
buf[15] = '\0';
if(strcasecmp(buf,"Content-Length:") == 0){
content_length = atoi(&(buf[16]));
}
numchars = get_line(client,buf,sizeof(buf));
}
if(content_length == -1){
bad_request(client);
return ;
}
}
// return 200 OK
sprintf(buf,"http/1.0 200 OK\r\n");
send(client,buf,strlen(buf),0);
//建立管道
if(pipe(cgi_output) < 0){
cannot_execute(client);
return ;
}
if(pipe(cgi_input) < 0){
cannot_execute(client);
return ;
}
if((pid = fork()) < 0){
cannot_execute(client);
return ;
}
//creat a child thread to run cgi
if(pid == 0){
char meth_env[255];
char query_env[255];
char length_env[255];
dup2(cgi_output[1],1);
dup2(cgi_input[0],0);
close(cgi_output[0]);
close(cgi_input[1]);
sprintf(meth_env,"REQUEST_METHOD=%s",method);
putenv(meth_env);
if(strcasecmp(method,"GET") == 0){
// 设置query_string的环境变量
sprintf(query_env,"QUERY_STRING=%s",query_string);
putenv(query_env);
}else{
// 设置content_length的环境变量
sprintf(length_env,"CONTENT_LENGTH=%d",content_length);
putenv(length_env);
}
execl(path,path,NULL);
exit(0);
}else{
close(cgi_output[1]);
close(cgi_input[0]);
//子进程所调用的脚本可以从标准输入获得post数据
if(strcasecmp(method,"POST") == 0){
for(i = 0 ;i < content_length;i++){
recv(client,&c,1,0);
write(cgi_input[1],&c,1);
}
}
while(read(cgi_output[0],&c,1)>0){
send(client,&c,1,0);
}
close(cgi_output[0]);
close(cgi_input[1]);
waitpid(pid,&status,0);
}
}
send函数
功能
向一个已经连接的socket发送数据
函数声明:
int send( SOCKET s, const char FAR *buf, int len, int flags );
参数:
s:指定发送端socket描述符
buf:指明一个存放应用程序要发送数据的缓冲区
len:指明实际要发送的数据的字节数
flags:0
返回值
成功:发送数据的总数
失败:SOCKET_ERROR
pipe函数
功能
创建一个管道,实现进程之间的通信
函数声明:
int pipe(int fd[2]);
参数
fd[0]存放管道的读端,fd[1]存放管道的写端。
返回值
若成功,则返回0
若失败,则返回-1,并设置errno
pid_t类型
一个short类型变量,实际上是内核进程表的索引。
fork函数
功能
创建进程
- fork()函数产生了一个和当前进程完全一样的新进程 ,并和当前进程一样从fork()函数里返回。
- 原来一条执行通路(父进程),现在变成两条(父进程 + 子进程)。
返回值
fork()回返回两次:父进程中返回一次,子进程中返回一次,而且,fork()在父进程中返回的值和在子进程中返回的值是不同的。
子进程的fork()返回值是0;
父进程的fork()返回值是新建立的子进程的ID,因为全局变量g_mygbltest的值发生改变,导致主,子进程内存被单独的分开,所以每个的g_mygbltest值也不同;
失败则直接返回-1
dup/dup2函数
函数声明
int dup(int oldfd);
int dup2(int oldfd, int newfd);
当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。
putenv函数
定义函数:
int putenv(const char * string);
函数说明:putenv()用来改变或增加环境变量的内容. 参数string 的格式为name=value, 如果该环境变量原先存在, 则变量内容会依参数string 改变, 否则此参数内容会成为新的环境变量.
返回值:执行成功则返回0, 有错误发生则返回-1.
waitpid(等待子进程中断或结束):
定义函数
pid_t waitpid(pid_t pid,int * status,int options);
函数说明
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用waitpid()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status可以设成NULL。参数pid为欲等待的子进程识别码,其他数值意义如下:
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=-1 等待任何子进程,相当于wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。
返回值 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno。
execl
函数定义
int execl(const char *path, const char *arg, ...)
函数说明 execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
(这个说实话没有太看懂)
recv函数
用来接收远程主机通过套接字sockfd发送来的数据,并把这些数据保存到数组buf中。
函数声明:
int recv(int sockfd, void *buf, int len, int flags)
参数
sockfd:连接的套接字
buf:接收到的数据保存在数组中
len:数组的长度
flags:设置为0
返回值
0:表示执行成功,返回实际接受的字符个数
= 0:另外一端关闭连接
< 0:执行失败
write函数
获取打开文件的指针位置
int write(int handle,void *buf,int len);
参数:
handle :获取文件指针的文件句柄
*buf :要写入的内容
len :要写入的长度
返回值
返回实际写入文件内容的长度
read函数
获取打开文件的指针位置
int write(int fd,void *buf,int len);
参数:
fd :获取文件指针的文件句柄
*buf :要读取的内容
len :要读取的长度
返回值
返回实际读取文件内容的长度
execute_cgi()函数总结(未完成)
在Get请求下:
读出剩余报文请求头内容并丢弃,直到遇到两个换行符,后面再读就是请求数据。
在Post请求下:
读报文请求头部中的content-length字段,判断是否又有效内容,该字段的值为post请求的数据长度。
day 5
错误请求
返回400状态码
void bad_request(int client){
char buf[1024];
sprintf(buf,"HTTP/1.0 400 BAD REQUEST\r\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"Content-type: text/html\r\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"\r\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"<p>Your browser sent a bad request, ");
send(client,buf,sizeof(buf),0);
sprintf(buf,"such as a POST without a Content-Length.\r\n");
send(client,buf,sizeof(buf),0);
}
cat函数
得到文件内容,发送
void cat(int client,FILE *resource){//read file
char buf[1024];
fgets(buf,sizeof(buf),resource);
while(!feof(resource)){
send(client,sizeof(buf),strlen(buf),0);
fgets();
}
}
feof函数
检查流上的文件结束符,结束返回非0值,否则返回0
get_line函数
得到一行数据,只要发现c为\n,就认为是一行结束,如果读到\r,再用MSG_PEEK的方式读入一个字符,如果是\n,从socket用读出
如果是下个字符则不处理,将c置为\n,结束。如果读到的数据为0中断,或者小于0,也视为结束,c置为\n
int get_line(int sock,char *buf,int size){
int i = 0;
char c = '\0';
int n;
while((i < size-1) && (c!='\n')){
n = recv(sock,&c,1,0);
if(n > 0){
if(c == '\r'){
n = recv(sock,&c,1,MSG_PEEK);
if((n > 0) && (c == '\n')) recv(sock,&c,1,0);
else c = '\n';
}
buf[i] = c;
i++;
}else c = '\n';
}
buf[i] = '\0';
return i;
}
serve_file函数
不是CGI,直接读取文件
void serve_file(int client,const char *filename){//将请求的文件发送回浏览器客户端
FILE *resource = NULL;
int numchars = 1;
char buf[1024];
//默认字符?这个没太懂
buf[0] = 'A';buf[1] = '\0';
while((numbers > 0) && strcmp("\n",buf)){
numchars = get_line(client,buf,sizeof(buf));
}
resource = fopen(filename,"r");
if(resource == NULL)not_found(client);
else {
//http head
headers(client,filename);
//send file content
cat(client,resource);
}
fclose(resource);
}
unimplmented函数
//方法没有实现,就返回此信息
void unimplemented(int client){
char buf[1024];
//返回501状态码
sprintf(buf,"HTTP/1.0 501 Method Implemented\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf,SERVER_STRING);
send(client,buf,strlen(buf),0);
sprintf(buf,"Content-Type: text/html\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"<HTML><HEAD><TITLE>Method Not Implemented\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"</TITLE></HEAD>\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"<BODY><P>HTTP request method not supported.\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"</BODY></HTML>\r\n");
send(client,buf,strlen(buf),0);
}
not_found函数
返回404状态码
void not_found(int client){//client not found
char buf[1024];
//返回404
sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), 0);
}
headers函数
返回200状态码
void headers(int client ,const char *filename){
char buf[1024];
(void)filename;
strcpy(buf,"HTTP/1.0 200 OK\r\n");
send(client,buf,strlen(buf),0);
strcpy(buf,SERVER_STRING);
send(client,buf,strlen(buf),0);
strcpy(buf,"Content-Type: text/html\r\n");
send(client,buf,strlen(buf),0);
strcpy(buf,"\r\n");
send(client,buf,strlen(buf),0);
}
cannot_execute函数
void cannot_execute(int client){
//发送500
sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
send(client, buf, strlen(buf), 0);
}
void error_die(const char *sc){
perror(sc);
exit(1);
}
bad_request函数
void bad_request(int client){
char buf[1024];
sprintf(buf,"HTTP/1.0 400 BAD REQUEST\r\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"Content-type: text/html\r\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"\r\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"<p>Your browser sent a bad request, ");
send(client,buf,sizeof(buf),0);
sprintf(buf,"such as a POST without a Content-Length.\r\n");
send(client,buf,sizeof(buf),0);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!