-
2021-08-01 01:24:14
2019-03-27
管家婆登陆提示“连接服务器失败”怎么办
管家婆连接不上服务器,登录提示“连接服务器失败”,如何处理当管家婆连接不上服务器,登录提示“连接服务器失败”,如何处理?一、常规检查当软件出现无法登录软件问题时,请先检查电脑的右下角任务栏位置管家婆所需的3个服务器是否均已运行,如果是网络版请先到服务器端去确认。 1)管家婆服务器2)管家婆套接字服务器(辉煌Ⅱ -- ;辉煌v8。51,2005,2008,366系列 -- )3)SQL Server2000服务管理器二、故障排除1)如果缺少管家婆服务器或套接字服务器,可以在“开始→程序”中找到管家婆辉煌软件选项,在该选项中会存在服务器或套接字服务器,将未启动的服务启动。 启...全部
管家婆连接不上服务器,登录提示“连接服务器失败”,如何处理当管家婆连接不上服务器,登录提示“连接服务器失败”,如何处理?一、常规检查当软件出现无法登录软件问题时,请先检查电脑的右下角任务栏位置管家婆所需的3个服务器是否均已运行,如果是网络版请先到服务器端去确认。
1)管家婆服务器2)管家婆套接字服务器(辉煌Ⅱ -- ;辉煌v8。51,2005,2008,366系列 -- )3)SQL Server2000服务管理器二、故障排除1)如果缺少管家婆服务器或套接字服务器,可以在“开始→程序”中找到管家婆辉煌软件选项,在该选项中会存在服务器或套接字服务器,将未启动的服务启动。
启动后检查:(1)套接字服务器中的端口是否为211,并且只有211,如下图:(2)管家婆服务器中设置数据库连接的服务器名是否为本机名字、用户名是否为sa、密码是否输入正确。如下图:2)缺少SQL Server2000服务管理器,可以在“开始→运行”中输入“sqlmangr”后回车,开启服务管理器界面确认服务器名是否为本机的名字,如果是点击“开始/继续”运行服务器,如果已经运行,点击刷新服务。
3) 在确认服务器均已启动的情况下,打开软件,确认在登陆向导输入了正确ip地址或机器名字,如果是网络版客户端需要输入服务器的ip或机器名字。单机版和网络版服务器端可以输入服务器的ip或服务器名字: (指向本机的通用指令)Localhost(指向本机的通用名字)本机的IP地址(可以在管家婆服务器中查到)参考资料: 。
收起
更多相关内容 -
web服务器
2021-03-24 00:34:17web服务器 服务器的相关概念 服务器与客户端 服务器的概念:提供网络服务的一台机器,通过在自己的电脑上安装特殊的软件(或者是运行某段特殊的代码)来提供服务 服务器 = 电脑 + 可以给其他电脑提供服务的软件 ...web服务器
1.服务器的相关概念
1.1服务器与客户端
服务器的概念:提供网络服务的一台机器,通过在自己的电脑上安装特殊的软件(或者是运行某段特殊的代码)来提供服务
-
服务器 = 电脑 + 可以给其他电脑提供服务的软件
-
服务器:提供服务的是服务器
-
客户端 = 电脑 + 软件(平时个人使用的软件)
-
客户端:享受服务的是客户端
1.2服务器的类型
根据服务不同,服务器的类型也不同:
- web服务器:安装apache, tomcat, iis, 或者在 node.js 环境写代码 来提供:图片浏览,新闻浏览…等服务的服务器。
- ftp服务器:安装serv-U软件,为其它电脑提供文件下载,共享服务。
- 数据库服务器:安装mysql软件,为其它电脑提供数据库服务。
- …
1.3web服务器
- 用户通过浏览器来享受web服务器提供的服务
- 我们用url地址来访问某个web服务器上的资源
- 浏览器端发起请求,web服务器收到请求后,响应这请求,并将处理结果返回给浏览器
- 浏览器端与web服务器是通过http(或者是https)协议来进行请求和响应的
1.4IP地址
全称:I ntternet P rotocol Address
作用:标识一个网络设备在某个具体的网络当中的地址。我们要想访问某个电脑上的资源,首先要找到它对应的IP
分类:IPV4 IPV6
格式:**[0255].[0255].[0255].[0255] ** 例如ipv4:127.0.0.1 (这个地址是本机的ip地址)
即四个0~255的数字组成(在同一个网络中,计算机的IP地址是不允许重复的,都是唯一的)
1.5域名
域名:ip地址的别名,由于ip地址不好记忆,就取了一个好记的别名。
localhost这个域名特指127.0.0.1这IP地址。
localhost = 127.0.0.1 (一样,都表示本机的地址,只是换了一个好记的名称)
域名解析系统:把域名翻译成IP地址的系统
1.6端口
一个IP地址可以有65536个端口(范围是从[0,65535])。
不同的端口被不同的软件占用,以提供不同的服务。
一台电脑可以通过安装多个服务器端软件来提供服务,比如web服务器,ftp服务器…。
仅仅通过ip地址是无法区分不同的服务的,所有需要 IP地址 + 端口号 来区分不同的服务
- 服务器要提供服务必须通过指定的端口
- 服务器与客户端都需要通过端口进行通信
- 端口是可以编程分配的
- 有一些端口是被约定的了
可以通过 小黑窗输入命令: netstat -a -n -o 查看端口的使用情况
1.7协议(http)
制定客户端与服务器之间的通讯规则。不同的协议的作用也不同。
http协议:
-
HTTP(HyperText Transfer Protocol)是超文本传输协议
-
协议双方:浏览器与web服务器都要遵守的协议
-
请求通常是由浏览器发起的
-
HTTP协议中明确规定了 请求数据 和 响应数据 的格式(报文)
-
浏览器 请求 资源 要遵守http协议:请求报文 (请求行,请求头,请求体)
-
浏览器 返回 资源 要遵守http协议:响应报文 (响应行,响应头,响应体)
-
2.用http模块写一个简单的web服务器
2.1用http实现一个简单的虚拟服务器(效果如下)
// 1.引入 http 模块 const http = require('http'); // console.log(http); // 2.创建服务// request:本次请求// res : 本次响应 const server = http.createServer((req,res)=>{ // 回调函数 : 每一次收到请求,他就执行一次 // 设置响应体 res.setHeader('content-type','text/html;charset=utf-8') // 结束本次请求 // 设置响应体:返回给用户看的 res.end('你好,虚拟服务器完成'); }); // 3.启动服务 server.listen(8000,()=>{ console.log('虚拟服务器启动成功','端口是8000'); });
运行结果:
2.2操作步骤
-
创建一个文件,名为 http模块实现虚拟服务器.js的文件(文件名路径可以自行设置,建议不要使用中文命名,这里为了演试就用中文的名字)。
-
在js文件中写入如下(如上图效果图)
- 1)引入http核心模块
- 2)使用createServer来创建服务
- 3)使用listener来启动服务
-
运行js代码
在js文件目录,打开小黑窗,键入命令
node http.js
,此时会弹出一个小黑窗,不要关。 -
本地验收
打开一个浏览器页面,输入’http://localhost:8000’,观察效果:
-
共享地址
把localhost改成你自己电脑的ip地址,再把这个路径发你的朋友(同一个局域网)来访问。
-
停止服务( ctrl + c )
2.3工作原理
使用http模块在本机中创建一个虚拟的服务器,用来接收浏览器的请求,并给出响应
注意:小黑窗不要关,它就是服务器,它不会有主动行为(看起来没有任何变化),它在时刻等待客户端的访问。
2.4代码解析
-
引入核心模块,得到的http是一个对象。
-
http.createServer方法创建一个http服务。
参数是一个回调函数:当有http请求进来时,它会自动被调用。
请求一次,它就被调用一次
。第一个参数:
客户端的请求
。第二个参数:
设置对本次请求的响应
。res.end() :设置响应体,结束请求。
-
server.listen() 用来监听端口。
格式:server.listen(端口号,[回调函数])。回调是可选的。
说明:
-
如果监听成功,则回调函数会执行一次。
-
如果不成功(例如端口被占用),会报错。
-
2.5修改代码之后要重启服务器
更改res.end()的内容,
重启
后,再次观察- 停止服务: 在小黑窗中按下ctrl+c 停止服务。
- 重启服务:就是重新运行程序(按下向上的箭头,再回车)。
3.理解请求与响应
3.1请求
当web服务器就绪之后,如果没有客户端来访问,它是不会有任何效果的。那么回调函数就不会被执行了。
然而每一次的请求,都会导致回调函数要执行一次。
3.2服务器的响应内容格式
res.end()的格式只能是 string 或 buffer
4.根据不同的url返回不同的内容-认识URL
4.1URL全称
Uniform Resource Locator,统一资源定位符。
4.2作用
定位资源
4.3格式
协议://主机地址[:端口]/路径?查询字符串#锚点
-
协议:http 或者是 https
-
主机地址:IP地址 或者 域名
-
端口号:
-
http请求,默认端口80(可以省略)
-
https请求,默认端口443(可以省略)
-
MySQL默认端口3306
-
-
路径:服务器文件夹上的资源。(.html/.css/.images/.js/接口)
-
查询字符串(参数):? 后面的部分,是键值对的形式
-
锚点:网页内部的锚点链接
5.不同的URL返回不同的内容-req.url
5.1目标:
通过 req.url来获取当前请求的url地址 ,并做出相应处理
5.2req.url 用来获取本次请求的资源地址。
//引入http模块 const http = require('http'); // 创建服务 const server = http.createServer(function(req, res) { console.log(req.url) res.end(req.url) }); // 启动服务 server.listen(8081, function() { console.log('success'); });
req.url一定是以**/**开头的。
在现代浏览器中,它们会自动去请求服务器上的
favicon.ico
(先不用理这个)6.不同的URL返回不同的内容-读取文件内容并返回
6.1目标
用户在访问服务器上不同的url时,能返回不同的内容
目录结构
|-index.html |-style.css |-01.png |-js/jquery.js |-server.js
请求与响应的对应的关系
用户的请求地址 服务器的动作 http://localhost:8000 读出index.html的内容并返回 http://localhost:8000/index.html 读出index.html的内容并返回 http://localhost:8000/style.css 读出style.css的内容并返回 http://localhost:8000/01.png 读出01.png的内容并返回 http://localhost:8000/js/jquery.js 读出jquery.js的内容并返回 http://localhost:8000/xxxx 或者不是上面的地址 返回404 代码示例:
// 1.引入 http 模块 const http = require('http'); // console.log(http); const fs = require('fs') const path = require('path') // 2.创建服务 // request:本次请求 // res : 本次响应 const server = http.createServer((req,res)=>{ // 回调函数 : 每一次收到请求,他就执行一次 // 读出文件 // fs.readFile('') const {url} = req; console.log(url); if(url === '/' || url === '/index.html'){ const pathFile = path.join(__dirname,'index.html'); // console.log(pathFile); // 同步读取文件 const content = fs.readFileSync(pathFile,'utf8'); // console.log(a); // 结束请求 res.end(content) } else if(url === '/style.css'){ const pathFile = path.join(__dirname,'style.css'); // console.log(pathFile); // 同步读取文件 const content = fs.readFileSync(pathFile,'utf8'); // console.log(a); // 结束请求 res.end(content) } else if(url === '/01.jpg'){ const pathFile = path.join(__dirname,'01.jpg'); console.log(pathFile); // 同步读取文件 const content = fs.readFileSync(pathFile); console.log(content); // console.log(content); // 结束请求 res.end(content) } else if(url === '/js/jQuery.js'){ const pathFile = path.join(__dirname,url); console.log(pathFile); // 同步读取文件 const content = fs.readFileSync(pathFile); res.setHeader('content-type','text/html;charset=utf-8') console.log(content); // console.log(content); // 结束请求 res.end(content) } else{ res.end('ok') } }); // 3.启动服务 server.listen(8000,()=>{ console.log('服务器启动成功'); })
6.2什么是静态资源
静态资源指的是html文件中链接的外部资源,如.html, css、js、image文件等等),服务器的处理方法就是:读出文件内容,返回给用户。
7.不同的URL返回不同的内容-设置content-type
7.1目标
会根据不同的文件类型来设置不同的content-type
7.2content-type的作用
在http协议中,content-type用来告诉对方本次传输的数据的类型是什么。
- 在请求头中设置content-type来告诉服务器,本次请求携带的数据是什么类型的
- 在响应头中设置content-type来告诉服务器,本次返回的数据是什么类型的
通过使用res对象中的setHeader方法,我们可以设置content-type这个响应头。这个响应头的作用是告诉浏览器,本次响应的内容是什么格式的内容,以方便浏览器进行处理。
7.3常见的几种文件类型及content-type
- .html:
res.setHeader('content-type', 'text/html;charset=utf8')
- .css:
res.setHeader('content-type', 'text/css;charset=utf8')
- .js:
res.setHeader('content-type', 'application/javascript')
- .png:
res.setHeader('content-type', 'image/png')
- json数据:
res.setHeader('content-type', 'application/json;charset=utf-8')
其它类型,参考这里:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
7.4代码示例:
目录结构:
|--pubic |-public/index.html |-public/stye.css |-public/1.png |-server.js
// 1.引入http模块 const http = require('http'); // console.log(http); //输出的一个对象 const path = require('path'); const fs = require('fs'); // - .html:` res.setHeader('content-type', 'text/html;charset=utf8') ` // - .css:`res.setHeader('content-type', 'text/css;charset=utf8')` // - .js:`res.setHeader('content-type', 'application/javascript') ` // - .png:`res.setHeader('content-type', 'image/png')` // - json数据:`res.setHeader('content-type', 'application/json;charset=utf-8')` // 2.创建服务 const server = http.createServer((req, res) => { // 解构赋值 const { url } = req; // console.log(url); // 获取路径 const pathFile = path.join(__dirname, 'public', url); // console.log(pathFile); // 获取后缀名 const ext = path.extname(pathFile); // console.log(ext); try { if (ext === '.html') { res.setHeader('content-type', 'text/html;charset=utf-8') const conter = fs.readFileSync(pathFile); res.end(conter); }else if(ext === '.css'){ res.setHeader('content-type', 'text/css;charset=utf-8') const conter = fs.readFileSync(pathFile); res.end(conter); } else if(ext === '.js'){ res.setHeader('content-type', 'application/javascript') const conter = fs.readFileSync(pathFile); res.end(conter); } else if(ext === '.jpg'){ res.setHeader('content-type', 'image/jpg') const conter = fs.readFileSync(pathFile); res.end(conter); } else{ res.end('404'); } } catch (err) { res.statusCode = 404; res.end("请求不到数据", '404') } }); // 3.启动服务 server.listen(8000, () => { console.log('启动服务器成功', '端口号', 8000); });
8.不同的URL返回不同的内容-设置statusCode
代码演示:
res.statusCode = 404; res.end("请求不到数据", '404')
9.处理.html文件的二次请求
从服务器获取html文件之后,如果这个html文件中还引用了其它的外部资源(图片,样式文件等),则浏览器会重新再发请求。
假设在index.html中还引入了 style.css 1.png 或者 .js文件,则:浏览器请求localhost:8000/index.html之后,得到的从服务器反馈的内容,解析的过程中还发现有外部的资源,所以浏览器会再次发出第二次请求,再去请求相应的资源。
10.统一处理静态资源
文件目录:
|-public |-public/index.html |-public/stye.css |-public/1.png |-server.js
代码演示:
// 1.引入 http 模块 const http = require('http'); // 引入模块 const fs = require('fs'); const path = require('path'); // console.log(http); const extToContentType = { '.html': 'text/html;charset=utf-8', '.css': 'text/css;charset=utf-8', '.js': 'application/javascript', '.jpg': 'image/jpg', } // 2.创建服务 // request:本次请求 // res : 本次响应 const server = http.createServer((req, res) => { // 回调函数 : 每一次收到请求,他就执行一次 // const { url } = req; // console.log(url); // const pathFile = path.join(__dirname, 'public-content-type', url); // console.log(pathFile); // const conter = fs.readFileSync() // 解构赋值 const { url } = req; // console.log(url); // 获取路径 const pathFile = path.join(__dirname, 'public', url); // console.log(pathFile); // 获取后缀名 const ext = path.extname(pathFile); // console.log(ext); if (extToContentType[ext]) { // const conter = fs.readFileSync(pathFile); res.setHeader('content-type', extToContentType[ext]) res.end(conter); } else { res.statusCode = 404; res.end("请求不到数据", '404') } }); // 3.启动服务 server.listen(8001, () => { console.log('服务器启动成功'); })
11.理解静态资源与接口的区别
服务器上有很多的资源,每个资源都有自己的url。客户端浏览器想要访问某个资源就要向服务器发起对应的请求。
11.1资源的分类
- 静态资源
- 它们一般表现为一个一个的文件。例如index.html, style.css, index.js, mp4, .png…。
- 处理请求静态资源时,服务器一般就直接读出资源的内容,再返回给客户端浏览器
- 动态资源:接口
- 它们不是以某个具体的文件存在的,而是服务器上的一段代码,访问接口时,服务器会执行这段代码,然后把代码的执行结果返回给客户端浏览器。
11.2发送请求的途径
- 在地址栏中直接访问这个url
- 通过某个a标签进行进行跳转
- 通过表单进行提交
- 通过ajax技术访问这个url
11.3发送请求的类型
- get:在地址栏中直接访问这个url就是get方式。对于静态资源,我们通过的处理方式就是get请求。
- post: 通过表单提交,可以设置form的method为post
- delete
- put
- patch
- options
- …
12.写一个不带任何参数的get类型接口
12.1目标
提供一个名为getList的接口(http://localhost:8083/getList),它以json字符串格式返回
db/data.json
的内容。12.2目录结构
|-db |---data.json # 数据 |-server.js # 你的代码
12.3代码演示
// 三要素 // 1.引入 http 模块 const http = require('http'); // console.log(http); const path = require('path'); // 引入模块 const fs = require('fs'); // 2.创建服务 // request:本次请求 // res : 本次响应 const server = http.createServer((req, res) => { // 回调函数 : 每一次收到请求,他就执行一次 // 1. 当你的请求是get类型,并且地址是/getList时,解析查询字符串 // 设置响应体 // res.setHeader('content-type','text/html;charset=utf-8') const { url, method } = req; console.log(url, method); if (url === "/getList" && method === "GET") { const pathFile = path.join(__dirname, "db", 'data.json'); console.log(pathFile); res.setHeader('content-type','application/json;charset=utf8') const conter = fs.readFileSync(pathFile) res.end(conter); } else { res.statusCode = 404; res.end("404") } }); // 3.启动服务 server.listen(8452, () => { console.log('服务器启动成功'); });
注意:类型
- req.method 可以判断请求的类型
- res.end()的参数只能是字符串(或者是buffer),而不能是对象
13.写一个不带任何参数的get类型接口
13.1目标
提供一个名为getList?name=abc的接口(http://localhost:8083/getList?name=abc),它以json字符串格式返回
db/data.json
的内容。13.2目录结构
|-db |---data.json # 数据 |-server.js # 你的代码
13.3代码演示:
// 1.引入 http 模块 const http = require('http'); // console.log(http); // 引入模块 const fs = require('fs'); const path = require('path'); const ps = require("querystring"); // 2.创建服务 // request:本次请求 // res : 本次响应 const server = http.createServer((req, res) => { // 回调函数 : 每一次收到请求,他就执行一次 const [url, queryStr] = req.url.split("?"); if (url === "/getList" && req.method === "GET") { const qsObj = ps.parse(queryStr) console.log(qsObj); const pathFile = path.join(__dirname, "db", "data.json") res.setHeader('content-type', 'application/json;charset=utf8') const conter = fs.readFileSync(pathFile) const arr = JSON.parse(conter) const re = arr.find(function (item) { if(item.name === qsObj.name){ return true; }else{ return false; } }) const resa = JSON.stringify(re) console.log(resa); res.end(resa) } else { res.end('你好'); } // 结束本次请求 // 设置响应体:返回给用户看的 }); // 3.启动服务 server.listen(8004, () => { console.log('服务器启动成功'); })
-
-
配置IIS express失败未找到web服务器“http://localhost”
2017-10-12 09:49:17VS2013新建asp.net网站时配置IIS express失败,未找到web服务器“http://localhost” 求各位大佬,谢谢 -
简单web服务器的实现(C++)
2018-09-20 20:41:25一、具体功能实现 GET方法请求解析 POST方法请求解析 返回请求资源页面 利用GET方法实现加减法 利用POST方法实现加减法 ... 400、403、404错误码返回的处理 注意:!...本人也是小白一只,这是刚刚开始...二、什么...一、具体功能实现
- GET方法请求解析
- POST方法请求解析
- 返回请求资源页面
- 利用GET方法实现加减法
- 利用POST方法实现加减法
- HTTP请求行具体解析
- 400、403、404错误码返回的处理
注意:!!本人也是小白一只,这是刚刚开始学习网络编程写的东西,存在很多问题。也未用到RAII等机制,纯属是披着C++皮的C语言项目,类的封装也不是太好。也未经过压力、性能等测试。
二、什么是web服务器
- web服务器就是在物理服务器基础上的具有服务端功能的网络连接程序,简而言之就是处理客户端发来的各种请求然后根据服务器的逻辑处理返回一个结果给客户端。在web服务器和客户端之间的通信是基于HTTP协议进行的。而客户端可以是浏览器也可以是支持HTTP协议的APP。
- 那么浏览器应该怎么连接上自己的web服务器呢,最简单的web服务器就是通过TCP三次握手建立连接后,服务器直接返回一个结果给浏览器。浏览器和服务器是通过TCP三路握手建立连接的。浏览器在通过URL(统一资源定位符,就是我们俗称的网络地址)去请求服务器的连接,并且通过URL中的路径请求服务器上的资源。举个栗子就是这样的:
最简单的web服务器:
#include<stdio.h> #include<stdlib.h> #include<sys/socket.h> #include<sys/types.h> #include<sys/stat.h> #include<sys/sendfile.h> #include<fcntl.h> #include<netinet/in.h> #include<arpa/inet.h> #include<assert.h> #include<unistd.h> #include<string.h> const int port = 8888; int main(int argc,char *argv[]) { if(argc<0) { printf("need two canshu\n"); return 1; } int sock; int connfd; struct sockaddr_in sever_address; bzero(&sever_address,sizeof(sever_address)); sever_address.sin_family = PF_INET; sever_address.sin_addr.s_addr = htons(INADDR_ANY); sever_address.sin_port = htons(8888); sock = socket(AF_INET,SOCK_STREAM,0); assert(sock>=0); int ret = bind(sock, (struct sockaddr*)&sever_address,sizeof(sever_address)); assert(ret != -1); ret = listen(sock,1); assert(ret != -1); while(1) { struct sockaddr_in client; socklen_t client_addrlength = sizeof(client); connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength); if(connfd<0) { printf("errno\n"); } else{ char request[1024]; recv(connfd,request,1024,0); request[strlen(request)+1]='\0'; printf("%s\n",request); printf("successeful!\n"); char buf[520]="HTTP/1.1 200 ok\r\nconnection: close\r\n\r\n";//HTTP响应 int s = send(connfd,buf,strlen(buf),0);//发送响应 //printf("send=%d\n",s); int fd = open("hello.html",O_RDONLY);//消息体 sendfile(connfd,fd,NULL,2500);//零拷贝发送消息体 close(fd); close(connfd); } } return 0; }
最简单的html文件:
<html> <body bgcolor="blue"> this is the html. <hr> <p>hello word! waste young! </p><br> </body> </html>
运行web.c文件,生成执行文件a.out,在终端执行后,我们在浏览器的网址栏中输入:http://localhost:8888 然后确认后,就会返回hello.html的文件页面
这里的URL,localhost:实际就是hostname,然后8888是端口,如果在端口后面再加上比如/hello.html这样的路径就表示请求服务器上的一个hello.html,这里请求方法是GET,所以要求服务器返回该资源的页面。
那么此时再来看下服务器接收到的东西,就是HTTP请求。
第一行就是请求行,请求行的格式是这样的:请求方法+空格+URL+空格+协议版本+\r+\n 这里的请求方法是GET ,URL是/(在这里,URL就相当于资源的路径,若在网址栏输入的是http://localhost:8888/hello.html的话,这里浏览器发送过来的URL就是/hello.html),协议版本是HTTP/1.1(现在多数协议版本都是这个)。
第二行到最后一行都是请求头部,请求头部的格式是这样的: 头部字段:+空格+数值+\r+\n 然后多个头部子段组织起来就是请求头部,在最后的头部字段的格式中需要有两个换行符号,最后一行的格式是:头部字段:+空格+数值+\r+\n+\r+\n 因为在后面还要跟着请求数据,为了区分请求数据和请求头的结束,就多了一个换行符。
三、HTTP请求和响应
(1)HTTP请求
简而言之就是客户端发送给服务端的请求。请求格式上面略提到了一点点,大概的格式就如下所示:
其中的细节就很多了,但是主要的是请求方法。其中头部字段有很多,大家可以上网百度。主要实现的就是GET方法和POST方法,其中GET方法是请求资源,但是不改变服务器上资源的,POST方法的话就会请求更改服务器上的资源。除了这两个方法外,还有PUT,DELETE,HEAD,TRACE等等。对应增删查改的就是PUT、DELETE、POST、GET。
然后URL就是要请求的资源路径,协议版本为HTTP/1.1,头部字段根据每个头部字段名都代表着给服务器的一个信息,具体可以根据以下网址查看:https://blog.csdn.net/sinat_22840937/article/details/64438253
(2)HTTP响应
HTTP响应就是服务端返回给客户端的响应消息。响应格式大概如下:
其中响应首行格式如:HTTP/1.1+状态响应码+\r\n 状态响应码参考如下:https://baike.baidu.com/item/HTTP状态码/5053660?fr=aladdin
这里大概用的是200,400,403,404,其中头部字段需要注意content-length,在服务器中响应码若没有消息题的长度,浏览器就只能通过关闭客户端才可以得知消息体的长度,才可以显示出消息体的具体表现。而且消息体的长度必须要和消息体吻合。如果服务端发送的消息体长度不正确的话,会导致超时或者浏览器一直显示不了要的资源文件。详细可以参考博客:https://www.cnblogs.com/lovelacelee/p/5385683.html
四、如何写出小型 web服务器
1、代码预备知识
- 了解TCP三次握手和TCP四次挥手
- 线程同步机制包装类
- 线程池创建
- epoll多路复用
(1)TCP三次握手
- 服务器需要准备好接受外来连接,通过socket bind listen三个函数完成,然后我们称为被动打开。
- 客户则通过connect发起主动连接请求,这就导致客户TCP发送一个SYN(同步)分节去告诉服务器客户将在待建立的连接中发送的数据的初始序列号,通常SYN不携带数据,其所在IP数据只有一个IP首部,一个TCP首部以及可能有的TCP选项。
- 服务器确认客户的SYN后,同时自己也要发送一个SYN分节,它含有服务器将在同一个连接中发送的数据的初始化列序号,服务器在单个分节中发送SYN和对客户SYN的确认
- 客户必须去确认服务器的SYN
(2)TCP四次挥手
- 某一个应用进程首先调用close,称为该端执行主动关闭,该端的TCP会发送一个FIN分节,表示数据已经发送完毕
- 接到FIN的对端将执行被动关闭,这个FIN由TCP确认,它的接受也作为一个文件结束符传递给接收端应用进程(放在已排队等候该应用进程接收的任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应连接上已无额外数据可以接收
- 一段时间后,接收到这个文件结束符的应用进程会调用close关闭它的套接字,这会导致它的TCP也要发送一个FIN
- 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN
参考网站:https://www.cnblogs.com/Andya/p/7272462.html
(3)线程池的创建
我用的是半同步/半反应堆线程池。该线程池通用性比较高,主线程一般往工作队列中加入任务,然后工作线程等待后并通过竞争关系从工作队列中取出任务并且执行。而且应用到服务器程序中的话要保证客户请求都是无状态的,因为同一个连接上的不同请求可能会由不同的线程处理。
ps:若工作队列为空,则线程就处于等待状态,就需要同步机制的处理。
代码:
#ifndef _THREADPOOL_H #define _THREADPOOL_H #include<iostream> #include<list> #include<cstdio> #include<semaphore.h> #include<exception> #include<pthread.h> #include"myhttp_coon.h" #include"mylock.h" using namespace std; template<typename T> /*线程池的封装*/ class threadpool { private: int max_thread;//线程池中的最大线程总数 int max_job;//工作队列的最大总数 pthread_t *pthread_poll;//线程池数组 std::list<T*> m_myworkqueue;//请求队列 mylocker m_queuelocker;//保护请求队列的互斥锁 sem m_queuestat;//由信号量来判断是否有任务需要处理 bool m_stop;;//是否结束线程 public: threadpool(); ~threadpool(); bool addjob(T* request); private: static void* worker(void *arg); void run(); }; /*线程池的创建*/ template <typename T> threadpool<T> :: threadpool() { max_thread = 8; max_job = 1000; m_stop = false; pthread_poll = new pthread_t[max_thread];//为线程池开辟空间 if(!pthread_poll) { throw std::exception(); } for(int i=0; i<max_thread; i++) { cout << "Create the pthread:" << i << endl; if(pthread_create(pthread_poll+i, NULL, worker, this)!=0) { delete [] pthread_poll; throw std::exception(); } if(pthread_detach(pthread_poll[i]))//将线程分离 { delete [] pthread_poll; throw std::exception(); } } } template <typename T> threadpool<T>::~threadpool() { delete[] pthread_poll; m_stop = true; } template <typename T> bool threadpool<T>::addjob(T* request) { m_queuelocker.lock(); if(m_myworkqueue.size()> max_job)//如果请求队列大于了最大请求队列,则出错 { m_queuelocker.unlock(); return false; } m_myworkqueue.push_back(request);//将请求加入到请求队列中 m_queuelocker.unlock(); m_queuestat.post();//将信号量增加1 return true; } template <typename T> void* threadpool<T>::worker(void *arg) { threadpool *pool = (threadpool*)arg; pool->run(); return pool; } template <typename T> void threadpool<T> :: run() { while(!m_stop) { m_queuestat.wait();//信号量减1,直到为0的时候线程挂起等待 m_queuelocker.lock(); if(m_myworkqueue.empty()) { m_queuelocker.unlock(); continue; } T* request = m_myworkqueue.front(); m_myworkqueue.pop_front(); m_queuelocker.unlock(); if(!request) { continue; } request->doit();//执行工作队列 } } #endif
(4)同步机制的包装类
因为采用了线程池,就相当于用了多线程编程,此时就需要考虑各个线程对公共资源的访问的限制,因为方便之后的代码采用了三种包装机制,分别是信号量的类,互斥锁的类和条件变量的类。在服务器中我使用的是信号量的类。其中信号量的原理和System V IPC信号量一样(不抄书了,直接拍照了。。。)
代码实现:
#ifndef _MYLOCK_H #define _MYLOCK_H #include<iostream> #include<list> #include<cstdio> #include<semaphore.h> #include<exception> #include<pthread.h> #include"myhttp_coon.h" using namespace std; /*封装信号量*/ class sem{ private: sem_t m_sem; public: sem(); ~sem(); bool wait();//等待信号量 bool post();//增加信号量 }; //创建信号量 sem :: sem() { if(sem_init(&m_sem,0,0) != 0) { throw std ::exception(); } } //销毁信号量 sem :: ~sem() { sem_destroy(&m_sem); } //等待信号量 bool sem::wait() { return sem_wait(&m_sem) == 0; } //增加信号量 bool sem::post() { return sem_post(&m_sem) == 0; } /*封装互斥锁*/ class mylocker{ private: pthread_mutex_t m_mutex; public: mylocker(); ~mylocker(); bool lock(); bool unlock(); }; mylocker::mylocker() { if(pthread_mutex_init(&m_mutex, NULL) != 0) { throw std::exception(); } } mylocker::~mylocker() { pthread_mutex_destroy(&m_mutex); } /*上锁*/ bool mylocker::lock() { return pthread_mutex_lock(&m_mutex)==0; } /*解除锁*/ bool mylocker::unlock() { return pthread_mutex_unlock(&m_mutex) == 0; } /*封装条件变量*/ class mycond{ private: pthread_mutex_t m_mutex; pthread_cond_t m_cond; public: mycond(); ~mycond(); bool wait(); bool signal(); }; mycond::mycond() { if(pthread_mutex_init(&m_mutex,NULL)!=0) { throw std::exception(); } if(pthread_cond_init(&m_cond, NULL)!=0) { throw std::exception(); } } mycond::~mycond() { pthread_mutex_destroy(&m_mutex); pthread_cond_destroy(&m_cond); } /*等待条件变量*/ bool mycond::wait() { int ret; pthread_mutex_lock(&m_mutex); ret = pthread_cond_wait(&m_cond,&m_mutex); pthread_mutex_unlock(&m_mutex); return ret == 0; } /*唤醒等待条件变量的线程*/ bool mycond::signal() { return pthread_cond_signal(&m_cond) == 0; } #endif
(5)epoll多路复用
epoll系列系统调用函数(#include<sys/epoll.h>):
int epoll_create(int size);创建内核事件表
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);操作epoll的内核事件表
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);一段时间内等待一组文件描述符上的就绪事件
除此这些函数外,还需要了解epoll的LT模式和ET模式还有EPOLLONESHOT事件.
下面三篇博客了解下:?
https://blog.csdn.net/davidsguo008/article/details/73556811
https://blog.csdn.net/men_wen/article/details/53456491
https://blog.csdn.net/yusiguyuan/article/details/15027821
代码:
#include<iostream> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdio.h> #include<errno.h> #include<string.h> #include<fcntl.h> #include<stdlib.h> #include<assert.h> #include<sys/epoll.h> #include"threadpool.h" //#include"myhttp_coon.h" using namespace std; const int port = 8888; int setnonblocking(int fd) { int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option; } void addfd(int epfd, int fd, bool flag) { epoll_event ev; ev.data.fd = fd; ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP; if(flag) { ev.events = ev.events | EPOLLONESHOT; } epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); setnonblocking(fd); } int main(int argc, char *argv[]) { threadpool<http_coon>* pool = NULL; pool = new threadpool<http_coon>; http_coon* users = new http_coon[100]; assert(users); struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = htons(INADDR_ANY); int listenfd = socket(AF_INET,SOCK_STREAM,0); assert(listenfd >= 0); int ret; ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); assert(ret != -1); ret = listen(listenfd,5); assert(ret >= 0); int epfd; epoll_event events[1000]; epfd = epoll_create(5); assert(epfd != -1); addfd(epfd, listenfd, false);//listen不能注册EPOLLONESHOT事件,否则只能处理一个客户连接 while(true) { int number = epoll_wait(epfd, events, 1000, -1); if( (number < 0) && (errno != EINTR) ) { printf("my epoll is failure!\n"); break; } for(int i=0; i<number; i++) { int sockfd = events[i].data.fd; if(sockfd == listenfd)//有新用户连接 { struct sockaddr_in client_address; socklen_t client_addresslength = sizeof(client_address); int client_fd = accept(listenfd,(struct sockaddr*)&client_address, &client_addresslength); if(client_fd < 0) { printf("errno is %d\n",errno); continue; } /*如果连接用户超过了预定于的用户总数,则抛出异常*/ /* if(http_coon::m_user_count > MAX_FD) { show_error(client_fd, "Internal sever busy"); continue; }*/ //初始化客户连接 cout << epfd << " " << client_fd << endl; addfd(epfd, client_fd, true); cout << "client_fd:" << client_fd << "****\n"; users[client_fd].init(epfd,client_fd); } else if(events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { /*出现异常则关闭客户端连接*/ users[sockfd].close_coon(); } else if(events[i].events & EPOLLIN)//可以读取 { if(users[sockfd].myread()) { /*读取成功则添加任务队列*/ pool->addjob(users+sockfd); } else{ users[sockfd].close_coon(); } } else if(events[i].events & EPOLLOUT)//可写入 { if(!users[sockfd].mywrite()) { users[sockfd].close_coon(); } } } } close(epfd); close(listenfd); delete[] users; delete pool; return 0; }
2、主要逻辑思路
- 首先创建和客户端的连接
- 服务器通过客户端的HTTP请求解析来判断返回何种结果.HTTP解析是以行为单位的,前提条件是根据\r\n来判断是否完整度入一行,若完整读入一行了那么就可以进行解析了。
- 通过HTTP请求的解析后,在写缓冲区写如HTTP响应,发送给客户端(HTTP应答包括一个状态行,多个头部字段,一个空行和资源内容,其中前三个部分的内容一般会被web服务器放置在一块内存中,而文档的内容通常会被放到另一个单独的内存中)
- 发送响应首行后,就可以发送主要的消息体了
主要就是封装在myhttp_coon.h中:
#ifndef _MYHTTP_COON_H #define _MYHTTP_COON_H #include<iostream> #include<stdio.h> #include<string.h> #include<sys/wait.h> #include<sys/socket.h> #include<errno.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<sys/sendfile.h> #include<sys/epoll.h> #include<sys/fcntl.h> #include<sys/stat.h> #include<sys/types.h> using namespace std; #define READ_BUF 2000 class http_coon{ public: /*NO_REQUESTION是代表请求不完整,需要客户继续输入;BAD_REQUESTION是HTTP请求语法不正确;GET_REQUESTION代表获得并且解析了一个正确的HTTP请求;FORBIDDEN_REQUESTION是代表访问资源的权限有问题;FILE_REQUESTION代表GET方法资源请求;INTERNAL_ERROR代表服务器自身问题;NOT_FOUND代表请求的资源文件不存在;DYNAMIC_FILE表示是一个动态请求;POST_FILE表示获得一个以POST方式请求的HTTP请求*/ enum HTTP_CODE{NO_REQUESTION, GET_REQUESTION, BAD_REQUESTION, FORBIDDEN_REQUESTION,FILE_REQUESTION,INTERNAL_ERROR,NOT_FOUND,DYNAMIC_FILE,POST_FILE}; /*HTTP请求解析的状态转移。HEAD表示解析头部信息,REQUESTION表示解析请求行*/ enum CHECK_STATUS{HEAD,REQUESTION}; private: char requst_head_buf[1000];//响应头的填充 char post_buf[1000];//Post请求的读缓冲区 char read_buf[READ_BUF];//客户端的http请求读取 char filename[250];//文件总目录 int file_size;//文件大小 int check_index;//目前检测到的位置 int read_buf_len;//读取缓冲区的大小 char *method;//请求方法 char *url;//文件名称 char *version;//协议版本 char *argv;//动态请求参数 bool m_linger;//是否保持连接 int m_http_count;//http长度 char *m_host;//主机名记录 char path_400[17];//出错码400打开的文件名缓冲区 char path_403[23];//出错码403打开返回的文件名缓冲区 char path_404[40];//出错码404对应文件名缓冲区 char message[1000];//响应消息体缓冲区 char body[2000];//post响应消息体缓冲区 CHECK_STATUS status;//状态转移 bool m_flag;//true表示是动态请求,反之是静态请求 public: int epfd; int client_fd; int read_count; http_coon(); ~http_coon(); void init(int e_fd, int c_fd);//初始化 int myread();//读取请求 bool mywrite();//响应发送 void doit();//线程接口函数 void close_coon();//关闭客户端链接 private: HTTP_CODE analyse();//解析Http请求头的函数 int jude_line(int &check_index, int &read_buf_len);//该请求是否是完整的以行\r\n HTTP_CODE head_analyse(char *temp);//http请求头解析 HTTP_CODE requestion_analyse(char *temp);//http请求行解析 HTTP_CODE do_post();//对post请求中的参数进行解析 HTTP_CODE do_file();//对GET请求方法中的url 协议版本的分离 void modfd(int epfd, int sock, int ev);//改变socket为状态 void dynamic(char *filename, char *argv);//通过get方法进入的动态请求处理 void post_respond();//POST请求响应填充 bool bad_respond();//语法错误请求响应填充 bool forbiden_respond();//资源权限限制请求响应的填充 bool succeessful_respond();//解析成功请求响应填充 bool not_found_request();//资源不存在请求响应填充 }; void http_coon::init(int e_fd, int c_fd) { epfd = e_fd; client_fd = c_fd; read_count = 0; m_flag = false; } http_coon::http_coon() { } http_coon::~http_coon() { } /*关闭客户端链接*/ void http_coon::close_coon() { epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, 0); close(client_fd); client_fd = -1; } /*改变事件表中的事件属性*/ void http_coon::modfd(int epfd, int client_fd, int ev) { epoll_event event; event.data.fd = client_fd; event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP; epoll_ctl(epfd, EPOLL_CTL_MOD, client_fd, &event); } /*read函数的封装*/ int http_coon::myread() { bzero(&read_buf,sizeof(read_buf)); while(true) { int ret = recv(client_fd, read_buf+read_count, READ_BUF-read_count, 0 ); if(ret == -1) { if(errno == EAGAIN || errno == EWOULDBLOCK)//读取结束 { break; } return 0; } else if(ret == 0) { return 0; } read_count = read_count + ret; } strcpy(post_buf,read_buf); return 1; } /*响应状态的填充,这里返回可以不为bool类型*/ bool http_coon::succeessful_respond()//200 { m_flag = false; bzero(requst_head_buf,sizeof(requst_head_buf)); sprintf(requst_head_buf,"HTTP/1.1 200 ok\r\nConnection: close\r\ncontent-length:%d\r\n\r\n",file_size); } bool http_coon::bad_respond()//400 { bzero(url, strlen(url)); strcpy(path_400,"bad_respond.html"); url = path_400; bzero(filename,sizeof(filename)); sprintf(filename,"/home/jialuhu/linux_net/web_sever/%s",url); struct stat my_file; if(stat(filename,&my_file)<0) { cout << "文件不存在\n"; } file_size = my_file.st_size; bzero(requst_head_buf,sizeof(requst_head_buf)); sprintf(requst_head_buf,"HTTP/1.1 400 BAD_REQUESTION\r\nConnection: close\r\ncontent-length:%d\r\n\r\n",file_size); } bool http_coon::forbiden_respond()//403 { bzero(url, strlen(url)); strcpy(path_403,"forbidden_request.html"); url = path_403; bzero(filename,sizeof(filename)); sprintf(filename,"/home/jialuhu/linux_net/web_sever/%s",url); struct stat my_file; if(stat(filename,&my_file)<0) { cout << "失败\n"; } file_size = my_file.st_size; bzero(requst_head_buf,sizeof(requst_head_buf)); sprintf(requst_head_buf,"HTTP/1.1 403 FORBIDDEN\r\nConnection: close\r\ncontent-length:%d\r\n\r\n",file_size); } bool http_coon::not_found_request()//404 { bzero(url, strlen(url)); strcpy(path_404,"not_found_request.html"); url = path_404; bzero(filename,sizeof(filename)); sprintf(filename,"/home/jialuhu/linux_net/web_sever/%s",url); struct stat my_file; if(stat(filename,&my_file)<0) { cout << "草拟\n"; } file_size = my_file.st_size; bzero(requst_head_buf,sizeof(requst_head_buf)); sprintf(requst_head_buf,"HTTP/1.1 404 NOT_FOUND\r\nConnection: close\r\ncontent-length:%d\r\n\r\n",file_size); } /*动态请求处理*/ void http_coon::dynamic(char *filename, char *argv) { int len = strlen(argv); int k = 0; int number[2]; int sum=0; m_flag = true; bzero(requst_head_buf,sizeof(requst_head_buf)); sscanf(argv,"a=%d&b=%d",&number[0],&number[1]); if(strcmp(filename,"/add")==0) { sum = number[0] + number[1]; sprintf(body,"<html><body>\r\n<p>%d + %d = %d </p><hr>\r\n</body></html>\r\n",number[0],number[1],sum); sprintf(requst_head_buf,"HTTP/1.1 200 ok\r\nConnection: close\r\ncontent-length: %d\r\n\r\n",strlen(body)); } else if(strcmp(filename,"/multiplication")==0) { cout << "\t\t\t\tmultiplication\n\n"; sum = number[0]*number[1]; sprintf(body,"<html><body>\r\n<p>%d * %d = %d </p><hr>\r\n</body></html>\r\n",number[0],number[1],sum); sprintf(requst_head_buf,"HTTP/1.1 200 ok\r\nConnection: close\r\ncontent-length: %d\r\n\r\n",strlen(body)); } } /*POST请求处理*/ void http_coon::post_respond() { if(fork()==0) { dup2(client_fd,STDOUT_FILENO); execl(filename,argv,NULL); } wait(NULL); } /*判断一行是否读取完整*/ int http_coon::jude_line(int &check_index, int &read_buf_len) { cout << read_buf << endl; char ch; for( ; check_index<read_buf_len; check_index++) { ch = read_buf[check_index]; if(ch == '\r' && check_index+1<read_buf_len && read_buf[check_index+1]=='\n') { read_buf[check_index++] = '\0'; read_buf[check_index++] = '\0'; return 1;//完整读入一行 } if(ch == '\r' && check_index+1==read_buf_len) { return 0; } if(ch == '\n') { if(check_index>1 && read_buf[check_index-1]=='\r') { read_buf[check_index-1] = '\0'; read_buf[check_index++] = '\0'; return 1; } else{ return 0; } } } return 0; } /*解析请求行*/ http_coon::HTTP_CODE http_coon::requestion_analyse(char *temp) { char *p = temp; cout << "p=" << p << endl; for(int i=0; i<2; i++) { if(i==0) { method = p;//请求方法保存 int j = 0; while((*p != ' ') && (*p != '\r')) { p++; } p[0] = '\0'; p++; cout << "method:" <<method << endl; // method++; } if(i==1) { url = p;//文件路径保存 while((*p != ' ') && (*p != '\r')) { p++; } p[0] = '\0'; p++; cout << "url:" << url << endl; } } version = p;//请求协议保存 while(*p != '\r') { p++; } p[0] = '\0'; p++; p[0] = '\0'; p++; cout << version << endl; if(strcmp(method,"GET")!=0&&strcmp(method,"POST")!=0) { return BAD_REQUESTION; } if(!url || url[0]!='/') { return BAD_REQUESTION; } if(strcmp(version,"HTTP/1.1")!=0) { return BAD_REQUESTION; } status = HEAD;//状态转移到解析头部 return NO_REQUESTION;//继续解析 } /*解析头部信息*/ http_coon::HTTP_CODE http_coon::head_analyse(char *temp) { if(temp[0]=='\0') { //获得一个完整http请求 return GET_REQUESTION; } //处理其他头部 else if(strncasecmp(temp,"Connection:", 11) == 0) { temp = temp+11; while(*temp==' ') { temp++; } if(strcasecmp(temp, "keep-alive") == 0) { m_linger = true; } } else if(strncasecmp(temp,"Content-Length:", 15)==0) { temp = temp+15; while(*temp==' ') { cout << *temp << endl; temp++; } m_http_count = atol(temp);//content-length需要填充 } else if(strncasecmp(temp,"Host:",5)==0) { temp = temp+5; while(*temp==' ') { temp++; } m_host = temp; } else{ cout << "can't handle it's hand\n"; } return NO_REQUESTION; } http_coon::HTTP_CODE http_coon::do_file()//GET方法请求,对其请求行进行解析,存写资源路径 { char path[40]="/home/jialuhu/linux_net/web_sever"; char* ch; if(ch=strchr(url,'?')) { argv = ch+1; *ch = '\0'; strcpy(filename,url); return DYNAMIC_FILE; } else{ strcpy(filename,path); strcat(filename,url); struct stat m_file_stat; if(stat(filename, &m_file_stat) < 0) { //cout << "打不开\n"; return NOT_FOUND;//NOT_FOUND 404 } if( !(m_file_stat.st_mode & S_IROTH))//FORBIDDEN_REQUESTION 403 { return FORBIDDEN_REQUESTION; } if(S_ISDIR(m_file_stat.st_mode)) { return BAD_REQUESTION;//BAD_REQUESTION 400 } file_size = m_file_stat.st_size; return FILE_REQUESTION; } } http_coon::HTTP_CODE http_coon::do_post()//POST方法请求,分解并且存入参数 { int k = 0; int star; char path[34]="/home/jialuhu/linux_net/web_sever"; strcpy(filename,path); strcat(filename,url); star = read_buf_len-m_http_count; argv = post_buf + star; argv[strlen(argv)+1]='\0'; if(filename!=NULL && argv!=NULL) { return POST_FILE; } return BAD_REQUESTION; } /*http请求解析*/ http_coon::HTTP_CODE http_coon::analyse() { status = REQUESTION; int flag; char *temp = read_buf; int star_line = 0; check_index = 0; int star = 0; read_buf_len = strlen(read_buf); int len = read_buf_len; while((flag=jude_line(check_index, len))==1) { temp = read_buf + star_line; star_line = check_index; switch(status) { case REQUESTION://请求行分析,包括文件名称和请求方法 { cout << "requestion\n"; int ret; ret = requestion_analyse(temp); if(ret==BAD_REQUESTION) { cout << "ret == BAD_REQUESTION\n"; //请求格式不正确 return BAD_REQUESTION; } break; } case HEAD://请求头的分析 { int ret; ret = head_analyse(temp); if(ret==GET_REQUESTION)//获取完整的HTTP请求 { if(strcmp(method,"GET")==0) { return do_file();//GET请求文件名分离函数 } else if(strcmp(method,"POST")==0) { return do_post();//POST请求参数分离函数 } else{ return BAD_REQUESTION; } } break; } default: { return INTERNAL_ERROR; } } } return NO_REQUESTION;//请求不完整,需要继续读入 } /*线程取出工作任务的接口函数*/ void http_coon::doit() { int choice = analyse();//根据解析请求头的结果做选择 switch(choice) { case NO_REQUESTION://请求不完整 { cout << "NO_REQUESTION\n"; /*改变epoll的属性*/ modfd(epfd, client_fd, EPOLLIN); return; } case BAD_REQUESTION: //400 { cout << "BAD_REQUESTION\n"; bad_respond(); modfd(epfd, client_fd, EPOLLOUT); break; } case FORBIDDEN_REQUESTION://403 { cout << "forbiden_respond\n"; forbiden_respond(); modfd(epfd, client_fd, EPOLLOUT); break; } case NOT_FOUND://404 { cout<<"not_found_request"<< endl; not_found_request(); modfd(epfd, client_fd, EPOLLOUT); break; } case FILE_REQUESTION://GET文件资源无问题 { cout << "文件file request\n"; succeessful_respond(); modfd(epfd, client_fd, EPOLLOUT); break; } case DYNAMIC_FILE://动态请求处理 { cout << "动态请求处理\n"; cout << filename << " " << argv << endl; dynamic(filename, argv); modfd(epfd, client_fd, EPOLLOUT); break; } case POST_FILE://POST 方法处理 { cout << "post_respond\n"; post_respond(); break; } default: { close_coon(); } } } bool http_coon::mywrite() { if(m_flag)//如果是动态请求,返回填充体 { int ret=send(client_fd,requst_head_buf,strlen(requst_head_buf),0); int r = send(client_fd,body,strlen(body),0); if(ret>0 && r>0) { return true; } } else{ int fd = open(filename,O_RDONLY); assert(fd != -1); int ret; ret = write(client_fd,requst_head_buf,strlen(requst_head_buf)); if(ret < 0) { close(fd); return false; } ret = sendfile(client_fd, fd, NULL, file_size); if(ret < 0) { close(fd); return false; } close(fd); return true; } return false; } #endif
其中两个附加功能加法和减法的实现(通过GET方法请求),以及POST方法请求的加法和减法的实现
- 动态请求是什么样子(GET)
sum.html文件:
<html> <head> <meta charset="utf-8"> <title>sum</title> </head> <body> <form action="add"> a: <input type="text" name="a"><br> b: <input type="text" name="b"><br> <input type="submit" value="提交"> </form> <p>点击"提交"按钮,表单数据将被发送到服务器上的“add”程序上。</p> </body> </html>
服务器收到的请求是这样的,首先是打开sum.html文件
然后在表单上提交要相加的两个数字
点击提交后,此时服务器收到的请求是这样的:
看到了/add?a=33&b=33 这就是通过方法GET提交上来的参数a和b ,此时我们在解析请求行的时候就可以通过问好来判断是否是GET的动态请求,若是那么根据sscanf()函数,分离出参数a和b,进行相加后就可以填充HTTP响应发送给浏览器了。此处我根据提交的程序名称来选择函数,在函数中相加填充返回给浏览器。当然我觉得正确的做法是重新写一个add.c然后执行生产add文件,再在fork()一个子线程通过execl( )函数去执行。
- 那么POST请求又是什么样子呢,其实POST请求将参数放在了请求
修改后的sum.html文件
<html> <head> <meta charset="utf-8"> <title>sum</title> </head> <body> <form action="add" method="post"> a: <input type="text" name="a"><br> b: <input type="text" name="b"><br> <input type="submit" value="提交"> </form> <p>点击"提交"按钮,表单数据将被发送到服务器上的“add”程序上。</p> </body> </html>
加入了属性method="post",此时打开sum.html文件依然是GET方法,只是点击提交表单后用的是POST方法。
和GET不同的是,参数被在请求的数据部分,也就是空行之后,此时若方法是POST的话,根据read_buf_len和Content_Length就可以求出参数在read_buf中的起始位置。然后又可以通过sscanf( )分离参数了,然后fork()一个进程,利用dup2函数,将标准输出重定向到浏览器的sockfd上,再执行execl( )函数。此时我们的add执行文件的.c文件如下:
#include<stdio.h> #include<string.h> int main(int argc, char *argv[]) { char re_head[1000]; char message[1000]; int ret; int a,b,result; ret = sscanf(argv[0],"a=%d&b=%d", &a, &b); //printf("a=%d\t b=%d\n",a,b); if(ret < 0 || ret != 2) { sprintf(message,"<html><body>\r\n"); sprintf(message,"%s<p>failure</p>\r\n",message); sprintf(message,"%s</body></html>"); sprintf(re_head,"HTTP/1.1 GET\r\n"); sprintf(re_head,"%scontent-length: %d\r\n",re_head,strlen(message)); sprintf(re_head,"%scontent-type: text/html\r\n",re_head); sprintf(re_head,"%sconection: close\r\n\r\n"); /*错误提示消息*/ } else{ result = a+b; /*返回正确信息*/ sprintf(message,"<html><body>\r\n"); sprintf(message,"%s<p>%d + %d = %d</p><br>\r\n",message,a,b,result); sprintf(message,"%s<p>welcome to the word of jialuhu</p><br>\r\n",message); sprintf(message,"%s</body></html>\r\n",message); sprintf(re_head,"HTTP/1.1 200 ok\r\n"); sprintf(re_head,"%sContent-length: %d\r\n",re_head,(int)strlen(message)); sprintf(re_head,"%scontent-type: text/html\r\n\r\n",re_head); // sprintf(re_head,"%sconection: close\r\n\r\n"); } printf("%s",re_head); printf("%s",message); fflush(stdout); return 0; }
当然除了加减法,还有很多功能可以去实现。此处就简单实现了这些功能。还有一些HTML文件,因为懒癌原因,所以随便写了几个。
五、总结
纵观博客其实感觉涉及的知识有点杂乱,但是很综合吧。首先满足代码上高性能的需求,利用了线城池和epoll多路复用,其中也包括同步机制的封装。其次就是HTTP这块的知识了,包括请求格式响应格式和请求方法和响应状态码,很多很多都是零零碎碎平凑一起的。而且感觉这个服务器的实现,也终于明白了浏览器和后台是怎么沟通交流的,有时候看不如动手实现下,很多东西就会突然明白了。大体模块就是epoll、线城池、同步机制、逻辑处理。代码里肯定也有很多没有测试出来的bug,但是实现大概三分之二后还是有丢丢开心的吧。
-
本机访问虚拟机Web服务器失败的解决办法
2016-05-23 14:12:18在虚拟机中搭建了LAMP,打算用宿主机的浏览器对虚拟机的web服务器进行访问,但是却出现了访问失败的问题,又到了查错的时候了,我对本机和虚拟机做了检查: 1.宿主机可以ping通虚拟机 2.虚拟机可以ping通宿...在虚拟机中搭建了LAMP,打算用宿主机的浏览器对虚拟机的web服务器进行访问,但是却出现了访问失败的问题,又到了查错的时候了,我对本机和虚拟机做了检查:
1.宿主机可以ping通虚拟机
2.虚拟机可以ping通宿主机
3.虚拟机可以通过http://localhost来访问web服务器
4.宿主机通过浏览器不能访问到web服务器
首先,我在宿主机的dos窗口下telnet虚拟机的80窗口,失败了,由此可以确定是虚拟机的80窗口有问题,应该是被防火墙堵住了。
找了好久,终于是找到了解决的办法了:
1.修改防火墙设置:在Shell下输入命令 /sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT
2.保存上面的设置:/etc/rc.d/init.d/iptables saved
3.重启防火墙:/etc/init.d/iptables restart
现在,在本机的浏览器输入虚拟机的IP地址,访问web服务器成功!!以下是访问到index.php文件显示的结果
-
C#实现WEB服务器
2019-07-04 19:46:16WWW的工作基于客户机/服务器计算模型,由Web浏览器(客户机)和Web服务器(服务器)构成,两者之间采用超文本传送协议(HTTP)进行通信。HTTP协议是基于TCP/IP协议之上的协议,是Web浏览器和Web服务器之间的应用层协议,... -
当Web服务器访问人数超过了设计访问人数上限,将可能出现的HTTP状态
2021-08-02 06:37:15【导读】华图河南人事考试网同步华图教育发布:当Web服务器访问...单选题当Web服务器访问人数超过了设计访问人数上限,将可能出现的HTTP状态码是什么?()A. 200K请求已成功,请求所希望的响应头或数据体将随此响应返... -
2.服务器部署web服务器
2021-11-01 15:00:09文章目录1.看图说话2.开始部署django3.遇到的坑4.声明 #前几天按照文档走了一道,也...django使用python作为开发语言,开发完毕作为web应用程序运行在web服务器上,但是我们选择的web服务器例如Nginx,uWSGI都不认识pyt -
【Unity3D日常开发】Unity3D中实现向Web服务器上传图片以及下载图片功能
2022-01-07 09:53:11今天分享一下从搭建web服务器,到向服务器发送图片,以及加载图片的整体实现。 因为是Demo演示,所以尽可能的简单、详细且实用,有什么错误敬请指正。 先看一下效果图: 文章参考:Unity向Web服务器上传和下载图片 ... -
[Linux CentOS7] Web服务器搭建和设置
2019-04-21 08:32:56VMware Linux CentOS7 Web服务器搭建和设置 -
关于安卓手机改装成Web服务器的尝试
2021-11-23 17:16:55关于安卓手机改装成Web服务器的尝试 手机型号:华为的畅享9 然后,我这几天尝试了网上的许多种方法把手机安装一个Linux系统,下面是几种可行的方法 方法一:Busybox + Linux Deplay(要root权限) 这个方法的先行... -
使用Golang 搭建http web服务器
2018-07-21 17:02:32Golang在搭建web服务器方面的能力是毋庸置疑的。官方已经有提供net/http包为搭建http服务器做准备。使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置。至于这 -
基于Nginx以及web服务器搭建在线视频播放
2022-04-20 14:31:35基于Nginx以及web服务器搭建在线视频播放 -
阿里云服务器Windows Server 2019 安装Web服务器(IIS)教程
2020-07-10 11:06:52最近买了一个阿里云Windows server 2019服务器来搭建云服务器,过程给大家分享一下互相...二、阿里云服务器Windows Server 2019 安装Web服务器(IIS) 1、点击【服务器管理器】 2、服务器管理器的仪表板,点击... -
【小5聊】VS运行调试报错无法连接到Web服务器IIS Express
2021-07-05 17:14:05无法连接到Web服务器IIS Express 原因:打开了多个相同的项目,并且端口一样,必须修改另一个 1、VS运行时,提示如下 2、修改项目属性端口 鼠标右键项目 > 点开调试 -
初识web服务器(汤姆猫)&HTTP
2020-05-20 00:02:061.服务器概述 1.1什么是服务器?...1.2什么是web服务器? web服务器是运行在互联网上的计算机程序 作用:用于接收客户端浏览器的请求,根据请求进行处理,最后将处理的结果响应给浏览器; ... -
本机访问虚拟机web服务器失败的解决办法
2015-05-12 18:51:33本机访问虚拟机web服务器失败的解决办法 中午在虚拟机中搭建了LAMP,打算用宿主机的浏览器对虚拟机的web服务器进行访问,但是却出现了访问失败的问题,又到了查错的时候了,我对本机和虚拟机做了检查,发现状况... -
linux搭建web服务器(网站)(不同场景中最简单的搭建方法),windows和linux域名解析方式说明
2020-10-20 12:36:47文章目录说明准备条件搭建一个默认web服务器最简单的方法搭建一个自定义index.html路径的web服务器添加web服务器路径修改默认监听端口创建一个动态web服务器 说明 常规情况下,只要安装了http服务,启动这个服务就会... -
在虚拟机中配置linux(web)服务器
2020-07-06 00:07:59接下来就是用户登录配置服务器压在root用户下进行然后关闭防火墙 关闭防火墙的命令 命令 : systemctl stop firewalld.service 查看防火墙状态命令 命令: systemctl stop firewalld.service 开启防火墙的命令 ... -
Web服务器配置
2018-04-28 12:45:07课程名称服务器配置与管理实验成绩 实验名称Web服务器配置学号 姓名;指导老师-龚蕾 班级 日期 实验目的:1.掌握liunx系统的基本命令2 掌握Web服务器配置的基本原理3.掌握Apache服务器的安装与配制方法和客户端... -
VS2019 无法连接到Web服务器“IIS Express”
2020-03-10 10:27:34git冲突 删除掉项目根文件夹下的隐藏文件.vs和.Git -
Linux下Apache Web服务器的安装与配置
2020-03-07 21:54:341.Apache Web服务器简述 Web服务是目前Internet应用最流行、最受欢迎的服务之一,Linux平台使用最广泛的Web服务器是Apache,它是目前性能最优秀、最稳定的Web服务器之一。 WWW(World Wide Web)服务... -
mqtt服务器—web服务器——web页面
2019-03-30 20:15:51web服务器------------>web页面 详情:mqtt服务器发布主题之后,客户端订阅主题,通过回调类拿到数据保存到数据库,接着web服务器读取数据库中的数据显示在页面。 大部分内容复制粘贴而来,加了一些自己的见解... -
教程 | 用安卓手机搭建 web 服务器(一)—— 应用安装配置
2018-07-04 16:47:28之前无意间看到了一篇《在安卓上部署服务器》的文章,正好最近换了新手机,正好最近在学 Python 和 HTML,就想着跟着教程搭一个服务器。 但是原博有些地方写的比较简单,有些有坑的地方没有提及,有些软件更新了... -
Web服务器配置与管理
2015-12-27 19:24:00Web 服务器配置与管理 一.目的: 主要目的是学习网络服务器配置,包括IIS 配置方法、 Apache 配置 ... (3)制作简单网页验证Web服务器配置情况。 三.环境: (1)WindowsXP/Windows Server 2003 -
Hadoop通过WEB上传文件到HDFS失败
2021-12-15 10:10:15没想到WEB也需要知道内部的主机名,不科学啊…… -
c语言实现简单web服务器
2018-01-11 17:26:54host主机地址:port端口/urlhost会被DNS服务器 解析成IP地址,所以有时候可以直接用域名,http默认访问80端口,https默认访问443端口大致流程就是:浏览器输入地址后,首先和web服务器建立tcp连接,然后浏览器发送http请求... -
从零开始教大家用Python一步步构建Web应用程序及其Web服务器
2018-12-31 22:50:24今天教大家在Python中从零开始构建Web应用程序及其Web服务器,所有内容完全依赖Python标准库,并且忽略WSGI标准。 更多Python视频、源码、资料加群683380553免费获取 Web服务器 第一步是编写能够为网络应用... -
AndServer,一个Android端的web服务器
2019-06-15 07:34:56大家好,今天跟大家介绍一个让原生Android也可以做Web...AndServer是一个Android端的Web服务器,类似Apache或者Tomcat,但又有不同,它是一个普通的Android Library,Android项目Gradle远程依赖或者添加Jar包皆可...