【web服务器】预备知识

举报
xcc-2022 发表于 2022/11/28 18:51:07 2022/11/28
【摘要】 @[toc]首先我们需要一个浏览器,访问的时候得要有web服务器,在浏览器访问网页的时候我们一般都是敲www.baid.com,在网络上不知道这是什么,要判断去那里得用ip;我们通过ping测试连接性查看到baidu的IP,紧跟着ip后面有个端口号;通过DNS解析成112.80.248.76传给浏览器;我们也可以通过ip进入到百度首页和域名 一样的,在ip的后面默认跟着文件类型/index....

@[toc]
首先我们需要一个浏览器,访问的时候得要有web服务器,在浏览器访问网页的时候我们一般都是敲www.baid.com,在网络上不知道这是什么,要判断去那里得用ip;我们通过ping测试连接性查看到baidu的IP,紧跟着ip后面有个端口号;通过DNS解析成112.80.248.76传给浏览器;

image-20221004072619255

我们也可以通过ip进入到百度首页和域名 一样的,在ip的后面默认跟着文件类型/index.html,首页的意思,可以省略

image-20221004073109275

而我们的ip和web服务器通信是使用HTTP协议,然后我们的服务器需要解析请求,解析112.80.248.76:80/index.html的文件,并找浏览器请求的文件,如果找到发回去,没找到,就404 NOT FOUND;

Web服务器开发准备

​ 为了编写web服务器,我们需要学会编写html页面,以及掌握部分http协议知识,这两部分内容将在接下来进行介绍。这两个准备工作之后,还需要知道web服务器的通信流程是什么?还需要思考如何支持多浏览器并发访问!

Html语言基础

系统学习可以查看此篇

Html简介

​ Html(Hyper Texture Markup Language)是超文本标记语言,在计算机中以 .html或者.htm作为扩展名,可以被浏览器识别,就是经常见到的网页.

​ Html的语法非常简洁,比较松散,以相应的英语单词关键字进行组合,html标签不区分大小写,标签大多数成对出现,有开始,有结束,例如 <html></html>,但是并没有要求必须成对出现.同时也有固定的短标签,例如
,


.

​ 学习html基本可以认为就是学习各种标签,标签也可以设置属性,例如hello, world,示例中color代表标签的颜色属性,red代表标签是红色字体,hello,world为实际显示的内容.可以新建一个文本文档,然后将后缀名修改.html文件,用代码编辑器打开该html文件可以编辑文件(例如notepad++),将上述内容保存到文件中,双击该文件可以看到如下效果:

img

​ Html的组成可以分为如下部分:

  1. <!doctype html> 声明文档类型,可以不写

  2. <html> 开始 和</html> 结束,属于html的根标签
  3. <head></head> 头部标签,头部标签内一般有 <title></title>
  4. <body></body> 主体标签,一般用于显示内容
<html> 
	<head>
		<title>这是一个标题</title>
	</head>
 
	<body>
		<font color="red" size="5">hello, world</font>
	</body>
</html>

如果想要添加注释,可以使用 <!—我是注释 -->的方式.

也可以指定页面类型和字符编码,下面设置页面类型为html,并且字符编码为utf8

<meta http-equiv="content-Type" content="text/html; charset=utf8">

Html标签属性,可以双引号,单引号,或者不写

Html标签介绍

题目标签

​ 共有6种,

,

,…

,其中

最大,

最小

文本标签

标签,可以设置颜色和字体大小属性

​ 颜色表示方法(可以参考网站: http://tool.oschina.net/commons?type=3):

  1. 英文单词 red green blue …

  2. 使用16进制的形式表示颜色:#ffffff

  3. 使用rgb(255,255,0)

字体大小可以使用size属性,大小范围为1-7,其中7最大,1最小.

有时候需要使用换行标签 ,这是一个短标签

与之对应另外还有一个水平线也是短标签,


,水平线也可以设置颜色和大小

列表标签

​ 列表标签分无序列表和有序列表,分别对应

      .

      无序列表的格式如下:
      	<ul>
      	<li>列表内容1</li>
      	<li>列表内容2</li></ul>
      无序列表可以设置type属性:
      实心圆圈:type=disc
      	空心圆圈:type=circle
      	小方块:  type=square
      	有序列表的格式如下:
      	<ol>
      	<li>列表内容1</li>
      	<li>列表内容2</li></ol>
      

      有序列表同样可以设置type属性

      数字:type=1,也是默认方式

      英文字母:type=a或type=A

      罗马数字:type=i或type=I

      图片标签

      ​ 图片标签使用,内部需要设置若干属性,可以不必写结束标签

      ​ 属性:

      1. src=”3.gif” 图片来源,必写

      2. alt=”小岳岳” 图片不显示时,显示的内容

      3. title=”我的天呐” 鼠标移动到图片上时显示的文字

      4. width=”600” 图片显示的宽度

      5. height=”400” 图片显示的高度

      例如:

      小岳岳

      ​ 注意:当图片未定义宽高,图片百分百比例显示,如果只改变图片宽度或者高度,会等比例缩放

      超链接标签

      超链接标签使用,同样需要设置属性表明要链接到哪里.

      属性:

      1. href=”http://www.baidu.cn”,前往地址,必填,注意要写http://

      2. title=”前往百度” 鼠标移动到链接上时显示的文字

      3. target=”_self”或者”_blank”,_self是默认值,在自身页面打开,_blank是新开页面前往连接地址

      示例:

      来百度

      当我们访问某个网站的时候,当请求的资源不存在,经常会给我们报告一个错误,显示为404错误,一般会给请求用户返回一个错误页,大家可以自行尝试一下编写一个我们自己的错误页.

      http超文本传输协议

      http协议和html前面的ht都是超文本的意思,所以http与html是配合非常紧密的一对,我们可以认为http就是为了传输html这样的文件,http位于应用层,侧重于解释.

      http协议对消息区分可以分为请求消息和响应消息.

      http请求消息

      ​ 我们要开发的服务器与浏览器通信采用的就是http协议,在浏览器想访问一个资源的时候,在浏览器输入访问地址(例如http://127.0.0.1:8000),地址输入完成后当敲击回车键的时候,浏览器就将请求消息发送给服务器

      我们可以先用测试工具创建一个socket服务器下载链接

      image-20221004081551889

      image-20221004091808534

      切记不要一直开着,会有一定的缓存

      img

      之后通过浏览器请求地址,就会看到浏览器发送过来的请求消息

      img

      ​ 这个消息看起来很乱很复杂,对应的就是我们说的请求消息.

      ​ 请求消息分为四部分内容:

      1. 请求行 说明请求类型,要访问的资源,以及使用的http版本

      2. 请求头 说明服务器使用的附加信息,都是键值对,比如表明浏览器类型

      3. 空行 不能省略-而且是\r\n,包括请求行和请求头都是以\r\n结尾

      4. 请求数据 表明请求的特定数据内容,可以省略-如登陆时,会将用户名和密码内容作为请求数据

      img

      请求类型

      ​ http协议有很多种请求类型,对我们来说常见的用的最多的是get和post请求。常见的请求类型如下:

      1. Get 请求指定的页面信息,并返回实体主体

      2. Post 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。

      3. Head 类似于get请求,但是响应消息没有内容,只是获得报头

      4. Put 从客户端向浏览器传送的数据取代指定的文档内容

      5. Delete 请求服务器删除指定的页面

      6. Connect HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器

      7. Options 允许客户端查看浏览器的性能

      8. Trace 回显服务器收到的请求,主要用于测试和诊断

      get 和 post 请求都是请求资源,而且都会提交数据,如果提交密码信息用get请求,就会明文显示,而post则不会显示出涉密信息.

      http响应消息

      响应消息是代表服务器收到请求消息后,给浏览器做的反馈,所以响应消息是服务器发送给浏览器的,响应消息也分为四部分:

      1. 状态行 包括http版本号,状态码,状态信息

      2. 消息报头 说明客户端要使用的一些附加信息,也是键值对

      3. 空行 \r\n 同样不能省略

      4. 响应正文 服务器返回给客户端的文本信息

      示例:

      img

      http常见状态码

      http状态码由三位数字组成,第一个数字代表响应的类别,有五种分类:

      1. 1xx 指示信息–表示请求已接收,继续处理

      2. 2xx 成功–表示请求已被成功接收、理解、接受

      3. 3xx 重定向–要完成请求必须进行更进一步的操作

      4. 4xx 客户端错误–请求有语法错误或请求无法实现

      5. 5xx 服务器端错误–服务器未能实现合法的请求

      常见的状态码如下:

      • 200 OK 客户端请求成功
      • 301 Moved Permanently 重定向
      • 400 Bad Request 客户端请求有语法错误,不能被服务器所理解
      • 401 Unauthorized 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
      • 403 Forbidden 服务器收到请求,但是拒绝提供服务
      • 404 Not Found 请求资源不存在,eg:输入了错误的URL
      • 500 Internal Server Error 服务器发生不可预期的错误
      • 503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常

      web服务器开发

      我们要开发web服务器已经明确要使用http协议传送html文件,那么我们如何搭建我们的服务器呢?注意http只是应用层协议,我们仍然需要选择一个传输层的协议来完成我们的传输数据工作,所以开发协议选择是TCP+HTTP,也就是说服务器搭建浏览依照TCP,对数据进行解析和响应工作遵循HTTP的原则.

      这样我们的思路很清晰,编写一个TCP并发服务器,只不过收发消息的格式采用的是HTTP协议,如下图:

      img

      为了支持并发服务器,我们可以有多个选择,比如多进程服务器,多线程服务器,select,poll,epoll等多路IO工具都可以,甚至如果读者觉得libevent非常熟练的话,也可以使用libevent进行开发.

      基于epoll的web服务器

      由于我们知道epoll在大量并发少量活跃的情况下效率很高,所以本文以epoll为例,介绍epoll开发的主体流程:

      img

      对于我们来说,上述的框架基本没问题,除了处理客户端请求部分,我们可以考虑封装成一个函数,思考:函数参数如何设计?

      处理客户端请求流程:

      img

      思考题:

      1. 由于每个响应消息都是分为四部分,可以考虑封装为函数,封装几个函数更方便?都分别对应什么功能?

      2. 目录请求相对复杂,需要遍历目录内的内容,也就是读内容,思考如何做?读到内容形成正文发送的时候,对应的文件类型应该是什么?

      //epoll_web.c
      #include "stdio.h"
      #include "wrap.h"
      #include "sys/epoll.h"
      #include <fcntl.h>
      #include <sys/stat.h>
      #include "pub.h"
      #include "dirent.h"
      #include "signal.h"
      #define PORT 8889
      void send_header(int cfd, int code,char *info,char *filetype,int length)
      {	//发送状态行
      	char buf[1024]="";
      	int len =0;
      	len = sprintf(buf,"HTTP/1.1 %d %s\r\n",code,info);
      	send(cfd,buf,len,0);
      	//发送消息头
      	len = sprintf(buf,"Content-Type:%s\r\n",filetype);
      	send(cfd,buf,len,0);
      	if(length > 0)
      	{
      			//发送消息头
      		len = sprintf(buf,"Content-Length:%d\r\n",length);
      		send(cfd,buf,len,0);
      
      	}
      	//空行
      	send(cfd,"\r\n",2,0);
      }
      void send_file(int cfd,char *path,struct epoll_event *ev,int epfd,int flag)
      {
      		int fd = open(path,O_RDONLY);
      		if(fd <0)
      		{
      			perror("");
      			return ;
      		}
      		char buf[1024]="";
      		int len =0;
      		while( 1)
      		{
      
      			len = read(fd,buf,sizeof(buf));
      			if(len < 0)
      			{
      				perror("");
      				break;
      
      			}
      			else if(len == 0)
      			{
      				break;
      			}
      			else
      			{
      				int n=0;
      				n =  send(cfd,buf,len,0);
      				printf("len=%d\n", n);
      
      			}
      
      
      		}
      		close(fd);
      		//关闭cfd,下树
      		if(flag==1)
      		{
      			close(cfd);
      			epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,ev);
      		}
      
      
      
      }
      void read_client_request(int epfd ,struct epoll_event *ev)
      {
      	//读取请求(先读取一行,在把其他行读取,扔掉)
      	char buf[1024]="";
      	char tmp[1024]="";
      	 int n = Readline(ev->data.fd, buf, sizeof(buf));
      	 if(n <= 0)
      	 {
      	 	printf("close or err\n");
      	 	epoll_ctl(epfd,EPOLL_CTL_DEL,ev->data.fd,ev);
      	 	close(ev->data.fd);
      	 	return ;
      	 }
      	 printf("[%s]\n", buf);
      	 int ret =0;
      	 while(  (ret = Readline(ev->data.fd, tmp, sizeof(tmp))) >0);  
      	 //解析请求行  GET /a.txt  HTTP/1.1\R\N
      	 //
      	 char method[256]="";
      	 char content[256]="";
      	 char protocol[256]="";
      	 sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]",method,content,protocol);
      	 printf("[%s]  [%s]  [%s]\n",method,content,protocol );
      	 //判断是否为get请求  get   GET
      	 if( strcasecmp(method,"get") == 0)
      	 {
      	 	//[GET]  [/%E8%8B%A6%E7%93%9C.txt]  [HTTP/1.1]
      	 		char *strfile = content+1;
      	 		strdecode(strfile,strfile);
      	 		 //GET / HTTP/1.1\R\N
      	 		if(*strfile == 0)//如果没有请求文件,默认请求当前目录
      	 			strfile= "./";
      	 		//判断请求的文件在不在
      	 		struct stat s;
      	 		if(stat(strfile,&s)< 0)//文件不存在
      	 		{
      	 			printf("file not fount\n");
      	 			//先发送 报头(状态行  消息头  空行)
      	 			send_header(ev->data.fd, 404,"NOT FOUND",get_mime_type("*.html"),0);
      	 			//发送文件 error.html
      	 			send_file(ev->data.fd,"error.html",ev,epfd,1);
      
      
      	 		}
      	 		else
      	 		{
      	 			
      	 			//请求的是一个普通的文件
      	 			if(S_ISREG(s.st_mode))
      	 			{
      	 				printf("file\n");
      	 				//先发送 报头(状态行  消息头  空行)
      	 				send_header(ev->data.fd, 200,"OK",get_mime_type(strfile),s.st_size);
      	 				//发送文件
      	 				send_file(ev->data.fd,strfile,ev,epfd,1);
      
      	 			}
      	 			else if(S_ISDIR(s.st_mode))//请求的是一个目录
      	 			{
      						printf("dir\n");
      						//发送一个列表  网页
      						send_header(ev->data.fd, 200,"OK",get_mime_type("*.html"),0);
      						//发送header.html
      						send_file(ev->data.fd,"dir_header.html",ev,epfd,0);
      
      						struct dirent **mylist=NULL;
      						char buf[1024]="";
      						int len =0;
      						int n = scandir(strfile,&mylist,NULL,alphasort);
      						for(int i=0;i<n;i++)
      						{
      							//printf("%s\n", mylist[i]->d_name);
      							if(mylist[i]->d_type == DT_DIR)//如果是目录
      							{
      								len = sprintf(buf,"<li><a href=%s/ >%s</a></li>",mylist[i]->d_name,mylist[i]->d_name);
      							}
      							else
      							{
      								len = sprintf(buf,"<li><a href=%s >%s</a></li>",mylist[i]->d_name,mylist[i]->d_name);
      							}
      
      							
      
      							send(ev->data.fd,buf,len ,0);
      
      							free(mylist[i]);
      						}
      						free(mylist);
      
      
      						send_file(ev->data.fd,"dir_tail.html",ev,epfd,1);
      	 			}
      
      	 		}
      	 }
      }
      int main(int argc, char const *argv[])
      {
      	signal(SIGPIPE,SIG_IGN);
      	//切换工作目录
      	//获取当前目录的工作路径
      	char pwd_path[256]="";
      	char * path = getenv("PWD");
      	///home/itheima/share/bjc++34/07day/web-http
      	strcpy(pwd_path,path);
      	strcat(pwd_path,"/web-http");
      	chdir(pwd_path);
      
      
      	//创建套接字 绑定
      	int lfd = tcp4bind(PORT,NULL);
      	//监听
      	Listen(lfd,128);
      	//创建树
      	int epfd = epoll_create(1);
      	//将lfd上树
      	struct epoll_event ev,evs[1024];
      	ev.data.fd = lfd;
      	ev.events = EPOLLIN;
      	epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
      	//循环监听
      	while(1)
      	{
      		int nready = epoll_wait(epfd,evs,1024,-1);
      		if(nready < 0)
      		{
      			perror("");
      			break;
      		}
      		else
      		{
      			for(int i=0;i<nready;i++)
      			{
      				printf("001\n");
      				//判断是否是lfd
      				if(evs[i].data.fd == lfd && evs[i].events & EPOLLIN)
      				{
      					struct sockaddr_in cliaddr;
      					char ip[16]="";
      					socklen_t len = sizeof(cliaddr);
      					int cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);
      					printf("new client ip=%s port=%d\n",
      						inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
      						ntohs(cliaddr.sin_port));
      					//设置cfd为非阻塞
      					int flag = fcntl(cfd,F_GETFL);
      					flag |= O_NONBLOCK;
      					fcntl(cfd,F_SETFL,flag);
      					//上树
      					ev.data.fd = cfd;
      					ev.events = EPOLLIN;
      					epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
      
      
      				}
      				else if(evs[i].events & EPOLLIN)//cfd变化
      				{
      					read_client_request(epfd,&evs[i]);
      
      				}
      			}
      		}
      	}
      	//=收尾
      	return 0;
      }
      
      
      //pub.c
      #include "pub.h"
      //通过文件名字获得文件类型
      char *get_mime_type(char *name)
      {
          char* dot;
      
          dot = strrchr(name, '.');	//自右向左查找‘.’字符;如不存在返回NULL
          /*
           *charset=iso-8859-1	西欧的编码,说明网站采用的编码是英文;
           *charset=gb2312		说明网站采用的编码是简体中文;
           *charset=utf-8			代表世界通用的语言编码;
           *						可以用到中文、韩文、日文等世界上所有语言编码上
           *charset=euc-kr		说明网站采用的编码是韩文;
           *charset=big5			说明网站采用的编码是繁体中文;
           *
           *以下是依据传递进来的文件名,使用后缀判断是何种文件类型
           *将对应的文件类型按照http定义的关键字发送回去
           */
          if (dot == (char*)0)
              return "text/plain; charset=utf-8";
          if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
              return "text/html; charset=utf-8";
          if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
              return "image/jpeg";
          if (strcmp(dot, ".gif") == 0)
              return "image/gif";
          if (strcmp(dot, ".png") == 0)
              return "image/png";
          if (strcmp(dot, ".css") == 0)
              return "text/css";
          if (strcmp(dot, ".au") == 0)
              return "audio/basic";
          if (strcmp( dot, ".wav") == 0)
              return "audio/wav";
          if (strcmp(dot, ".avi") == 0)
              return "video/x-msvideo";
          if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
              return "video/quicktime";
          if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
              return "video/mpeg";
          if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
              return "model/vrml";
          if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
              return "audio/midi";
          if (strcmp(dot, ".mp3") == 0)
              return "audio/mpeg";
          if (strcmp(dot, ".ogg") == 0)
              return "application/ogg";
          if (strcmp(dot, ".pac") == 0)
              return "application/x-ns-proxy-autoconfig";
      
          return "text/plain; charset=utf-8";
      }
      //获得一行数据,每行以\r\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);
              /* DEBUG printf("%02X\n", c); */
              if (n > 0)
              {
                  if (c == '\r')
                  {
                      n = recv(sock, &c, 1, MSG_PEEK);//MSG_PEEK 从缓冲区读数据,但是数据不从缓冲区清除
                      /* DEBUG printf("%02X\n", c); */
                      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);
      }
      
      //下面的函数第二天使用
      /*
       * 这里的内容是处理%20之类的东西!是"解码"过程。
       * %20 URL编码中的‘ ’(space)
       * %21 '!' %22 '"' %23 '#' %24 '$'
       * %25 '%' %26 '&' %27 ''' %28 '('......
       * 相关知识html中的‘ ’(space)是&nbsp
       */
       // %E8%8B%A6%E7%93%9C
      void strdecode(char *to, char *from)
      {
          for ( ; *from != '\0'; ++to, ++from) {
      
              if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { //依次判断from中 %20 三个字符
      
                  *to = hexit(from[1])*16 + hexit(from[2]);//字符串E8变成了真正的16进制的E8
                  from += 2;                      //移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
              } else
                  *to = *from;
          }
          *to = '\0';
      }
      
      //16进制数转化为10进制, return 0不会出现
      int hexit(char c)
      {
          if (c >= '0' && c <= '9')
              return c - '0';
          if (c >= 'a' && c <= 'f')
              return c - 'a' + 10;
          if (c >= 'A' && c <= 'F')
              return c - 'A' + 10;
      
          return 0;
      }
      
      //"编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
      //strencode(encoded_name, sizeof(encoded_name), name);
      void strencode(char* to, size_t tosize, const char* from)
      {
          int tolen;
      
          for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
              if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
                  *to = *from;
                  ++to;
                  ++tolen;
              } else {
                  sprintf(to, "%%%02x", (int) *from & 0xff);
                  to += 3;
                  tolen += 3;
              }
          }
          *to = '\0';
      }
      
      //warp.c
      #include <stdlib.h>
      #include <stdio.h>
      #include <unistd.h>
      #include <errno.h>
      #include <string.h>
      #include <sys/socket.h>
      #include <arpa/inet.h>
      #include <strings.h>
      
      void perr_exit(const char *s)
      {
      	perror(s);
      	exit(-1);
      }
      
      int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
      {
      	int n;
      
      again:
      	if ((n = accept(fd, sa, salenptr)) < 0) {
      		if ((errno == ECONNABORTED) || (errno == EINTR))//如果是被信号中断和软件层次中断,不能退出
      			goto again;
      		else
      			perr_exit("accept error");
      	}
      	return n;
      }
      
      int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
      {
          int n;
      
      	if ((n = bind(fd, sa, salen)) < 0)
      		perr_exit("bind error");
      
          return n;
      }
      
      int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
      {
          int n;
      
      	if ((n = connect(fd, sa, salen)) < 0)
      		perr_exit("connect error");
      
          return n;
      }
      
      int Listen(int fd, int backlog)
      {
          int n;
      
      	if ((n = listen(fd, backlog)) < 0)
      		perr_exit("listen error");
      
          return n;
      }
      
      int Socket(int family, int type, int protocol)
      {
      	int n;
      
      	if ((n = socket(family, type, protocol)) < 0)
      		perr_exit("socket error");
      
      	return n;
      }
      
      ssize_t Read(int fd, void *ptr, size_t nbytes)
      {
      	ssize_t n;
      
      again:
      	if ( (n = read(fd, ptr, nbytes)) == -1) {
      		if (errno == EINTR)//如果是被信号中断,不应该退出
      			goto again;
      		else
      			return -1;
      	}
      	return n;
      }
      
      ssize_t Write(int fd, const void *ptr, size_t nbytes)
      {
      	ssize_t n;
      
      again:
      	if ( (n = write(fd, ptr, nbytes)) == -1) {
      		if (errno == EINTR)
      			goto again;
      		else
      			return -1;
      	}
      	return n;
      }
      
      int Close(int fd)
      {
          int n;
      	if ((n = close(fd)) == -1)
      		perr_exit("close error");
      
          return n;
      }
      
      /*参三: 应该读取固定的字节数数据*/
      ssize_t Readn(int fd, void *vptr, size_t n)
      {
      	size_t  nleft;              //usigned int 剩余未读取的字节数
      	ssize_t nread;              //int 实际读到的字节数
      	char   *ptr;
      
      	ptr = vptr;
      	nleft = n;
      
      	while (nleft > 0) {
      		if ((nread = read(fd, ptr, nleft)) < 0) {
      			if (errno == EINTR)
      				nread = 0;
      			else
      				return -1;
      		} else if (nread == 0)
      			break;
      
      		nleft -= nread;
      		ptr += nread;//更新位置
      	}
      	return n - nleft;
      }
      /*:固定的字节数数据*/
      ssize_t Writen(int fd, const void *vptr, size_t n)
      {
      	size_t nleft;
      	ssize_t nwritten;
      	const char *ptr;
      
      	ptr = vptr;
      	nleft = n;
      	while (nleft > 0) {
      		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
      			if (nwritten < 0 && errno == EINTR)
      				nwritten = 0;
      			else
      				return -1;
      		}
      
      		nleft -= nwritten;
      		ptr += nwritten;
      	}
      	return n;
      }
      
      static ssize_t my_read(int fd, char *ptr)
      {
      	static int read_cnt;
      	static char *read_ptr;
      	static char read_buf[100];
      
      	if (read_cnt <= 0) {
      again:
      		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
      			if (errno == EINTR)
      				goto again;
      			return -1;
      		} else if (read_cnt == 0)
      			return 0;
      		read_ptr = read_buf;
      	}
      	read_cnt--;
      	*ptr = *read_ptr++;
      
      	return 1;
      }
      
      ssize_t Readline(int fd, void *vptr, size_t maxlen)
      {
      	ssize_t n, rc;
      	char    c, *ptr;
      
      	ptr = vptr;
      	for (n = 1; n < maxlen; n++) {
      		if ( (rc = my_read(fd, &c)) == 1) {
      			*ptr++ = c;
      			if (c  == '\n')
      				break;
      		} else if (rc == 0) {
      			*ptr = 0;
      			return n - 1;
      		} else
      			return -1;
      	}
      	*ptr  = 0;
      
      	return n;
      }
      
      int tcp4bind(short port,const char *IP)
      {
          struct sockaddr_in serv_addr;
          int lfd = Socket(AF_INET,SOCK_STREAM,0);
          bzero(&serv_addr,sizeof(serv_addr));
          if(IP == NULL){
              //如果这样使用 0.0.0.0,任意ip将可以连接
              serv_addr.sin_addr.s_addr = INADDR_ANY;
          }else{
              if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
                  perror(IP);//转换失败
                  exit(1);
              }
          }
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_port   = htons(port);
         // int opt = 1;
      	//setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      
          Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
          return lfd;
      }
      

      同一个网段如果要手机连接web服务器的话,虚拟机的网络需要是桥接

      编译:

      gcc epoll_web.c wrap.c pub.c -std=gnu99

      文件已经打包,在个人主页支援页面可找到

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。