C语言开发网站
0.导语
最近要把防火墙项目做个页面,而底层全部c语言实现,那么就得做个web页面,想了一下,C大法这么厉害,也应该可以的,然后大家就见到了这篇文章。
本篇文章主要讲使用C语言如何开发网站,CGI,Nginx CGI如何部署等问题。
1.Socket通信
初探网站开发,直接上手熟悉的Socket通信编程,这方面网上资料非常多。以网上一张图片为例:
图片来自:https://www.jianshu.com/p/dd580395bf11
本次实践以Get/Post
提交表单为例,学习如何解析Html,后端与前端如何通信,Socket如何使用的问题。
直接先放主函数,然后再从每个函数讲解。
代码语言:javascript复制int main(){
//1.创建监听套接字,返回是套接字描述符
int sockfd = create_listenfd();
int fd;
while (1){
//2.等待客户端响应
int fd = accept(sockfd,NULL,NULL);
//3.处理客户端发来的请求
handle_request(fd);
close(fd);
}
close(sockfd);
}
Socket操作分别为:
- 创建监听套接字,返回是套接字描述符
- 接收客户端发来的请求
- 处理客户端发来的请求
上述便是Socket通信的核心三步骤。
1.1 创建套接字
下面一一来分析上述三步如何写。
代码语言:javascript复制引入相关头文件
#include <sys/types.h>
#include <sys/socket.h>
核心函数解析
(1)获取一个socket descriptor
代码语言:javascript复制/**
获取一个socket descriptor
@params:
domain: 此处固定使用AF_INET
type: 此处固定使用SOCK_STREAM
protocol: 此处固定使用0
@returns:
nonnegative descriptor if OK, -1 on error.
*/
int socket(int domain, int type, int protocol);
(2)客户端socket向服务器发起连接
代码语言:javascript复制/**
客户端socket向服务器发起连接
@params:
sockfd: 发起连接的socket descriptor
serv_addr: 连接的目标地址和端口
addrlen: sizeof(*serv_addr)
@returns:
0 if OK, -1 on error
*/
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
(3)绑定
代码语言:javascript复制/**
服务器socket绑定地址和端口
@params:
sockfd: 当前socket descriptor
my_addr: 指定绑定的本机地址和端口
addrlen: sizeof(*my_addr)
@returns:
0 if OK, -1 on error
*/
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
(4)监听
代码语言:javascript复制/**
将当前socket转变为可以监听外部连接请求的socket
@params:
sockfd: 当前socket descriptor
backlog: 请求队列的最大长度
@returns:
0 if OK, -1 on error
*/
int listen(int sockfd, int backlog);
开始对上述操作进行封装,上述封装函数如下:
代码语言:javascript复制//监听套接字创建
int create_listenfd(){
//创建Tcp连接
int fd = socket(AF_INET,SOCK_STREAM,0);
int option = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(80);
sin.sin_addr.s_addr=INADDR_ANY;
int res = bind(fd,(struct sockaddr *)&sin,sizeof(sin));
if (res==-1){
perror("bind");
}
listen(fd,100);
return fd;
}
1.2 等待客户端请求到达
等待客户端请求到达
代码语言:javascript复制/**
等待客户端请求到达,注意,成功返回得到的是一个新的socket descriptor,
而不是输入参数listenfd。
@params:
listenfd: 当前正在用于监听的socket descriptor
addr: 客户端请求地址(输出参数)
addrlen: 客户端请求地址的长度(输出参数)
@returns:
成功则返回一个非负的connected descriptor,出错则返回-1
*/
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
在main函数中,使用while 1循环来进行等待:
代码语言:javascript复制while (1){
int fd = accept(sockfd,NULL,NULL);
}
1.3 处理客户端发来的请求
上述完成后,其实就可以运行代码,通过:
代码语言:javascript复制gcc -o main main.c
./main
即可完成socket的web server搭建,而客户端与服务端的更多交互操作,则需要更深入的学习,那么接下来就是来做这方面工作的。
首先来看一下我们的运行结果:
index.html图
post图
info.html图
客户端post前服务端接受数据图
客户端post后服务端接受数据图
该函数完成了如下操作:
分别有两个页面,分别是index.html
与info.html
。
当第一次打开index.html
时候,会通过get方式获取相关资源,如下图所示:
我们看到了获取index.html与2.jpg,所以我们看到了index页面信息。
而当我们发送post请求跳转到info.html时,我们会在info.html中看到post后的数据。
接下来就来看代码如何实现:
首先来看服务器端如何获取数据呢(也就是终端打印数据):
代码语言:javascript复制char buffer[40*1024]={0};
int nread=read(fd,buffer,sizeof(buffer));
printf("读到的请求是:n");
printf("%s",buffer);
printf("n--------------------n");
sscanf(buffer,"%s /%s HTTP/1.1",method,filename);
这里直接通过read函数传递一个socket描述符,然后通过read便可获取到当前index.html的数据。
接下来就是get请求:
在上述sscanf
函数中,我们解析出来了文件名与请求方法,然后根据请求方法做判断即可!
打开文件并发送该文件内容给浏览器,浏览器便可以接收到服务器端的响应数据!
代码语言:javascript复制char filename[10]={0};
char method[10]={0};
if(strcmp(method,"GET")==0){
printf("n解析出来的文件名是%sn",filename);
char mime[40*1024]={0};
get_filetype(filename,mime);
char response[40*1024]={0};
//rnrn表示换行后有一个空行
sprintf(response, "HTTP/1.1 200 OKrnContent-Type: %srnrn", mime);
int headlen = strlen(response);
//打开文件,读取内容,构建响应,发回给客户端
int filefd = open(filename,O_RDONLY);
int filelen = read(filefd,response headlen,sizeof(response)-headlen);
//发送响应头 内容
write(fd,response,headlen filelen);
close(filefd);
}
最后就是post请求:
上述我们通过post数据后到了info.html页面,那么这个如何做到的呢,就是通过解析post方法,然后对客户端,也就是浏览器做出响应即可!
代码语言:javascript复制char username[50] = { 0 };
char sex[50] = { 0 };
char email[50] = { 0 };
else if(strcmp(method,"POST")==0){
char response[40*1024]={0};
char mime[40*1024]={0};
get_filetype(filename,mime);
//rnrn表示换行后有一个空行
sprintf(response, "HTTP/1.1 200 OKrnContent-Type: %srnrn", mime);
int headlen = strlen(response);
char *postbuffer = strstr(buffer, "username=");
if(postbuffer){
// char * after = strchr(buffer, '&');
printf("------>%sn",postbuffer);
// printf("------>%sn",postbuffer 9);
char *sexbuffer = strstr(postbuffer, "&sex=");
char *emailbuffer = strstr(sexbuffer, "&email=");
printf("email:%sn",emailbuffer);
strncpy(username,(const char *)postbuffer,strlen(postbuffer)-strlen(sexbuffer));
printf("用户名=%sn",urldecode(username 9));
strncpy(sex,(const char *)sexbuffer,strlen(sexbuffer)-strlen(emailbuffer));
printf("性别=%sn",urldecode(sex));
printf("邮箱=%sn",urldecode(emailbuffer 7));
// char *email = strstr(buffer, "&email=");
// int sex_email = email-sex;
}
char *res[100]={0};
//发送数据给浏览器
sprintf(res,"<!DOCTYPE html><html><head><meta charset="UTF-8"><title>结果</title></head><body><p>POST结果:%s</p></body></html>",urldecode(postbuffer));
sprintf(response,"%s",res);
write(fd,response,headlen strlen(res));
printf("%sn",response);
// close(filefd);
printf("n解析出来的文件名是%sn",filename);
}
除此之外,上述post后,碰到中文会出现乱码,以%
的数据发送了过去,也就是通常的url编码与解码,这里直接用c实现解码函数即可:
// 解码url
char * urldecode(char url[])
{
int i = 0;
int len = strlen(url);
int res_len = 0;
char res[BURSIZE];
for (i = 0; i < len; i)
{
char c = url[i];
if (c != '%')
{
res[res_len ] = c;
}
else
{
char c1 = url[ i];
char c0 = url[ i];
int num = 0;
num = hex2dec(c1) * 16 hex2dec(c0);
res[res_len ] = num;
}
}
res[res_len] = '