网络基础—-应用层
应用层为应用程序提供服务并规定应用程序中相关细节。
我们所写的一个个解决实际问题的网络程序,都是在应用层。
应用层决定了向用户提供应用服务时通信的活动。
关于协议
协议即是一种“约定”,应用层协议即保证一端发送的数据另一端能够正确进行解析
常见的应用层协议
- DNS:域名解析协议(UDP)
- FTP:文件传送协议(C/S)(TCP)
- SMTP:简单邮件传输协议(TCP)
- HTTP:超文本传输协议(C/S)(TCP)
- TELNET:远程登陆(C/S)(TCP)
序列化和反序列化:
- 序列化:将数据结构、对象转换成二进制串的过程
- 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
序列化和反序列化的工具:
- xml:帮助格式化的组织数据,方便进行解析
优点:可读性好
缺点:空间和时间开销比较大,对性能要求高
适用场景:广泛用于配置文件中
格式:
<request>
<plusnum>1</plusnum>
<plusnum>2</plusnum>
</request>
Protobuf:
产生于谷歌,仅支持Java、C++、Python三种语言。
它具有空间开销小和高性能解析的优点,序列化后数据量相对少,适合公司内部对性能要求高的RPC调用。json:也是格式化的组织数据,与xml类似,在很多场景下可以取代xml
优点:比xml更简洁解析速度更快,实时性要求相对低
缺点:额外开销空间较大,不适合大数据量。
典型应用场景是JSON+HTTP,适合跨防火墙访问
格式:{ request:[ {plusnum:1}, //每个元素是一个键值对 {plusnum:2}, ] }
实现服务器版的加法器
约定方案一:
- 约定客户端发送的请求包含两个数字,这两个数字使用“,”分割
- 服务器给客户端相应的数据只包含一个数字
- 序列化和反序列化的过程需要程序员手动完成
约定方案二:
- 定义结构体来存储要交互的信息
- 发送数据时将结构体以二进制数据流的方式传出,提取数据时将其提取到结构体中
- 这个过程就是“序列化”和“反序列化”
构造结构体进行发送:
弊端:由于是二进制,不好调试
应用层协议的定制包含两个方面:
1.制定好请求中包含哪些信息,响应中包含哪些信息
2.制定好如何进行序列化和反序列化
定义公共模块comm.h
typedef struct Request
{
int a;
int b;
}Request;
typedef struct Response
{
int sum;
}Response;
服务器端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "proto.h"
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void ProcessRequest(const Request* req,Response* resp)
{
resp->sum=req->a+req->b;
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
printf("Usage:./plus_server [ip] [port]\n");
return 1;
}
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0)
{
perror("socket");
return 1;
}
sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(argv[1]);
addr.sin_port=htons(atoi(argv[2]));
int ret=bind(sock,(sockaddr*)&addr,sizeof(addr));
if(ret<0)
{
perror("bind");
return 1;
}
while(1)
{
sockaddr_in peer;
socklen_t len=sizeof(peer);
//1.读取客户端的请求,把读到的字符串转换成Request结构体(反序列化)
// 读到的客户端发送的字符串中包含两个加数
Request req;
recvfrom(sock,&req,sizeof(req),0,(sockaddr*)&peer,&len);
printf("REquest : a=%d b=%d\n",req.a,req.b);
//sscanf(buf," ");//无法解析字符串,因为不知道其格式
//2.通过request结构体计算生成response结构体
// 服务器核心,业务逻辑所在,可能会非常复杂
// 不同的业务场景,此处的实现也不同
Response resp;
ProcessRequest(&req,&resp);
//3.把Response结构体转成一个字符串(序列化),在发送回客户端
sendto(sock,&resp,sizeof(resp),0,(sockaddr*)&peer,sizeof(peer));
}
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "proto.h"
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
int main(int argc, char *argv[])
{
if(argc!=3)
{
printf("Usage:./plus_server [ip] [port]\n");
return 1;
}
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0)
{
perror("socket");
return 1;
}
sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=inet_addr(argv[1]);
server_addr.sin_port=htons(atoi(argv[2]));
while(1)
{
//1.先从标准输入读入两个整数
Request req;
printf("input a: ");
scanf("%d",&req.a);
fflush(stdout);
printf("input b: ");
scanf("%d",&req.b);
fflush(stdout);
//2.把Request结构体发送给服务器
sendto(sock,&req,sizeof(req),0,(sockaddr*)&server_addr,sizeof(server_addr));
//3.从服务器读取reponse结构体
Response resp;
recvfrom(sock,&resp,sizeof(resp),0,NULL,NULL);
//4.把结果显示到标准输出上
printf("Response:sum=%d\n",resp.sum);
}
return 0;
}