用C语言搭建一个基于TCP传输控制协议的HTTP服务器框架

基于TCP协议

HTTP协议解析请求,构造响应

 

用命令行参数来传入参数

一.void   HttpServerStart(const char* ip,short port )来启动服务器

分为如下几步

  1.创建socket

  2.绑定端口号

  3.监听

  4.进入事件循环,当每次有新的连接的时候创建一个新线程,使accept被快速调用到,调用越频繁的话性能越高

  在线程入口函数ThreadEntry()中,对HTTP协议解析请求,构造相应,完成主体的业务逻辑

分为如下步骤

     1.解析请求

  (1)按行读socket读出HTTP请求的首行,分别解析方法,和URL,注意的从socket中读取一行数据,一个一个字符的读,如果当前字符是航分割符

。就认为这一行读完了,而不同的浏览器的航分割符不尽相同,有\n  \r  \r\n   做法是把他们都归结为\n处理,上述函数实现的行为是读出的>一行末尾带\n,int  ReadLine(int new_sock,char  line[])

    (2)解析首行,把空格换成\0.用一组指针记录切分出的每个部分。应切出三个部分。分别为方法,URL,版本号   ParseFirstLine(char first_line[],char ** p_method,char ** p_url)

 (3)解析URL,以问好为分割符左边就是url_path右边就是query_string ,int ParseUrl(char  url[],char **  p_url_path,char **  p_query_string )

   (4) 解析Header,在我的程序里只处理了Centend_length,注意当遇到了\n 说明处理完成

 

     2.构造响应

    (1) 处理页面找不到void  Handler404(int new_sock) 并发出

 (2)处理静态页面 (本质是在url_path中存在的页面,和服务其上的这个路经是匹配的,注意这个路径看起来是一个绝对路径,但其实是一个相

对路径,是相对于HTTP服务器的根路径,把允许对外访问的文件集中放入某个目录下,作为服务器的跟目录,我的服务器使用./wwwroot 作为根目

 录,所以可以看到url_path=>/index.html其实访问的就是./wwwroot/index.html) 并发出int HandlerStaticFile(const   HttpRequest*  req>,int   new_sock)

     ((1))拼接目录(根据url_path构造出当前文件在文件系统上的真实路径,,如果用户传的是一个目录,那么会有这个目录下的默认文件供人访

>问,这时需要用系统提供的API,也就造成了不可跨平台的问题。)GetFilePath (const char*url_path,char   file_path[])

     ((2))打开文件,读取文件内容,根据文件内容构造HTTP响应,将相应内容写入sock中。其中文件的内容就叫做HTTP相应的body部分,这时需要

先把数据从内核到用户(读),又从用户到内核(写),系统开销较大,于是我们用ssize_t    sendfile (int  out_fd,int in_fd,off_t  *offset,size_t  count)来优化,这个函数的操作全都在内核中,这里out_fd必须是socket,当我们发现乱码时大部分都是编解码不一致造成的,只是>可以打开浏览器随意找一个包,把这个包中header部分的编解码信息加入我们要发送的信息中。另外说明的一点是当客户端发起一次静态页面的请

求时,浏览器不一定只发一次请求,如果发现还有链接信息时,或其他路径的信息时,会继续发起请求,比如我的服务器中请求静态页面时就会发

起两次请求,一次显示Hello world,一次显示猫的图片,WriteStaticFile(const  char * file_path,int new_sock) ,写成功返回200,写失

败返回404

(3)处理动态页面  并发出(使用CGI技术.这时一个协议)

    协议的内容是

   ((1))HTTP服务器需要创建子进程

   ((2))子进程进行程序替换到磁盘上的某个可执行程序,这里需要把HTTP的请求方法,query_string(GET) ,body(POST),因为要进行

程序替换,程序替换虽然不改变PCB,但是要替换了代码段数据段,堆栈上内存从新分配,所以前两个要用环境变量出入子进程,而body用匿名管>道传输。最后要把构造好的动态页面通过匿名管道传回子进程。因为管道是单向通信所以要用两个管道,又因为创建管道其实也就是要创建两个变>量,程序替换之后,也就找不到了,但是管道的文件描述符还是在的,为了可以不通过这两个已经访问不到的变量访问文件,我们在进行程序替换

之前,把输入输出定向导管道上。

    也就是说在这一步应做的是如下几点

    ((1))先创建一对匿名管道

    ((2))创建子进程

    ((3))父进程执行父进程的相关逻辑(在最开始关掉不用的文件描述符)

         int HanderCGIFather(const HttpRequest*  req,int new_sock,int father_read,int father_write)

        (((1)))父进程把HTTP请求的body部分写到管道中

        (((2)))父进程尝试读取子进程(进程等待)

        (((3)))父进程构造HTTP响应,写会客户端

           (  (  (4)   )   )父进程回收子进程

     ((4))子进程执行子进程的相关逻辑(在最开始关掉不用的文件描述符)

        (((1)))设定环境变量,只可在子进程中设置(子进程的环境变量相互独立)。

        (((2)))进行重定向,标准输入输出重定向到管道上。

        (((3)))根据url_path构造出CGI程序的路径。

        (((4)))进行程序替换了(一旦进行程序替换,代码就执行到了CGI程序内部,也就是进入了CGI程序的内部)

3.把相应返回到客户端上

 

一般用sprintf ()函数构造字符串

 

这个项目中我将会做一个计算器来测试我的动态页面,其中提交数据是静态页面,返回结果为动态页面

后面是这个自制服务器的缺点;

CGI经典协议的问题在于性能问题。核心在于CGI协议中要求创建子进程。

改进方法,基于线程池的思想出现了一个改进版本FastCGI当前主流的HTTP服务器和应用程序交互的协议之一。

 

 

下面是我的代码,为了让大家看清楚服务器的根路径与虚拟机的根路径,我会把每个源代码文件的路径都加上

/home/a/Desktop/Mycode/http_server/http_server.c


  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <pthread.h>
  4 #include <stdlib.h>
  5 #include <unistd.h>
  6 #include <sys/socket.h>
  7 #include <netinet/in.h>
  8 #include <arpa/inet.h>
  9 #include <sys/stat.h>
 10 #include <fcntl.h>
 11 #include <sys/sendfile.h>
 12 #include <sys/wait.h>
 13 #include "http_server.h"
 14 
 15 
 16 typedef struct   sockaddr sockaddr;
 17 typedef struct   sockaddr_in sockaddr_in ;
 18 
 19 
 20 
 21 //一次从socket中读取以行数据
 22 //把数据放在buf缓冲区中
 23 //如果读取失败,返回值就是-1;
 24 //遇到\n或\r\n或\r证明读取完成
 25 int ReadLine(int  sock,char  buf[],ssize_t  size){
 26         //1.从socket中一次读取一个字符
 27         char  c='\0';
 28         ssize_t   i=0;//当前读了多少个字符
 29         //结束条件;
 30         //a)读到的长度太长,达到了缓冲区的上限
 31         //b)读到了结束标志,并把所有结束标字都转换成\n
 32 
 33         while(i<size-1&&c!='\n'){
 34                 ssize_t  read_size=recv(sock,&c,1,0);
 35                 //读取失败有两种
 36                 if(read_size<0){
 37                         return -1;
 38                 }
 39                 if(read_size==0){
 40                         //因为预期是要读到\n这样的换行符,
 41                         //结果还没有读得到就先读了EOF,这种情况我们也暂时认为是失败的
 42                         return -1;
 43                 }
 44                 if(c=='\r')
 45                 {
 46                         //当前遇到了\r,但还需要确定下一个字符是不是\n
 47                         //MSG_PEEK选项从内核的缓冲区中读取数据,
 48                         //但是读到的数据不会从缓冲区中删除掉。
 49                         recv(sock,&c,1,MSG_PEEK);
 50                         if (c=='\n'){
 51                                 //此时的分隔符就是\r\n
 52                                 recv(sock,&c,1,0);
 53                         }else
 54                         {
 55                                 //当前分隔符确定是\r,此时把分隔符转换成\n
 56                                 c='\n';
 57                         }
 58                 }
 59                 //只要上面的c读到的是\r,那么if结束后,c都变成了\n
 60                 //这种方式就是把前面的\r和\r\n两种情况都统一成了\n
 61                 buf[i++]=c;
 62 
 63         }
 64         buf [i]='\0';
 65         //  printf ("ReadLine\n");
 66         return i;//真正想缓冲区中放置的字符的个数
 67 }
 68 
 69 
 70 int Split(char  input[], const char *split_char,char* output[],int output_size)
 71 {
 72         //使用strtok
 73         int i=0;
 74         char *tmp=NULL;//保存上次的切分结果
 75         char * pch;
 76         //使用线程安全的strtok_r代替strtok
 77         //这一点是以后非常容易出错的一点
 78         pch = strtok_r (input,split_char,&tmp);
 79         while (pch != NULL)
 80         {
 81                 if (i>=output_size)
 82                 {
 83                         return i;
 84                 }
 85                 output[i++]=pch;
 86                 pch = strtok_r (NULL, split_char,&tmp);
 87         }
 88 
 89         //printf ("Split\n");
 90         return i;
 91 
 92 }
 93 
 94 int ParseFirstLine(char  first_line[],char**p_url,char  **p_method)
 95 {
 96         //把首行按空格进行字符串切分
 97         //切分得到的每一个部分,就放在tok数组里
 98         //返回值,就是tok数组包含几个元素。
 99 
100         char  *tok[10];
101         //最后一个参数10表示tok数组中最多可以放几个元素
102         int tok_size=Split(first_line," ",tok,10);
103         if (tok_size!=3)
104         {
105                 printf ("Split  failed!   tok_size =%d\n",tok_size);
106                 return -1;
107         }
108         *p_method=tok[0];
109         *p_url=tok[1];
110 
111         // printf ("ParseFirstLine\n");
112         return 0;
113 }
114 
115 
116 
117 int  ParseQueryString (char  *url,char  **p_url_path,char **p_query_string )
118 {
119         *p_url_path=url;
120         char *p=url;
121         for (;*p!='\0';++p)
122         {
123                 if (*p=='?')
124                 {
125                         *p='\0';
126                         *p_query_string =p+1;
127                         return 0;
128                 }
129         }
130         //循环结束都没有找到?,说明这个请求不带query_string
131         *p_query_string=NULL;
132         // printf ("ParseQueryString\n");
133         return 0;
134 }
135 
136 
137 
138 int    ParseHeader (int sock ,int *content_length)
139 {
140         //1.循环从socket中读取一行。
141         //2.判断当前行是不是Content—Length
142         //3.如果是Content-Length就直接把value读出来
143         //4.如果不是就直接丢弃
144         //5.读到空行,循环结束。
145         char buf [SIZE]={0};
146         while (1)
147         {
148                 //1.循环从socket中读取一行。
149                 ssize_t  read_size=ReadLine (sock,buf,sizeof (buf));
150                 //处理读失败的情况
151                 if (read_size<=0)
152                 {
153                         return -1;
154                 }
155                 //处理读完的情况
156                 if (strcmp (buf ,"\n")==0)
157                 {
158                         return 0;
159                 }
160                 //2.判定当前行是不是Content-Length
161                 //如果是Content-Length就直接把value读出来
162                 //如果不是就直接丢弃
163                 const  char *content_length_str="Content-Length: ";
164                 if (content_length!=NULL&&strncmp(buf,content_length_str,strlen(content_length_str))==0)
165                 {
166                         *content_length=atoi(buf+strlen(content_length_str));
167 
168                 }
169 
170 
171         }
172         return 0;
173         //读到空行循环结束
174 }
175 
176 
177 
178 void Handler404(int sock)
179 {
180         //构造一个完整的HTTP响应
181         //状态码是404
182         //body部分也应该是一个404相关的错误页面。
183         const char*  first_line="HTTP/1.1 404 Not Found\n";
184         const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
185         const char *  blank_line="\n";
186         const char * html="<head><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"></head>"
187                 "<h1>您的页面被喵星人吃掉了!!!</h1>";//提示浏览器按照utf8方式解码
188         send (sock,first_line ,strlen(first_line),0);
189         send (sock,type_line ,strlen(type_line),0);
190         send (sock,blank_line ,strlen(blank_line),0);
191         send (sock,html ,strlen(html),0);
192         return;
193 }
194 
195 
196 
197 void PrintRequest (Request*req)
198 {
199         printf ("method: %s\n",req->method);
200         printf ("url_path: %s\n",req->url_path);
201         printf ("query_string: %s\n",req->query_string);
202         printf ("content_length: %d\n",req->content_length);
203         return;
204 }
205 
206 
207 
208 int IsDir(const char *file_path)
209 {
210         struct stat st;
211         int ret=stat(file_path,&st);
212         if (ret<0)
213         {
214                 return 0;
215         }
216         if (S_ISDIR(st.st_mode))
217         {
218                 return 1;
219         }
220         return 0;
221 }
222 
223 
224 void HandlerFilePath(const  char*url_path,char  file_path[])
225 {
226         //(a,给url_path 加上前缀名(HTTP服务器的根目录)
227         //url_path-->/index.html
228         //file_path--->./wwwroot/index.html
229         sprintf (file_path,"./wwwroot%s",url_path);
230         //b)例如url_path是/,此时url_path 其实就是一个目录。
231         //如果是目录的话,就演给这个目录之中追加一个index.html 
232         //url_path /或者 /image/
233         if (file_path[strlen(file_path)-1]=='/')
234         {
235                 strcat(file_path,"index.html");
236         }
237         //url_path=>/image
238         if (IsDir(file_path))
239         {
240                 strcat (file_path,"/index.html");
241         }
242         return ;
243 
244 }
245 
246 
247 
248 ssize_t  GetFileSize(const char*  file_path)
249 {
250         struct stat  st;
251         int ret=stat(file_path,&st);
252         if (ret<0)
253         {
254                 //打开文件失败,很可能是文件不存在
255                 //此时直接返回文件长度为0
256                 return  0;
257         }
258         return st.st_size;
259 }
260 
261 
262 
263 
264 int WriteStaticFile (int sock,const char*  file_path)
265 {
266         //1.打开文件
267         int fd=open (file_path,O_RDONLY);
268         if (fd<0)
269         {
270                 //文件描述符不够用
271                 //文件不存在(404)不好意思找不到
272                 perror ("open");
273                 return 404;
274         }
275         //2.把构造出来的HTTP响应写到socket之中
276         //1).写入首行
277         //2).写入head
278         //3).写入空行
279         //4).写入body(文件内容)
280         const char *  first_line="HTTP/1.1 200 OK\n";
281         send (sock,first_line,strlen(first_line ),0);
282         //const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
283         //const char*  type_line="Content-Type: image/png;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
284         //send (sock,type_line ,strlen(type_line),0);
285         //两个都不添,让浏览器自己决定
286         const char *  blank_line="\n";
287         send (sock,blank_line ,strlen(blank_line),0);
288         /*ssize_t  file_size=GetFileSize(file_path);
289           ssize_t  i=0;
290           for(;i<file_size;i++)
291           {
292           char c;
293           read(fd,&c,1);
294           send (sock,&c,1,0);
295           }
296           */
297         sendfile(sock,fd,NULL,GetFileSize(file_path));
298         //3.关闭文件
299         close (fd);
300         return 200;
301 }
302 
303 
304 
305 int HandlerStaticFile (int sock ,Request *req)
306 {
307         //1根据url_path获取到文件在服务器上的真实路径。
308         char  file_path[SIZE]={0};
309         HandlerFilePath(req->url_path,file_path);
310         //2。读取文件,把文件的内容直接写道socket之中
311         int err_code=WriteStaticFile (sock,file_path);
312         return err_code;
313 }
314 
315 int    HandlerCGIFather(int new_sock,int   father_read,int father_write,int  child_pid,Request  *req){
316         //1如果是POST请求,就把body写到管道中
317         if (strcasecmp(req->method,"POST")==0){
318                 int i=0;
319                 char  c='\0';
320                 for (;i<req->content_length;++i){
321                         read(new_sock,&c,1);
322                         write(father_write,&c,1);
323                 }
324         }
325         //2构造HTTP响应
326         const char *  first_line="HTTP/1.1 200 OK\n";
327         send (new_sock,first_line,strlen(first_line ),0);
328         const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
329         //const char*  type_line="Content-Type: image/png;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
330         send (new_sock,type_line ,strlen(type_line),0);
331         //两个都可以不添(长度和类型),让浏览器自己决定
332         const char *  blank_line="\n";
333         send (new_sock,blank_line ,strlen(blank_line),0);
334         //循环的从管道中读取数据并写入数据到socket
335         char  c='\0';
336         while (read(father_read,&c,1)>0){
337         //从管道中读数据,如果(父进程,子进程)所有写端关闭,read将会读到EOF,从而返回0
338                 send(new_sock,&c,1,0);
339         }
340         //4回收子进程的资源
341         waitpid(child_pid,NULL,0);
342         return 200;
343 }
344 
345 
346 int   HandlerCGIChild(int child_read,int child_write,Request*  req){
347         //1.设置必要的环境变量
348         char   method_env[SIZE]={0};
349         sprintf (method_env,"REQUEST_METHOD=%s",req->method);
350         putenv(method_env);
351         //还需要设置QUERY_STRING或者是CONTENT_LENGTH
352         if(strcasecmp(req->method,"GET")==0){
353                 char  query_string_env[SIZE]={0};
354                 sprintf (query_string_env,"QUERY_STRING=%s",req->query_string);
355                 putenv(query_string_env);
356 
357         }else {
358                 char content_length_env[SIZE]={0};
359                 sprintf (content_length_env,"CONTENT_LENGTH=%d",req->content_length);
360                 putenv(content_length_env);
361 
362         }
363         //2把标准输入输出重定向到管道里
364         dup2(child_read,0);
365         dup2(child_write,1);
366         //3对子进程进行程序替换
367         //  url_path: /cgi-bin/test
368         //  file_path: ./wwwroot/cgi-bin/test
369         char   file_path[SIZE]={0};
370         HandlerFilePath(req->url_path,file_path);
371 
372         execl(file_path,file_path,NULL);
373         exit(1);//当程序替换失败时。一定让子进程终止,如果子进程不终止,因为父子进程是同一块代码,父进程一直在Listen状态一直等待,子进程也会等待,所以要直接结束进程,避免一直等待端口数据的返回;
374 //有  exec   l    lp  le   v   vp   ve,首先看知不知道可执行文件的完整路径 ,如有就可以不带P,因为P是在PATH中找,再看要不要环境变量,如果不用,就不需要带e,这里因为通过putenv ()的方式直接设到了ENV里面所以不用,说一直接用execl()
375         return 200;
376 }
377 
378 
379 
380 int HandlerCGI(int  new_sock,Request  *req)
381 {
382         int err_code=200;
383         //1.创建一对匿名管道
384         int fd1[2],fd2[2];
385         int ret =pipe(fd1);
386         if (ret<0)
387         {
388                 return 404;
389         }
390         ret=pipe (fd2);
391         if (ret<0)
392         {
393                 close(fd1[0]);
394                 close(fd1[1]);
395                 return 404;
396         }
397         //fd1,fd2这种变量名的描述性太差,后面直接用的话
398         //是非常容易弄混的,所以直接在此处定义几个
399         //更加明确的变量名来描述该文件描述符的用途
400         int   father_read=fd1[0];
401         int   child_write=fd1[1];
402         int   father_write=fd2[1];
403         int   child_read=fd2[0];
404         //2.创建子进程
405         ret=fork();
406         //3.父子进程各执行不同的逻辑
407         if (ret>0){
408 
409                 //father
410                 //此处父进程优先关闭这两个管道的文件描述符
411                 //是为了后续父进程从子进程这里读数据时,能够读到EOF,对于管道来说,所有写端关闭,继续读,才有EOF,而此时所有写端,一方面是父进程需要关闭,另一方面子进程也需要关闭。所以此处父进程先关闭不必要的写端之后,后续子进程用完了
412                 //直接关闭,父进程也就读到了EOF
413                 close (child_read);
414                 close(child_write);
415                 err_code=HandlerCGIFather(new_sock,father_read,father_write,ret,req);
416                 close(father_read);
417                 close(father_write);
418         }else if (ret==0){
419 
420                 //child
421                 close (father_read);
422                 close (father_write);
423                 err_code=HandlerCGIChild(child_read,child_write,req);
424                 //此处代码写上也执行不到,所以不用写close文件描述符
425 
426         }else{
427                 perror("fork");
428                 close (fd1[0]);
429                 close (fd1[1]);
430                 close (fd2[0]);
431                 close (fd2[1]);
432                 return  404;
433         }
434         //4.收尾工作和错误处理
435         return 200;
436 }
437 
438 
439 
440 void  HandlerRequest(int new_sock)
441 {
442         int err_code=200;//错误码初始200,默认没错
443         //1.读取并解析请求(反序列化)
444         Request req;
445         memset (&req,0,sizeof (req));
446         //a)从socket中读取出首行
447         if (ReadLine(new_sock,req.first_line,sizeof (req.first_line))<0)
448         {
449                 //失败处理
450                 err_code =404;
451                 goto END;
452         }
453         //b)解析首行,从首行中解析出url和method
454         if (ParseFirstLine(req.first_line,&req.url,&req.method))
455         {
456                 //失败处理
457                 err_code =404;
458                 goto END;
459 
460         }
461         //c)解析url,从url中解析出url_path和query_string
462         if (ParseQueryString(req.url,&req.url_path,&req.query_string))
463         {
464                 //失败处理
465                 err_code =404;
466                 goto END;
467 
468         }
469         //d)解析Header,丢弃了大部分header,只读取Content—Length
470         if (ParseHeader(new_sock,&req.content_length))
471         {
472                 //失败处理
473                 err_code =404;
474                 goto END;
475 
476         }
477 
478 
479         PrintRequest (&req);
480         //2.静态/动态方式生成页面
481         //3.把生成结果写回客户端上
482         if (strcasecmp(req.method,"GET")==0&&req.query_string==NULL)
483         {
484                 //a)如果是GET方式请求,并且没有query_string,
485                 //那么返回静态页面
486                 err_code=HandlerStaticFile(new_sock,&req);
487         }else  if (strcasecmp(req.method,"GET")==0&&req.query_string!=NULL)
488         {
489                 //b)如果是GET方式请求,并且有query_string,
490                 //那么返回动态页面
491                 err_code=HandlerCGI(new_sock,&req);
492         }else  if (strcasecmp(req.method,"POST")==0)
493         {
494                 //c)如果请求是POST类型的(一定是带参的,参数是通过body来传给服务器的),那么也返回动态页面
495                 err_code=HandlerCGI(new_sock,&req);
496         }else
497         {
498                 //失败处理
499                 err_code =404;
500                 goto END;
501         }
502 
503         //错误处理;直接返回一个404的HTTP响应
504 END:
505         if (err_code !=200)
506         {
507                 Handler404(new_sock);
508         }
509         close(new_sock);
510         return;
511 
512 }
513 
514 
515 
516 
517 void *ThreadEntry(void*arg)//   因为在64位机中指针占8个字节,如果强转回int会有丢失数据的危险
518 {
519         int64_t new_sock=(int64_t )arg;
520         //使用HandlerRequest完成具体的处理请求过程
521         //相当于线程入口函数只有一个包装,真正干活的是这个函数,这样最大的好处还是解耦和
522         //一旦需要把服务器改成多进程或者IO多路复用的形式
523         //代码的改动都是比较小的
524         HandlerRequest(new_sock);
525         return NULL;
526 }
527 
528 
529 
530 
531 //服务器启动
532 void HttpServerStart(const  char*  ip,short   port)
533 {
534         int listen_sock=socket(AF_INET,SOCK_STREAM,0);
535         if (listen_sock<0)
536         {
537                 perror("socket");
538                 return ;
539         }
540         int opt=1;
541         setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof (opt));//加上这个函数是为了使端口处于TIME-WAIT时复用地址
542         sockaddr_in addr;
543         addr.sin_family=AF_INET;
544         addr.sin_addr.s_addr=inet_addr(ip);
545         addr.sin_port=htons(port);
546 
547         int ret=bind(listen_sock,(sockaddr*)&addr,sizeof (addr));
548         if (ret<0)
549         {
550                 perror("bind");
551                 return ;
552         }
553         ret=listen(listen_sock,5);
554         if (ret<0)
555         {
556                 perror("listen");
557                 return ;
558         }
559         printf ("ServerInit  OK\n");
560         while (1)
561         {
562 
563                 sockaddr_in peer;
564                 socklen_t  len=sizeof (peer);
565                 printf ("3\n");
566                 int64_t   new_sock=accept(listen_sock,(sockaddr*)&peer,&len);
567                 printf ("2\n");
568                 if (new_sock<0)
569                 {
570                         perror("accept");
571                         continue;
572                 }
573                 //使用多线程的方式来实现TCP服务器
574                 pthread_t  tid;
575                 printf ("ThreadEntry\n");
576                 pthread_create(&tid,NULL,ThreadEntry,(void *)new_sock);
577                 pthread_detach(tid);
578         }
579 
580 }
581 
582 
583 
584 
585 //主函数的参数   ./http_server  [ip]   [port]
586 int main (int argc,char*   argv[])
587 {
588         if (argc!=3)
589         {
590                 printf ("Usage   ./http_server  [ip]   [port]\n");
591                 return 1;
592         }
593         HttpServerStart( argv[1],atoi(argv[2]));
594         return 0;
595 }



                                                                                                                                                                                                                                                                         









                                                                                                                                                                                                                                                                      

                                                                                                                                                                                                                                                                         


                                                                                                                                                                                        
                                                                                                                                                                               

/home/a/Desktop/Mycode/http_server/http_server.h

  1 #pragma  once
  2 
  3 #define SIZE   10240
  4 //我们需要一个结构体来解析http协议请求
  5 typedef  struct   Request
  6 {
  7 //解析首行
  8 char first_line[SIZE];
  9 char *method;// [SIZE];//方法
 10 char *url;//[SIZE]; //URL
 11 //char  *version;//[SIZE];//版本号,在这里我们暂时用不上
 12 char *url_path;//带层次的文件路径,重点关注的对象内容1
 13 char *query_string;//查询字符串,重点关注的内容2
 14 //接下来解析header部分。
 15 //此处需要使用二叉搜索树或hash表
 16 //这里我们偷懒一下,其他的header都不要了,只保留一个Content——Length
 17 int content_length;
 18 }Request;
 19 /*typedef  struct   Request
 20 {
 21 char first_line[SIZE];//首行
 22 char method [SIZE];//方法
 23 char url[SIZE]; //URL
 24 char version[SIZE];//版本号
 25 char url_path[SIZE];//带层次的文件路径
 26 char query_string[SIZE];//查询字符串
 27 }Request;
 28 */

/home/a/Desktop/Mycode/http_server/Makefile

  1 http_server:http_server.c
  2         gcc  $^  -o  $@  -lpthread
  3 
  4 
  5 .PHONY:clean
  6 clean:
  7         rm  http_server

/home/a/Desktop/Mycode/http_server/wwwroot/index.html

<h1>Hello   world</h1>
<img  src="/image/1.png">

/home/a/Desktop/Mycode/http_server/wwwroot/Makefile  

select:select.c
        gcc  $^  -o  $@  -L  /usr/lib64/mysql  -lmysqlclient
.PHONY:clean
clean:
        rm select

/home/a/Desktop/Mycode/http_server2/wwwroot/calc/calc.c



  1 //实现一个用来简单计算的CGI程序
  2 
  3 
  4 
  5 
  6 
  7 #include <stdio.h>
  8 #include <stdlib.h>
  9 #include <unistd.h>
 10 #include <string.h>
 11 #define  SIZE  (1024*10)
 12 
 13 
 14 void  GetQueryString(char  output[]){
 15         //按照CGI的协议来实现此处的逻辑
 16         //1.先获取到方法
 17         char* method=getenv("REQUEST_METHOD");
 18         if (method==NULL){
 19                 //没有获取到环境变量
 20                 fprintf(stderr,"REQUEST_METHOD  filed\n");
 21                 return ;
 22         }
 23         if (strcmp(method,"GET")==0){
 24                 //获取QUERY_STRING
 25                 char *query_string =getenv("QUERY_STRING");
 26                 if (query_string==NULL){
 27                         fprintf(stderr,"QUERY_STRING  failed\n");
 28                         return ;
 29                 }
 30                 strcpy(output,query_string);
 31         }else{
 32 //post
 33 //获取CONTENT_LENGTH
 34 char*  content_length_env=getenv("CONTENT_LENGTH");
 35 if (content_length_env==NULL){
 36 fprintf(stderr,"CENTENT_LENGTH   failed\n");
 37 return ;
 38 }
 39 int content_length=atoi(content_length_env);
 40 int i=0;
 41 for (;i<content_length;++i){
 42 char  c='\0';
 43 read(0,&c,1);
 44 output[i]=c;
 45 }
 46 return ;
 47 }
 48 }
 49 
 50 int main (){
 51         //1.基于CGI协议获取到需要的参数
 52         char  query_string[SIZE]={0};
 53         GetQueryString(query_string);
 54         //2.根据业务逻辑进行计算
 55         //此时时获取到的QUERY_STRING的形式如下
 56         //a=10&b=20
 57         int a=0;
 58         int b=0;
 59         sscanf(query_string ,"a=%d&b=%d\n",&a,&b);
 60         int sum =a+b;
 61         //3.把结果构造成HTML写回到标准输出
 62         printf ("<html><h1>sum=%d</h1></html>",sum);
 63         return 0;
 64 }

/home/a/Desktop/Mycode/http_server2/wwwroot/calc/index.html


  4 <html>
  5 <form action="/calc/calc"method="POST">
  6 a:<br>
  7 <input type="text" name="a" >
  8 <br>
  9 b:<br>
 10 <input type="text" name="b" >
 11 <br><br>
 12 <input type="submit" value="Submit">
 13 </form>
 14 </html>

/home/a/Desktop/Mycode/http_server2/wwwroot/calc/Makefile

  1 calc:calc.c
  2         gcc  $^  -o  $@
  3 
  4 .PHONY:clean
  5 clean:
  6         rm  calc

/home/a/Desktop/Mycode/http_server2/wwwroot/image/1.png 这是一张图片

/home/a/Desktop/Mycode/http_server2/wwwroot/mysql/select.c

  1 //本程序基于MySQL  API完成对数据库的查询操作
  2 //不管用户输入什么,都尝试把TestTable这个表里的所有数据都查询出来,展示到以页面上
  3 //注意这个CGI存在的问题是,如果访问时不加参数会当成要下载的静态页面,所以访问时应该在后面加上一个参数虽然我们不用,原因是我们前面判断是动态页面还是静态页面的///根据以下步骤1,先看方法,Get方法的QUERY_STRING是否为空,为空时静态页面>    ,不为空时,是动态页面,post请求直接是动态页面
  4 //改进通过stat函数判断是否是可执行程序。
  5 
  6 
  7 #include <stdio.h>
  8 #include <stdlib.h>
  9 #include <mysql/mysql.h>
 10 
 11 
 12 int main (){
 13         //1.获取到CGI程序需要的相关参数(此处可以不获取)
 14         //2.调用MySQL  API 来访问和操作数据库
 15         //(a)创建一个MySQL的连接句柄
 16         MYSQL * connect_fd=mysql_init(NULL);
 17         //(b)和数据库服务器建立连接
 18         if (mysql_real_connect(connect_fd,"192.168.0.104","root"," ","TestDB",3306,NULL,0)==NULL){
 19                 fprintf(stderr,"mysql_real_connect  failed\n");
 20                 return 1;
 21         }
 22         fprintf(stderr,"mysql_real_connect  ok\n");
 23         //(c)构造SQL语句,此处不需要加分号,但在SQL语句时是要加的
 24         const char* sql="select *from TestTable";
 25                 //(d)把构造的SQL语句发上给Mysql 服务器,并执行
 26                 int ret=mysql_query(connect_fd,sql);
 27         if (ret<0){
 28                 fprintf(stderr,"mysql_query  failed\n");
 29                 return 1;
 30         }
 31         //(e)解析和遍历返回结果()结果是一个表格
 32         MYSQL_RES*  result=mysql_store_result(connect_fd);
 33         //先获取行数和列数
 34         int rows=mysql_num_rows(result);
 35         int fields=mysql_num_fields(result);
 36         //获取表头(每列的意思)
 37         printf(<html>\n);
 38         printf("rows=%d,fields=%d<br>\n",rows,fields);
 39         MYSQL_FIELD*  field=mysql_fetch_field(result);//一次只取一列的表头
 40         while (field!=NULL){
 41                 printf("%s\t",field->name );
 42                 field=mysql_fetch_field(result);//取下一列的表头
 43         }
 44         printf("<br>");
 45         //按行获取表的具体内容
 46         int i=0;
 47         for (;i<rows;i++){
 48                 MYSQL_ROW  row=mysql_fetch_row(result);//每调用一次就取一行
 49                 int j=0;
 50                 for (;j<fields;++j){
 51                         printf("%s\t",row[j]);
 52                 }
 53                 printf ("<br>");
 54         }
 55         printf("</html>\n");
 56         //(f)断开连接
 57         mysql_close(connect_fd);
 58         //3。把获取的数据组织成html 返回给客户端
 59         return ;
 60 }

基于HTTP服务器实现的业务:

(1)用户通过浏览器网页(静态页面)提供给服务器一个日期和一个天数(N)。

(2)服务器会对日期和天数进行计算(动态页面),计算出这个日期N天后的日期。

(3)通过浏览器返回,以便用户更合理的安排时间。

日期类我写在了另一篇博客https://blog.csdn.net/a15929748502/article/details/81369350欢迎查阅

下面是我的测试;

及结果:

 

### 回答1: 好的,我可以给你一些基本的指导。 在使用 C 语言编写基于 TCP 协议的文件传输程序时,你需要用到以下步骤: 1. 包含必要的头文件:`#include <stdio.h>`、`#include <string.h>` 和 `#include <sys/socket.h>`。 2. 设置服务器的 IP 地址和端口号。 3. 创建一个 socket: ``` int sockfd = socket(AF_INET, SOCK_STREAM, 0); ``` 4. 设置服务器地址结构体: ``` struct sockaddr_in server_address; memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_port = htons(port); server_address.sin_addr.s_addr = inet_addr(ip); ``` 5. 连接到服务器: ``` int ret = connect(sockfd, (struct sockaddr*)&server_address, sizeof(server_address)); if (ret < 0) { // 连接失败 } ``` 6. 打开要传输的文件: ``` FILE* file = fopen(filename, "r"); if (file == NULL) { // 文件打开失败 } ``` 7. 循环读取文件的内容,并发送给服务器: ``` char buffer[1024]; while (fgets(buffer, sizeof(buffer), file)) { send(sockfd, buffer, strlen(buffer), 0); } ``` 8. 关闭文件和 socket: ``` fclose(file); close(sockfd); ``` 希望这些信息对你有帮助! ### 回答2: 基于TCP协议使用C语言编写文件传输程序的过程如下: 首先,需要使用C语言中的socket函数创建一个套接字,并指定使用TCP协议。可以使用如下代码创建套接字:int sockfd = socket(AF_INET, SOCK_STREAM, 0);其中AF_INET表示使用IPv4地址族,SOCK_STREAM表示使用TCP协议。 接下来,需要连接到文件传输的目标主机。可以使用如下代码连接到目标主机:int connect_result = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));其中server_addr是一个sockaddr_in类型的结构体,其中保存了目标主机的IP地址和端口号。 连接成功后,可以使用C语言中的文件操作函数打开要传输的文件。可以使用如下代码打开文件:FILE *fp = fopen(filename, "rb");其中filename是要传输的文件名,"rb"表示以二进制读取方式打开文件。 然后,需要循环读取文件的内容,并使用send函数将数据发送到目标主机。可以使用如下代码实现数据的发送:while((bytes = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0){int bytes_sent = send(sockfd, buffer, bytes, 0);} 接收端获取数据的过程类似,只需要使用recv函数从套接字中接收数据,然后使用fwrite函数将接收到的数据写入到目标文件中。 最后,传输完成后需要关闭套接字和文件。可以使用如下代码关闭套接字和文件:close(sockfd);fclose(fp); 需要注意的是,在编写文件传输程序时,需要处理各种错误情况,例如连接失败、打开文件失败、发送或接收数据失败等。可以使用错误处理机制和适当的代码结构来处理这些错误。 ### 回答3: 基于TCP协议用C语言写文件传输程序的实现步骤如下: 1. 引入必要的头文件,包括stdio.h、stdlib.h和netinet/in.h等。 2. 创建socket,使用socket()函数,其中参数AF_INET表示使用IPv4协议,SOCK_STREAM表示使用流式套接字。 3. 绑定socket到指定的端口号,使用bind()函数,其中参数包括创建的socket和一个sockaddr_in结构体,其中包括IP地址和端口号。 4. 监听来自客户端的连接请求,使用listen()函数。 5. 接受客户端的连接请求,并创建一个新的socket用于与客户端通信,使用accept()函数,其中包括服务器的socket和一个sockaddr_in结构体。 6. 客户端发送文件名给服务器服务器接收文件名并保存。 7. 接收客户端发送的文件内容,并将文件内容保存到服务器端指定的文件中,使用recv()函数。 8. 重复步骤7,直到文件传输完毕。 9. 关闭与客户端通信的socket。 10. 重复步骤5至9,接收其他客户端的文件传输请求。 11. 关闭服务器的socket。 需要注意的是,向服务器发送文件的客户端程序需要先连接服务器,然后发送文件名和文件内容,这部分的代码也需要用C语言实现,但超出了300字的范围。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值