应用层网络实现一个服务器版的计算器及理解HTTP协议

2024-03-14 21:32

本文主要是介绍应用层网络实现一个服务器版的计算器及理解HTTP协议,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

应用层

满足我们日常需求的网络程序都在应用层。

协议是一种约定。socket api的接口,在读写数据时都是按照“字符串”的方式发送和接收的。但如果我们要传输一些“结构化的数据”该怎么办呢?例如,我们需要实现一个服务器版的加法器,客户端把要计算的两个加数发过去,然后由服务器进行计算,最后再把结果返回给客户端。这里有两个方案可以解决。

方案一:

  • 客户端发送一个形如“1+1”的字符串;
  • 这个字符串中有两个操作数,都是整形;
  • 两个数字之间有一个字符是运算符,运算符只能是+;
  • 数字和运算符之间没有空格。...

方案二:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串(此过程叫“序列化”,方便传输),接收到数据的时候再按照相同的规则把字符串转换回结构体(此过程叫“反序列化”,方便使用)。

相关代码:

Makefile:

.PHONY:all
all:client server
CC=gccclient:client.c$(CC) -o $@ $^ 
server:server.c$(CC) -o $@ $^ -lpthread.PHONY:clean
clean:rm -f client server

proto.h

typedef struct Request
{int x;int y;int op;
}Request;typedef struct Response
{int sum;int status;
}Response;

client.c

#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<error.h>
#include<string.h>
#include<sys/types.h>
#include<stdlib.h>
#include"proto.h"typedef struct sockaddr sockaddr;int main(int argc,char* argv[])
{if(argc!=3){printf("Usage:%s ip port\n",argv[0]);return 1;}//创建套接字int sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){perror("socket");return 2;}printf("Socket:%d\n",sock);struct sockaddr_in server;server.sin_family=AF_INET;server.sin_port=htons(atoi(argv[2]));server.sin_addr.s_addr=inet_addr(argv[1]);//建立连接if(connect(sock,(sockaddr*)&server,sizeof(server))<0){perror("connect");return 3;}char buf[128];Request rq;Response rp;while(1){printf("Please Enter<x,y>:");scanf("%d%d",&rq.x,&rq.y);fflush(stdout);printf("Please Enter op[1(+),2(-),3(*),4(/),5(%)]:");scanf("%d",&rq.op);write(sock,(void*)&rq,sizeof(rq));read(sock,&rp,sizeof(rp));printf("status:%d,result:%d\n",rp.status,rp.sum);}return 0;
}

server.c

#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/wait.h>
#include"proto.h"typedef struct sockaddr sockaddr;
typedef struct Arg
{int fd;
}Arg;void ProcessRequest(int sock)
{while(1){Response rp={0,0};Request rq;read(sock,&rq,sizeof(rq));switch(rq.op){case 1:rp.sum=rq.x+rq.y;break;case 2:rp.sum=rq.x-rq.y;break;case 3:rp.sum=rq.x*rq.y;break;case 4:if(rq.y==0){rp.status=-1;break;}else{rp.sum=rq.x/rq.y;break;}case 5:if(rq.y==0){rp.status=-2;break;}else{rp.sum=rq.x%rq.y;break;}default:rp.status=-3;break;}write(sock,&rp,sizeof(rp));}
}
void* service(void* ptr)
{Arg* arg=(Arg*)ptr;ProcessRequest(arg->fd);free(arg);return NULL;
}
int main(int argc,char* argv[])
{if(argc!=3){printf("Usage:%s ip port\n",argv[0]);exit(1);}//创建套接字int listen_sock=socket(AF_INET,SOCK_STREAM,0);if(listen_sock<0){perror("socket error");exit(2);}printf("Socket:%d\n",listen_sock);int opt=1;setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//命名套接字struct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(atoi(argv[2]));local.sin_addr.s_addr=htonl(INADDR_ANY);//绑定端口号if(bind(listen_sock,(sockaddr*)&local,sizeof(local))<0){perror("bind error");exit(3);}//开始监听if(listen(listen_sock,5)<0)//这里允许5个客户端连接等待,如果收到更多的请求则忽略{perror("listen error");exit(4);}//接受请求printf("bind and listen success,wait accept...\n");for(;;){struct sockaddr_in client;socklen_t len=sizeof(client);int new_sock=accept(listen_sock,(sockaddr*)&client,&len);if(new_sock<0){perror("accept error");}printf("get new link![%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));pthread_t id=0;Arg* arg=(Arg*)malloc(sizeof(Arg));arg->fd=new_sock;pthread_create(&id,NULL,service,&arg);pthread_detach(id);}return 0;
}

无论采用方案一还是方案二,或者其他方案,只要保证一端发送时构造的数据,在另一端能够正确地解析,就可以的,这种约定就是应用层协议。

HTTP协议

实际上,现在已经有大佬们定义了现成的有非常好用的应用层协议供我们直接参考使用,HTTP(超文本传输协议)就是其中之一。

认识URL:平时我们俗称的“网址”就是URL(统一资源定位器),是互联网上用来标识某一处资源的地址。例如http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name

1.协议部分:"http:"代表网页使用的是HTTP协议,后面的"//"为分隔符

2.域名部分:该URL的域名部分为"www.aspxfans.com"。一个URL中,也可以使用IP地址作为域名使用

3.端口部分:跟在域名后面的是端口,域名和端口之间使用 ":" 作为分隔符,端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口。

4.虚拟目录部分:从域名后的第一个"/"开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL中必须的部分,本例的虚拟目录为“/news/”

5.文件名部分:从域名后的最后一个“/”开始到“?”为止,是文件部分,如果没有”?“,则是从域名后的最后一个”/“开始到”#“为止,是文件部分。如果没有”?“和”#“,那么从域名后的最后一个”/“开始到结束,都是文件名部分。本例中文件名是"index.asp",文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件。

6.锚部分:从”#“开始到最后,都是锚部分。本例中锚部分是"name"。锚部分也不是一个URL必须的部分

7.参数部分:从”?“开始到”#“为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为"boardID=5&ID=24618&page=1"。参数可以允许有多个参数,参数与参数之间用"&"作为分隔符。

urlencode和urldecode

像/?:等字符,已经被URL当作特殊意义理解了。因此这些字符不能随意出项,如果某个参数中需要有这些特殊字符,就必须先对这些字符进行转义。

转义的规则如下:

将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY。例如,当我们在百度上搜索C++,网址栏上http://www.baidu.com/s?wd=c%2B%2B,其中,“+”被转义成“%2B”。

通过urlencode编码后的字符串,可通过urldecode进行解码。urldecode是urlencode的逆过程。

HTTP协议格式

HTTP请求

http请求中的格式:

  • 首行:[方法]+[url]+[版本]
  • Header:请求的属性,冒号分割的键值对,每组属性之间使用\n分隔,遇到空行表示Header部分结束。
  • Body:空行后面的内容都是Body,Body允许位空字符串,如果Body存在,则在Header中会有一个Content-Length属性来标识Body的长度。

HTTP响应

http响应中的格式:

  • 首行:[版本号]+[状态码]+[状态码解释]
  • 冒号分割的键值对,每组属性之间使用\n分隔,遇到空行表示Header部分结束。
  • 空行后面的内容都是Body,Body允许位空字符串,如果Body存在,则在Header中会有一个Content-Length属性来标识Body的长度。r如果服务器返回一个html页面,那么html页面内容就在Body中。

HTTP的方法

  • GET:获取资源。支持HTTP协议版本为1.0、1.1。GET是在url中传参,反映到url中,传的参数一般短小精悍,长度有限制。
  • POST:传输实体主体。支持HTTP协议版本为1.0、1.1。POST是在正文部分中传参,传的参数一般比较私密。表单一般用post更私密。
  • PUT:传输文件。支持HTTP协议版本为1.0、1.1。
  • HEAD:获得报文首部。支持HTTP协议版本为1.0、1.1。
  • DELETE:删除文件。支持HTTP协议版本为1.0、1.1。
  • OPTIONS:询问支持的方法。支持HTTP协议版本为1.1
  • TRACE:追踪路径。支持HTTP协议版本为1.1
  • CONNECT:要求用隧道协议连接代理。支持HTTP协议版本为1.1
  • LINK:建立和资源之间的联系。支持HTTP协议版本为1.0
  • UNLINE:断开连接关系。支持HTTP协议版本为1.0

注:post比get方法更私密,因为参数在正文中,在地址栏中看不到,但post方法不一定更安全。

HTTP有1.0和1.1版本,它们的区别就是1.1默认情况是长连接,而在1.0版本里需要通过头部Connection:Keep-alive来说明连接情况。如果不说明可能就是短连接。这里我介绍一下什么是短连接和长连接。

HTTP连接是在应用层协议,它是建立在传输层协议TCP协议和网络层IP协议上的。IP协议主要解决的是网络路由和寻址的问题,TCP协议主要是解决如何在IP层上可靠的传输数据包,使得发送端的数据都安全可靠的发送到接收端。HTTP的长短连接本质上是TCP的长连接和短连接。我们在用HTTP协议传输之前,需要先让发送端和接收端通过TCP协议连接起来,TCP三次握手成功后,我们就可以在TCP协议的基础上使用HTTP超文本传输协议来传输数据。当数据传输完成之后,就是长短连接做出区分的地方。

长连接比起短连接而言,省去了很多TCP连接创建和断开的时间,减少了资源的浪费。对于哪些服务器频繁请求客户端的操作可以采用长连接。但是这些连接不能一直不关闭,一直建立,如果这个连接存在周期过长,但是又不发送有效的请求,随着客户端连接服务器越来越多,服务器迟早会崩溃,所以我们经常会设置长连接的条件。因此长连接并不是越多越好,长连接确实会省去那些频繁发送请求的场景,但是长连接会用大量的系统资源,对于那些可能会有大量用户访问的网站常会采用短连接。

HTTP的状态码

 类别原因
1XXInformation(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

常见的状态码如:

200(OK),404(Not Found):服务器无法找到请求的页面或资源。403(Forbidden),301(Redirect):永久重定向。搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。302(Redirect):临时重定向。搜索引擎会抓取新的内容而保留旧的地址,因为服务器返回302,所以搜索引擎认为新的网址是暂时的。500(Internal Server Error):服务器内部错误,不能完成客户的请求。501(Not Implemented):尚未实施,或请求格式错误。

HTTP常见Header

  • Content-Type: 数据类型(text/html等);
  • Content-Length: Body的长度 ;
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能; 

Cookie是在本地存储的文件,存放在客户端,Cookie记录了用户的账号和密码等个人信息,从而方便下次访问。具体方法就是:当客户使用浏览器访问一个支持Cookie的网站的时候,客户会提供包括用户名在内的个人信息并且提交至服务器上;接着,服务器再向客户端回传相应的超文本的同时也会发回这些个人信息,这些信息并不放在HTTP响应体(Response Body)中,而是存放在HTTP响应头(Response Header);当客户端浏览器接受到来自服务器的响应后,浏览器会将这些信息存放到一个统一的位置。自此,客户端再向服务器发送请求时,都会把相应的Cookie再次发回至服务器。

但这种方法不安全,个人信息很容易被窃取。Session是一个对Cookie的解决方案,把Session放在服务器端更安全一些,因为服务器有专门的人员进行维护,出现问题的情况比较少。

因此http协议是无状态的协议,为了支持客户端与服务器之间的交互,我们就需要用Cookie和Session来解决。

http的核心工作就是把客户请求告诉服务器,服务器将相关资源的页面展示给客户。

这篇关于应用层网络实现一个服务器版的计算器及理解HTTP协议的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/809782

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言