栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

网络编程剖析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

网络编程剖析

1.网络编程核心概念:
协议:为进行数据通信而预定义的数据规则
地址:网络通信中的用于标识设备的整数值
端口号:
设备为收发数据而指定的数值,用于标识具体连接
可理解为:设备中用于网络通信的数据通道
服务端:等待连接的设备
客户端:发起连接的设备

网址就是IP地址吗?URL是什么?,域名又是什么?
网址不是IP地址,是网络信息资源的地址(如:具体网页的地址),即:URL
域名是IP地址的别名,多个域名可指向同一个IP地址
协议一定是看不懂的二进制数据吗?
协议是一种约定,协议可以基于文本定义,也可以基于二进制定义
网络字节序:网络字节顺序采用大端模式,所以:在小端系统中需要做字节序转换

#include 
#include 
int socket(int domain, int type, int protocal); //创建套接字,为网络连接做准备
int connect(int sock, struct sockaddr* addr, socklen_t len);  //连接指定地址的远程设备
ssize_t send(int fd, const void* buf, size_t n, int flags);  //发送数据到远程设备
ssize_t recv(int fd, void* buf, size_t n, int flags); //接受远程设备发回的数据
int close(int fd);  //关闭连接,销毁套接字

2.服务端编程
客户端/服务端 编程模式
服务端长期暴露与网络,并等到客户端连接
客户端发起连接动作,并等待服务端回应
特点:
服务端无法主动连接客户端
客户端只能按照预定义的方式连接服务端
服务端核心工作;绑定&监听&接受
绑定:

int bind(int sock, struct sockaddr* addr, socklen_t addrlen);
监听:
int listen(int sock, int backlog);
接受:
int accept(int sock, struct sockaddr* addr, socklen_t* addelen)

深度剖析服务端:
服务端socket只用于接受连接,不进行实际通信
当接受到连接时,accept()函数返回与客户端通信的socket
服务端socket产生用于通信的客户端socket

socket究竟是什么玩意?如何理解?
socke()是一个多功能函数,返回值是用于通信的资源标识符,socket()可提供不同类型的通信功能(本地进程间通信)

客户端/服务端编程的核心模式:
服务端长时间运行(死循环)接受客户端请求
客户端连接后向服务端发送请求(协议数据)

深入浅出IP地址:
网络编程接口中一些参数的意义是什么?

sock = socket(PF_INET, SOCK_STREAM, 0);

socket参数详解:
domain:套接字中使用的协议族信息 (PF_INET:IPv4互联网协议族,PF_INET6:IPv6互联网协议族,PF_LOCAL:本地通信的协议族,PF_PACKET:底层数据收发协议族,PF_IPX:Novell专用协议(互联网分组交换协议))
type:套接字数据传输类型信息,用于指定协议类型 (SOCK_STREAM:流式数据(TCP),SOCK_UGRAM:报文式数据(UDP))
protocol:设备间通信使用的协议信息 ,用于指定协议族中符合类型的具体协议 (domain和type几乎可以唯一确定一种协议,因此,这个参数通常为0)

端口号和IP地址:
端口号是一个2字节数据
0-1023作为特定端口被预定义(分配给特定应用程序)
IP地址是一个4字节地址族(可分为5类地址)
深入解析IP地址:
IP地址分为网络标识和主机标识两部分
网络标识:标识网络主机(设备)所在的网络
主机标识:标识网络主机具体地址

一个IP地址就4个字节,如何区分网络标识和主机标识呢?
IP地址与子网掩码配合使用区分网络标识和主机标识
子网掩码的表现形式也是一个4字节的整形数
子网掩码用于从IP地址中提取网络标识

深入理解子网掩码:
设:子网掩码为M.N.P.Q,则子网可用IP地址 n = (256 - M)(256-N)(256-p)*(256-Q)
IP地址211.99.34.33,掩码255.255.255.248
可知211.99.34.33所在子网有8个IP地址,且8=2的3次方,所以Y = 32 - 3 = 29
表示为211.99.34.33/29(简介表示法)

特殊的地址
	0.0.0.0/0 - 保留,常用于代表"缺省网络"
	127.0.0.08 -回环地址,常用于本地软件回送测试
	255.255.255.255/32 - 广播地址
私有地址:不在公网使用,只在内网使用
	10.0.0.0 - 10.255.255.255/8
	172.16.0.0 - 171.31.255.255/16
	192.168.0.0 - 192.168.255.255/24

IP地址相关函数:

#include 
in_addr_t inet_addr(const char* strptr);  //将IP字符串转换为符合网络字节序的整数
int inet_aton(const char* cp, struct in_addr* inp); //将IP字符串转换为符合网络字节序的整数,成功返回1,失败返回0
char* inet_ntoa(struct in_addr in);  //将符合网络字节序的整数地址转换为字符串形式

尝试select多路复用:
如何增强服务端能力,同时支持多个客户端?
linux中的文件是什么?
狭义:文件系统中物理意义上的文件
广义:
设备,管道,内存。。。
linux管理的一切对象
文件描述符:
文件描述符是一个非负整数值,本质是一个句柄
一切对用户透明的资源表示都可以看做句柄
用户使用文件描述符与内核交互
内核通过文件描述符操作对应资源的数据结构

事件相关函数的分类:
阻塞式函数:
函数调用后需要等待某个事件发生后才会返回
非阻塞式函数:
函数调用后能够及时返回(仅标记等待的事件)
事件发生后以回调方式传递

神奇的select()函数:
select()用于监视指定的文件描述符是否产生事件
可通过轮询的方式检测目标事件(事件产生则标记发生变化)
根据事件类型做出具体处理(如:读取数据)

int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptest, const struct timeval* timeout);

select()相关数据类型及操作:

FD_ZERO(fd_set* fdset);  //将fd_set变量的所有位设置为0
FD_SET(int fd, fd_set* fdset);  //在fd_set中指定需要监听的fd
FD_CLR(int fd, fd_set* fdset);  //在fd_set中剔除fd1,不在监听
FD_ISSET(int fd, fd_set* fdset);  //在fd_set查看是否包含fd

基于多路复用的服务端
问题:使用select()函数可以扩展服务端功能吗?如果可以 ,具体怎么实现?
服务端瓶颈分析:
client = accept(server, (struct sockaddr*)&caddr, &asize); //阻塞,等待客户端连接
r = recv(client, buf, sizeof(buf), 0); //阻塞,等待客户端数据
服务器大多时候处于等待状态,无法发挥主机的最大性能

解决方案:阻塞变轮询
通过select()函数首先监听服务端server_fd,目标事件为“连接”(读)
当事件发生(客户端连接)则调用accept()接受连接
将client_fd加入监听范围,目标事件为“数据接受”(读)
循环查看各个被监听的文件描述符是否有事件发生
实现逻辑:

while(1)
{
	rset = reads;
	num = select(max+1, &rset, 0, 0, &timeout);
	if(num > 0)
	{
		int i = 0;
		for(i = 1;i <= max; i++)
		{
			if(FD_ISSET(i, &rset))
			{
				if(i == server)
				{
					//accept and client to fd_set
				}
				else
				{
					//read data from client by i (fd)
				}
			}
		}
	}
}

实现关键:
动态调整需要监视的文件描述符
当接受到客户端连接时,将客户端文件描述符加入到监听变量(fd_set)中
当发现客户端断开时,在监听变量(fd_set)中剔除客户端文件描述符

if(client > -1)
{
	FD_set(client, &reads);
	max = (client > max) ? client : max;
	printf("client: %dn", client);
}
if(r == -1)
FD_CLR(i, &reads);

实现关键:
动态调整需要监视的文件描述符数量
保证每个需要监视的文件描述符能够被轮询
max = (client > max) ? client : max;

TCP与UDP:
TCP/IP分层结构:
应用层:各个应用程序可以定义(使用)各种各样的协议
传输层:确保发出的数据能够达到目标主机,完成数据传输
网络层:填写数据包地址,选择数据传递路径
数据链路层:融合不同连接方式的链路,屏蔽网络差异
物理层:具体连接方式:有线,无线,光纤
TCP / IP工作方式:
TCP/IP层次结构的特点:
上层依赖邻接下层的能力,下层只为直接邻接上层服务
上层不知道下层的工作机制,下层不管上层传输的数据内容
不做跨层服务,层次结构中的角色缺一不可

深入理解网络层(IP层):
IP地址:IP地址属于网络层地址,用于标识网络上的主机
路由控制:控制数据如何到达目标主机(如:需要经过那些路由器转发)
无连接:数据包根据IP地址在网络上传递(无需与目标实现简历连接)
MAC地址:数据链路层所使用的硬件地址。MAC地址与网络无关,出厂时写入到网络设备中。当主机从网络撒花姑娘每收到一个数据帧时,首先检查数据帧中的MAC地址。如果是发生本主机的数据帧则收下,之后进行其他的处理;否则将此帧丢弃,不再进行其他的处理。

IP地址和MAC地址:
IP地址是动态的,不特定于某个具体的硬件(MAC地址隶属于具体硬件)
IP地址是网络层使用的地址(用于夸网络投递数据包)
MAC地址是数据链接层使用的地址(用于确定目标网络中接受数据的主机)
路由器中记录了本网络中主机IP地址与MAC地址的映射关系
IP路由控制:
·为了将数据发给目标主机,所有主机都维护着一张路由表
路由表记录了IP数据包下一步应该发给哪个路由器
IP数据转发:
·IP数据包转发用的是“尽力服务”策略
"尽力服务"指会努力,但不保证结构
转发时会通知附加信息检查数据合法性,但出现异常不会进行长重发
以包为单位进行转发,不保证到达(发出之后,石沉网海)

问题:TCP/IP网络层次结构能否能提供可靠数据传输
传输控制协议:
TCP在协议实现上提供可靠数据传输
TCP不存在“数据包”的概念,实现了流失传输(数据如流水,无头无尾)
TCP内部有服务状态,能够精确知道数据是否已经发送成功,是否被接受。。。
TCP在行为上可进行阻塞控制(网络环境变差时,能够调整数据发送速度)
TCP连接建立:
TCP连接建立:

TCP的天生缺陷(DDos攻击):
·TCP连接断开:


UDP的特点:完全继承网络层工作方式
无需连接,直接指定IP地址和端口即可发送数据
监听固定端口,只要有数据,统统接受
不管网络情况,只要是数据统统可发送
不关心数据是否到达对端
UDP使用场合:
对数据不敏感,需要实时性的场合(如:直播,实时游戏)
网络环境比较好的场合(如:物联网结局)
需要深度定制协议的场合(如:不丢包的UDP协议)

应用层协议设计与实现:
小知识:
发送缓冲区:
数据先进入发送缓冲区,之后由操作系统送往远端主机
接受缓冲区:
远端数据被操作系统接受后放入缓冲区
之后应用程序从接受缓冲区读取数据
TCP应用编程中的“问题”:
数据接受端无法知道数据的发送方式!!
网络编程中的期望:
每发送一条完整的消息,每次接受一条完整的消息
即使接受缓冲区中有多条消息,也不会出现消息粘连
消息中涵盖了数据类型和数据长度等信息

应用层协议设计:
什么是协议?
协议是遵循通信双方为数据交换而建立的规则、标准或约定的集合
协议对数据传输的作用
通信双方根据协议能够正确收发数据
通信双方根据协议能够解释数据的意义
协议设计示例:
目标:设计可用于数据传输的协议
完整消息包含
数据头:数据类型(即:数据区用途,固定长度)
数据长度:数据区长度(固定长度)
数据区:字节数据(变长区域)
协议设计示例:
因此:消息至少12个字节(消息头 + 数据长度)
通过计算消息的总长度,能够避开数据粘连性的问题

协议设计示例:

typedef struct
{
	unsigned short type;
	unsigned short cmd;
	unsigned short index;
	unsigned short total;
	unsigned int length;
	unsigned char payload[];  //柔性数组
}Message;

应用层协议解析模块(上):
问题:如何在代码层面封装协议细节?如何将接受缓冲区中的数据解析成为Message
深度思考:
数据是否能够解析成为Message?
数据量足够
如果数据量足够,是否能够解析不知一个Message?
如何处理剩余数据(属于下一个Message)?
数据量不足
是否达到协议最小长度(12字节)?
如何处理数据量超过最小长度,但不足以创建一个Message的情况?
初步的解决方案
定义一个模块用于从字节流解析Message
可从指定内存或从指定文件描述符读取并解析
当至少存在12个字节时开始解析
1.首选解析协议中的头信息和数据区长度
2.根据数据区长度继续从字节流读取数据
3.当协议数据解析完成时,创建Message并返回,否则,返回NULL
协议解析模块的初步设计:
解析器数据结构:

typedef struct msg_parser
{
	Message cache;  //缓存已解析的消息头
	int header;  //标识消息头是否解析成功
	int need;    //标识还需要多少字节才能完成解析
	Message* msg;  //解析中的协议信息(半成品)
}MsgParser;

从内存中解析协议数据
条件:内存长度至少连续12个字节

memcpy(&p->cache, mem, p->need);

//从网络字节序转化为本机字节序
p->cache.type = ntohs(p->cache.type);
p->cache.cmd = ntohs(p->cache.cmd);
p->cache.index = ntohs(p->cache.index);
p->cache.total = ntohs(p->cache.total);
p->cache.length = ntohs(p->cache.length);

mem += p->need;
length -= p->need;

p->header = 1;
p->need = p->cache.length;
从内存中读取payload中的数据(可多次读取)
if(!p->msg)
{
//成功解析消息头之后,创建Message
	p->msg = malloc(sizeof(p->cache) + p->need);
	if(p->msg)
	{
		*p->msg = p->cache;
	}
}
if(p->msg)
{
	unsigned int len = (p->need < length) ? p->need : length;
	unsigned int offset = p->msg->length - p->need;
	
	memcpy(p->msg->payload + offset, mem, len);
	p->need -= len;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/736839.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号