首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >光城归来之C语言开发网站

光城归来之C语言开发网站

作者头像
公众号guangcity
发布于 2019-09-20 09:23:08
发布于 2019-09-20 09:23:08
1.1K00
代码可运行
举报
文章被收录于专栏:光城(guangcity)光城(guangcity)
运行总次数:0
代码可运行

C语言开发网站

0.导语

最近要把防火墙项目做个页面,而底层全部c语言实现,那么就得做个web页面,想了一下,C大法这么厉害,也应该可以的,然后大家就见到了这篇文章。

本篇文章主要讲使用C语言如何开发网站,CGI,Nginx+CGI如何部署等问题。

1.Socket通信

初探网站开发,直接上手熟悉的Socket通信编程,这方面网上资料非常多。以网上一张图片为例:

图片来自:https://www.jianshu.com/p/dd580395bf11

本次实践以Get/Post提交表单为例,学习如何解析Html,后端与前端如何通信,Socket如何使用的问题。

直接先放主函数,然后再从每个函数讲解。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
#include <sys/types.h>
#include <sys/socket.h>

核心函数解析

(1)获取一个socket descriptor

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
获取一个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
代码运行次数:0
运行
AI代码解释
复制
/**
客户端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
代码运行次数:0
运行
AI代码解释
复制
/**
服务器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
代码运行次数:0
运行
AI代码解释
复制
/**
将当前socket转变为可以监听外部连接请求的socket
@params:
    sockfd: 当前socket descriptor
    backlog: 请求队列的最大长度
@returns:
    0 if OK, -1 on error
*/
int listen(int sockfd, int backlog);

开始对上述操作进行封装,上述封装函数如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//监听套接字创建
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
代码运行次数:0
运行
AI代码解释
复制
/**
等待客户端请求到达,注意,成功返回得到的是一个新的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
代码运行次数:0
运行
AI代码解释
复制
while (1){
    int fd = accept(sockfd,NULL,NULL);
}

1.3 处理客户端发来的请求

上述完成后,其实就可以运行代码,通过:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcc -o main main.c 
./main

即可完成socket的web server搭建,而客户端与服务端的更多交互操作,则需要更深入的学习,那么接下来就是来做这方面工作的。

首先来看一下我们的运行结果:

index.html图

post图

info.html图

客户端post前服务端接受数据图

客户端post后服务端接受数据图

该函数完成了如下操作:

分别有两个页面,分别是index.htmlinfo.html

当第一次打开index.html时候,会通过get方式获取相关资源,如下图所示:

我们看到了获取index.html与2.jpg,所以我们看到了index页面信息。

而当我们发送post请求跳转到info.html时,我们会在info.html中看到post后的数据。

接下来就来看代码如何实现:

首先来看服务器端如何获取数据呢(也就是终端打印数据):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
char filename[10]={0};
char method[10]={0};

if(strcmp(method,"GET")==0){

    printf("\n解析出来的文件名是%s\n",filename);
    char mime[40*1024]={0};
    get_filetype(filename,mime);
    char response[40*1024]={0};

    //\r\n\r\n表示换行后有一个空行
    sprintf(response, "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n", 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
代码运行次数:0
运行
AI代码解释
复制
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);
    //\r\n\r\n表示换行后有一个空行
    sprintf(response, "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n", mime);
    int headlen = strlen(response);
    char *postbuffer = strstr(buffer, "username=");
    if(postbuffer){
        // char * after = strchr(buffer, '&');
        printf("------>%s\n",postbuffer);
        // printf("------>%s\n",postbuffer+9);
        char *sexbuffer = strstr(postbuffer, "&sex=");
        char *emailbuffer = strstr(sexbuffer, "&email=");
        printf("email:%s\n",emailbuffer);
        strncpy(username,(const char *)postbuffer,strlen(postbuffer)-strlen(sexbuffer));
        printf("用户名=%s\n",urldecode(username+9));
        strncpy(sex,(const char *)sexbuffer,strlen(sexbuffer)-strlen(emailbuffer));
        printf("性别=%s\n",urldecode(sex));
        printf("邮箱=%s\n",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("%s\n",response);

    // close(filefd);
    printf("\n解析出来的文件名是%s\n",filename);

}

除此之外,上述post后,碰到中文会出现乱码,以%的数据发送了过去,也就是通常的url编码与解码,这里直接用c实现解码函数即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 解码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] = '\0';
    strcpy(url, res);
    return url;
}

最后就会得到post后的含有特殊字符与中文的结果!

2.CGI+Nginx

2.1 概念初探

CGI

通用网关接口Common Gateway Interface/CGI描述了客户端和服务器程序之间传输数据的一种标准,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。CGI 独立于任何语言的,CGI 程序可以用任何脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行。Unix shell script, Python, Ruby, PHP, perl, Tcl,C/C++, 和 Visual Basic 都可以用来编写 CGI 程序。

如下图所示:

FastCGI

快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关接(CGI)的改进,描述了客户端和服务器程序之间传输数据的一种标准。FastCGI致力于减少Web服务器与CGI程式之间互动的开销,从而使服务器可以同时处理更多的Web请求。与为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI进程管理器管理,而不是web服务器。

Nginx

Nginx是异步框架的Web服务器,也可以用作反向代理,负载平衡器 和 HTTP缓存。

Nginx+CGI

nginx 不能直接执行外部可执行程序,并且cgi是接收到请求时才会启动cgi进程,不像fastcgi会在一开就启动好,这样nginx天生是不支持 cgi 的。nginx 虽然不支持cgi,但它支持fastCGI。所以,我们可以考虑使用fastcgi包装来支持 cgi。原理大致如下图所示:pre-fork几个通用的代理fastcgi程序——fastcgi-wrapper,fastcgi-wrapper启动执行cgi然后将cgi的执行结果返回给nginx(fork-and-exec)。

fastcgi-wrapper安装:进入下面地址:

https://github.com/gnosek/fcgiwrap

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
autoreconf -i
./configure
make && make install

启动fastcgi-wrapper

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
spawn-fcgi -f /usr/local/sbin/fcgiwrap -p 9000

nginx源码安装同上(不用执行第一行auto操作)。

安装完后,进入conf目录进行fcgiwrap配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
location ~ ^/cgi-bin/.*$ {
    root /home/light/nginx/; # 填写自己的nginx目录
    #cgi path 
    if (!-f $document_root$fastcgi_script_name) {
        return 404;
    }
    fastcgi_pass 127.0.0.1:9000;
    include fastcgi.conf;
}

在nginx目录下新建一个cgi-bin目录用于放置cgi程序。

编写cgi程序main.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int count = 0;
    printf("Content-type: text/html;charset=utf-8\r\n"
        "\r\n"
        "<title>光城第一次使用CGI!</title>"

        "<h1>光城第一次使用CGI!</h1>"
        "Request number %d running on host <i>%s</i>\n",
        ++count, getenv("SERVER_NAME"));
    return 0;
}

编译程序:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcc -o main main.c

开始部署,移动main到cgi-bin目录:

然后启动nginx:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
./sbin/nginx

打开浏览器:

看到如上页面,成功!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 光城 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++】基础:网络编程介绍与TCP&UDP示例
网络传输模型可以抽象为7个层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
DevFrank
2024/07/24
6200
【C++】基础:网络编程介绍与TCP&UDP示例
Linux网络编程TCP
TCP/IP 协议栈是一系列网络协议(protocol)的总和,是构成网络通信的核心骨架,它定义了电子设备如何连入因特网,以及数据如何在它们之间进行传输。
DeROy
2021/11/16
6K0
Linux网络编程TCP
c语言socket通信
1. 前 言 网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
全栈程序员站长
2022/09/14
1.4K0
C语言实现Socket简单通信
https://www.cnblogs.com/Dukefish/p/9197830.html
C语言中文社区
2022/05/30
7580
【TCP服务器的演变过程】编写第一个TCP服务器:实现一对一的连接通信
在构建网络应用时,常常会接触到各种网络协议,其中TCP(Transmission Control Protocol)作为一种面向连接、可靠的传输层协议,扮演着至关重要的角色。理解TCP协议及其在服务器开发中的应用,是每一位网络编程初学者的必经之路。
Lion 莱恩呀
2025/05/13
2000
【TCP服务器的演变过程】编写第一个TCP服务器:实现一对一的连接通信
【Linux/unix网络编程】之使用socket进行TCP编程
循环接收客户发来的信息并在终端上显示,同时在信息前加入序号并返回给客户端;当从客户接收到bye后不再发送给各户并退出程序。
马三小伙儿
2018/09/12
5990
多路I/O转接服务器
多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。
mindtechnist
2024/08/08
2640
多路I/O转接服务器
C语言网络编程:从入门到精通,一篇文章彻底搞懂
Socket接口主要工作在传输层和网络层,屏蔽了底层硬件和路由细节,让开发者可直接通过“端口+IP”定位目标进程。
C语言中文社区
2025/07/17
2820
C语言网络编程:从入门到精通,一篇文章彻底搞懂
1-UNIX网络编程-Socket套接字编程简介
触发学习UNIX网络编程的动力在于前段时间需要开发一个接入服务,需要考虑比较高的并发处理能力,且尽量少占用的机器资源,选用了JAVA的Netty框架,学习过程产生不少疑问,限于基础知识太薄弱无法理解原理,所以开始关注UNIX编程。
zoujunjie202
2022/07/27
1.4K0
1-UNIX网络编程-Socket套接字编程简介
SOCKET网络编程 (通俗易懂入门篇)
struct sockaddr :很多网络编程函数的出现早于IPV4协议,为了向前兼容,现在sockaddr都退化成(void *)结构了。 传递一个地址给函数,然后由函数内部再强制类型转换为所需的地址类型。
看、未来
2020/08/25
1.3K0
SOCKET网络编程 (通俗易懂入门篇)
内网穿透 TCP打洞 【c语言实现】
上篇文章中做了UDP打洞,这篇当然就会是TCP打洞了,两个处于不同内网的两台机器如何通过TCP/IP协议进行链接通讯呢?这其实跟UDP打洞差不多,基本步骤是这个样子的。 假设我们有两台处于不同内网的两台机器A和B和一台众所周知外网IP的服务器S,而机器A中运行着通讯的服务端程序B运行着通讯的客户端程序,那么
战神伽罗
2019/07/24
4.4K0
UDP套接口编程
常用的UDP实现的程序:DNS域名系统,NFS网络文件系统,SNMP简单网络管理协议 ssize_t recvfrom(int sockfd,void *buff,size_t nbytes,int flags,struct sockaddr * from,socklen_t *addrlen); ssize_t sendto(int sockfd,void *buff,size_t nbytes,int flags,struct sockaddr * to,socklen_t addrlen); sock
用户1154259
2018/01/17
1K0
Socket编程实践(2) Socket API 与 简单例程
在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程。该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端。 socket()函数 socket()函数用于创建一个套接字。这就好像购买了一个电话。不过该电话还没有分配号码。 #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol
Tencent JCoder
2018/07/02
9230
Linux下网络编程-UDP协议探测在线好友
UDP协议 相对TCP协议来讲属于不可靠协议,UDP协议是广播方式发送数据,没有服务器和客户端的概念。
DS小龙哥
2022/05/11
2.3K0
Linux下网络编程-UDP协议探测在线好友
【LINUX/UNIX网络编程】之简单多线程服务器(多人群聊系统)
RT,Linux下使用c实现的多线程服务器。这个真是简单的不能再简单的了,有写的不好的地方,还希望大神轻拍。(>﹏<)
马三小伙儿
2018/09/12
1.2K0
【LINUX/UNIX网络编程】之简单多线程服务器(多人群聊系统)
Linux下搭建简易的HTTP服务器完成图片显示
这篇文章作为Linux下socket(TCP)网络编程的练习,使用C语言代码搭建一个简单的HTTP服务器,完成与浏览器之间的交互,最终在浏览器上显示一张图片;通过这个例子可以巩固socket里多线程使用,也可以方便学习了解HTTP协议。
DS小龙哥
2022/05/11
1.7K0
Linux下搭建简易的HTTP服务器完成图片显示
网络编程入门_回显服务器
sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别: 程序员不应操作sockaddr,sockaddr是给操作系统用的 程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。 一般的用法为: 程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数
yifei_
2022/11/14
7760
网络编程入门_回显服务器
Linux原始系统api实现两个终端实时聊天
今天这篇文章基本上属于之前上学学习 c 语言的回顾了,要实现一个简单的聊天功能,其实还是需要话费一些代价的,这里面还是涉及到比较多的知识的。比如:
老码小张
2023/12/02
6200
Linux原始系统api实现两个终端实时聊天
C 语言实现一个简单的 web 服务器
说到 web 服务器想必大多数人首先想到的协议是 http,那么 http 之下则是 tcp,本篇文章将通过 tcp 来实现一个简单的 web 服务器。
C语言与CPP编程
2020/10/30
1.4K0
linux 下经典 IO 复用模型 -- epoll 的使用
epoll 是 linux 内核为处理大批量文件描述符而对 poll 进行的改进版本,是 linux 下多路复用 IO 接口 select/poll 的增强版本,显著提高了程序在大量并发连接中只有少量活跃的情况下的CPU利用率。 在获取事件时,它无需遍历整个被侦听描述符集,只要遍历被内核 IO 事件异步唤醒而加入 ready 队列的描述符集合就行了。 epoll 除了提供 select/poll 所提供的 IO 事件的电平触发,还提供了边沿触发,,这样做可以使得用户空间程序有可能缓存 IO 状态,减少 epoll_wait 或 epoll_pwait 的调用,提高程序效率。
用户3147702
2022/06/27
8340
linux 下经典 IO 复用模型 -- epoll 的使用
相关推荐
【C++】基础:网络编程介绍与TCP&UDP示例
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档