分布式应用开发的核心技术系列之——基于TCP/IP的原始消息设计
本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。
前言
本文的内容主要围绕以下几个部分:
- TCP/IP的简单介绍。
- 消息的介绍。
- 基于消息分类的传输格式(流类型和XML类型)。
- 消息体系的组成。
TCP/IP的简单介绍
TCP/IP (传输控制协议/网际协议) 是互联网中的基本通信语言或协议。它其实是一个两层的程序,分为高层与低层。高层为传输控制协议,负责聚集信息或把文件拆分成更小的包。这些包通过网络传送到接收端的 TCP层,接收端的 TCP 层把包还原为原始文件。低层是网际协议,它处理每个包的地址部分,使这些包正确地到达目的地。网络上的网关计算机根据信息的地址来进行路由选择。即使来自同一文件的分包路由也有可能不同,但最后会在目的地汇合。TCP/IP 使用客户端/服务器模式进行通信。
在架构上,TCP/IP 并不完全符合 0SI 的 7 层参考模型。传统的开放式系统互连参考模型是一种通信协议的 7 层抽象的参考模型,其中每一层执行某一特定任务。该模型的目的是使各种硬件在相同的层次上相互通信。这 7 层是: 物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而 TCP/IP 通信协议采用了 4 层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。这 4 层分别为:
- 应用层:应用程序间沟通的层,如简单邮件传输协议 (SMTP)、文件传输协议 (FTP)、远程网络访问协议 (Telnet) 等。
- 传输层:在此层中,它提供结点间的数据传送和应用程序之间的通信服务,主要功能是数据格式化、数据确认和丢失重传等。如传输控制协议 (TCP)、用户数据报协议 (UDP) 等,TCP 和 UDP 给数据包加入传输数据并把它传送到下一层中,这一层负责传送数据,并且确定数据已被送达并接收。
- 互连网络层:负责提供基本的数据封包传送功能,让每一个数据包都能够到达目的主机 (但不检查是否被正确接收),如网际协议 (IP)。
- 网络接口层 (主机-网络层): 接收 IP 数据报并进行传输,从网络上接收物理帧,抽取 IP 数据报转交给下一层,管理实际的网络媒体,定义如何使用实际网络 (如 Ethernet、Serial Line 等) 来传送数据。
Tcp/IP中常用的函数
1.Socket函数
int socket(int domain,int type,int protocol),
domain 指明所使用的协议族,通常为 PF INET,表示互联网协议族(TCP/IP 协议族); type 参数指定 socket 的类型;用于 TCP 的SOCK STREAM 或用于 UDP 的 SOCK DGRAM; protocol 通常赋值[0]。socket函数调用返回一个整型 socket 描述符,可以在后面调用它。
2.bind函数:
bind 函数将 socket 与本机上的一个端口相关联,随后就可以在该端口监听服务请求。bind 函数原型为:
int bind(int sockfd,struct sockaddr *my addr, int addrlen);
sockfd 是调用 socket 函数返回的 socket 描述符;my addr 是一个指向包含有本机 IP 地址及端口号等信息的 sockaddr 类型的指针:addrlen 常被设置为 sizeof (struct sockaddr)。
3.connect连接函数:
面向连接的客户程序使用连接 (connect) 函数来配置 socket 并与远端服务器建立一个 TCP 连接,其函数原型为:
int connect(int sockfd, struct sockaddr *serv addr,int addrlen);
sockfd 是 socket 函数返回的 socket 描述符; serv addr 是包含远端主机 IP 地址和端口号的指针; addrlen 是远端地址结构的长度。connect 函数在出现错误时返回-1,并且设置 errno 为相应的错误码。进行客户端程序设计无须调用 bind 0,因为这种情况下只需要知道目的机器的 IP 地址即可,而客户通过哪个端口与服务器建立连接并不需要关心socket 执行体程序自动选择一个未被占用的端口,并通知程序数据什么时候到达端口。
4.listen监听函数:
网络监听 (listen) 函数使 socket 处于被动的监听模式,并为该socket 建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
int listen(int sockfd, int backlog);
sockfd 是 Socket 系统调用返回的 socket 描述符;backlog 指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待接收函数accept 0)(参考下文)。backlog 对队列中等待服务的请求的数目进行了限制,通常系统默认值为 20。如果一个服务请求到来时,输入队列已满该 socket 将拒绝连接请求,客户将收到一个出错信息。
5.accept接收函数:
accept0函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用 accept 函数,然后睡眠并等待客户的连接请求。
int accept(int sockfd, void *addr, int *addrlen);
sockfd 是被监听的 socket 描述符,addr 通常是一个指向sockaddr_in 变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求); addrlen 通常为一个指向值为sizeof (struct sockaddr in) 的整型指针变量。出现错误时 accept 函数返回-1 并设置相应的 errno 错误码。
6.sendto函数和recvfrom函数:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen):
to 表示目的机的IP 地址和端号信息,而 tolen 常常被赋值为 sizeof (struct sockaddr)。sendto 函数返回实际发送的数据字节长度或在出现发送错误时返回-1。
int recyfrom(int sockfd,void *buf,int len,unsigned int flags,structsockaddr *from,int *fromlen);
from 是一个 struct sockaddr 类型的变量,该变量保存源主机的 IP 地址及端口号。fromlen 常置为 sizeof (struct sockaddr),当 recvfrom()返回时,fromlen 包含实际存入 from 中的数据字节数。recvfrom() 函数返回接收到的字节数或当出现错误时返回-1,并设置相应的 errno 错误码。
7.shutdown函数
shutdown函数来关闭该 socket。该函数允许你只停止某个方向上的数据传输,而另一个方向上的数据传输继续进行。
int shutdown(int sockfd,int how);
sockfd 是需要关闭的 socket 的描述符。参数 how 允许为 shutdown操作选择以下几种方式:
- 0一一不允许继续接收数据
- 1--不允许继续发送数据
- 2一一不允许继续发送和接收数据
shutdown 在操作成功时返回 0,在出现错误时返回-1 并设置相应errno 错误码。
8.fcntl函数
fcntl函数可以改变已打开的文件的性质。
int fcntl (int fields, int cmd, .../* int arg */) ;
9.getsockopt 与 setsockopt 函数
这两个函数可以获取或者设置与某个套接字关联的选项。为了操作套接字层的选项,应该将层的值指定为 SOL SOCKET。为了操作其他层的选项控制选项的合适协议号必须给出。例如,为了表示一个选项是由 TCP 解析,层应该设定为协议号 TCP。
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
10.select函数
select 函数是一种用于多路复用(Multiplexing)的系统调用或函数。它通常用于处理多个输入和输出流,以实现异步的 I/O 操作。
int select(int n, fd set * readfds, fd set * writefds, fd set * exceptfds,struct timeval * timeout);
参数 n 代表最大的文件描述词加 1,参数 readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读、写或例外的状况。
11.poll函数
int poll(struct pollfd fds[], nfds t nfds, int timeout);
其中 fds 是一个 struct pollfd 结构类型的数组,用于存放需要检测其状态的 socket 描述字。struct pollfd 的定义如下:
struct pollfd {
//descriptor to check
int fd;
//events of interest on fd
short events;
//events that occurred on fd
short revents;
}
什么是消息
消息是分布式应用开发中,网络上两个逻辑实体之间进行通信时,在编程层面的最小单元。
对以上定义,有以下几点说明:
(1) 消息的概念存在于开发工作中,位于编程层面。在系统运行时,对应用用户是透明的。
(2) 网络上的两个逻辑实体,是指两个可独立运行的程序,它们可以部署于网络中两个不同的物理设备上,也可以部署于同一个物理设备上,但一般是两个没有父子关系的独立进程 (这一点与 IPC 编程中最基本的消息概念不同)。
(3) 消息是分布式通信时编程层面的最小单元,即无论参与通信的数据量是多还是少,程序代码中都通过发送与接收一个或多个消息来实现。
(4) 网络上两个应用之间的通信,包括数据流传输与远程过程(函数)调用两种类型。
(5) 利用消息可以实现分布式应用之间的结构化数据通信。也就是说编程人员在通信层面面对的不再是实际字节流,而是可以由多种数据类型组合而成的结构化数据单元。
其实,这种结构化数据单元本身就是“消息”,它对外可以表现为结构或者类。因此,当基于以上定义的消息机制建立起来以后,程序员在编码过程中,当需要进行分布式通信时,只需要生成相应的消息,然后调用相应的发送与接收接口方便地实现即可,而不需要了解 TCP/IP 知识,不需要掌握socket 编程的基本技能,也不需要考虑串行消息过多、并发消息过多、网络流量控制等其他多方面的问题,从而才能真正地将分布式应用开发的精力集中到业务实现上来,极大地提高了分布式系统的开发效率与质量,特别是大型分布式系统。
关于消息的存在形式,在传统 C 语言中,可以是一个结构 struct;在面向对象语言中 (C++ 或 Java),则可以是一个类 class。
基于消息分类的传输格式
基于消息传输的格式不同,可以将消息分为流消息和XML消息,流消息基于二进制字节流式格式传输,XML消息基于XML格式的字符串传输。
流消息
流消息是指在计算机系统中,以流(stream)的方式传递和处理的消息。流消息由一系列连续的数据组成,在发送端按照一定的顺序生成,并以流的形式传输到接收端。传输过程中,接收端可以逐个读取流中的数据。,对于流消息来说,无论程序员如何表示消息,消息在真正发送之前,都需要先转换为二进制流格式,这个转换过程称为流化 (Streamlization),也可称序列化 (Serilization),
XML消息
XML消息是指使用可扩展标记语言(XML)作为消息格式的数据传输方式。XML是一种用于描述和存储数据的文本标记语言,它使用标签来定义数据的结构和属性。在 XML 消息机制中,程序员用 XML 格式表示消息内容之后,不需要再为发送传输做任何格式转换工作(不包括为安全传输所做的加密工作),直接就可以以 XML 字符串格式发送出去。XML 消息应用也比较广泛,如 Web Service 中的 SOAP 协议,就是基于 XML 消息设计实现的。
举个例子:基于流消息的设计与实现方法
下面小编为大家简单地介绍一下如何在两个应用程序上发送和接受一个人的信息(包括身高、姓名和年龄)
(1)定义一个类存放人的信息:
struct Person {
char name[20] ;
float height;
int age;
}
struct Person p;
strcpy(p.name ,"Michael Zhang");
height = 170.00;
age = 30;
(2)将信息序列结构化
char sendStream[1024] = {0};
sprintf(sendStream,"|%s|%f"%d",p.name, p.height, p.age);
(3)发送方发送字节流:
/*注: 这里省略建立/管理/关闭 TCP 连接的代码*/
char datalen[4+1] = (0);
sprintf(datalen,"04d" , strlen (sendStream) );
if(SendBytes ( socket, datalen, 4) == -1) {
return -l;
}
if(SendBytes(socket, sendStream, strlen(sendStream)) == -1) {
return -1
}
注意,以上代码中的函数 SendBytes 实际上是保证一定长度的字节流全部成功发送完毕后才返回,主要是由于在 socket 上调用 send 或 write函数不能保证一次能将一定长度的字节流发送完。SendBytes 的基本思想是循环发送,直至成功发完所有字节,其实现代码如下所示:
int SendBytes (int sd, const void *buffer, unsigned len) {
int rez = 0;
int leftlen = len;
int readlen = 0:
}
while(true) {
rez = write (socket, (char *)buffer+readlen, len-readlen);
if(rez < 0) {
if (errno != EWOULDBLOCK && errno != EINTR) {
ErrorMsg("Error is serious );
DisConnect(socket);
}
return -l:
}
readlen += rez;
leftlen -= rez;
if(leftlen <= 0){
break;
}
}
return len:
}
(4)接收方接收字节流:
char datalen[4+1] = {0};
char receiveStream[1024] = {0};
sprintf(datalen,"%04d", strlen(sendStream)) ;
if(ReceiveBytes(socket, datalen, 4) == -1 {
return -l;
}
int packet len = atoi(datalen) :
if(ReceiveBytes (socket, receiveStream, packet len) == -1) {
return -l;
}
ReceiveBytes函数可以参考第三步发送方发送该字节流。
(5)字节流反序列化得到结构:
struct Person p;
sscanf(receiveStream,"%[`|]|%f|%d", p.name, &p.height, &p.age) ;
总结
本文简单的介绍了TCP/IP协议及其常用的接口函数,然后介绍了TCP/IP协议中消息的分类以及传输格式,最终以一个简单的消息发送小例子作为收尾。如对内容有何意见建议,欢迎大家在评论区中留言和讨论。
参考书籍:《消息设计与开发——分布式应用开发的核心技术》 何小朝
扩展链接:
分布式应用开发的核心技术系列之——基于TCP/IP的原始消息设计的更多相关文章
- 读书笔记——网络编程与开发技术(3)基于TCP/IP协议的网络编程相关知识
TCP/IP协议:数据链路层,网络层,传输层,应用层. IP地址分为5类:A类.B类.C类.D类.E类. (A类.B类.C类是基本类,D类多用于多播传送,E类为保留类.) "*"表 ...
- 基于TCP/IP协议的C++网络编程(API函数版)
源代码:http://download.csdn.net/detail/nuptboyzhb/4169959 基于TCP/IP协议的网络编程 定义变量——获得WINSOCK版本——加载WINSOCK库 ...
- JAVA Socket 底层是怎样基于TCP/IP 实现的???
首先必须明确:TCP/IP模型中有四层结构: 应用层(Application Layer).传输层(Transport Layer).网络层(Internet Layer ).链路层( ...
- 20181225 基于TCP/IP和基于UDP/IP的套接字编程
一.TCP/IP的套接字编程 服务器端代码: import socketserver = socket.socket() # 默认是基于TCP# 基于TCP的对象serve=socket.sock ...
- 基于TCP/IP的长连接和短连接
1. TCP连接 当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次 ...
- 标准C实现基于TCP/IP协议的文件传输
上学期集成程序设计的课堂作业,对于理解TCP/IP实现还是挺有帮助的. TCP/IP编程实现远程文件传输在LUNIX中一般都采用套接字(socket)系统调用. 采用客户/服务器模式,其程序编写步骤如 ...
- 标准C语言实现基于TCP/IP协议的文件传输
TCP/IP编程实现远程文件传输在LUNIX中一般都采用套接字(socket)系统调用. 采用客户/服务器模式,其程序编写步骤如下: 1.Socket系统调用 为了进行网络I/O,服务器和客户机两 ...
- c#基于TCP/IP、CIP协议的欧姆龙PLC通信
一.关于CIP协议 CIP通信是Common Industrial Protocl(CIP)的简称,它是一个点到点的面向对象协议,能够实现工业器件(传感器,执行器)之间的连接,和高等级的控制器之间的连 ...
- 网络通信-在浏览器输入url,基于TCP/IP协议,浏览器渲染的解释
知识点1: 网络模型 TCP/IP四层 和ISO七层模型 (统一省略后面层字.比如传输代表传输层) 知识点2: 在应用层中TCP建立连接,经历的三次握手协议 首先:,TCP协议是什么? 为什么要三次握 ...
- 基于TCP/IP的程序设计
TCP特点 (1)面向连接的传输 (2)端到端的通信 (3)高可靠性,确保传输数据的正确性,不会出现丢失或者乱序 (4)全双工方式传输 (5)采用字节流方式,以字节为单位传输字节序列 (6)紧急数据传 ...
随机推荐
- [随笔]记一此更新win10后mysql服务消失的问题
十几天前系统自动更新 没在意 几天前用php连mysql的时候 报错 Fatal error: Uncaught PDOException: SQLSTATE[HY000] [2002] 由于目标计算 ...
- Tomcat启动时出现乱码的解决方式
在网上下载了一个版本号为apache-tomcat-8.5.38的Tomcat,因为这个Tomcat一直没有用过,所以今天启动时出现了如下乱码: 解决方案: 找到Tomcat目录下conf文件夹中的l ...
- pyinstaller打包程序后提示No module named ‘xxxx‘
解决方法1 1.检查 先在venv环境中安装xxx 报错的这个包 以我的举例 查看settings>project interpreter (存在对应的包) 解决方法2 2.在xxx.spec ...
- Elasticsearch日常开发
2020-08-12 14:51:37 每次遇到ES开发,一般都是查询es里面的数据,今天我教大家一个简单的es的查询.废话不多说,直接上代码. 在pom文件中引入 <dependency> ...
- 2023年陕西彬州第八届半程马拉松赛153pb完赛
1.赛事背景 2023年6月3日,我参加了2023陕西彬州第八届半程马拉松赛,最终153完赛,PB了5分钟.起跑时间早上7点30分,毕竟6月天气也开始热了.天气预报显示当天还是小到中雨,上次铜川宜君半 ...
- 打造原生 WebGL 2D 引擎:一场创意与技术的融合
打造原生 WebGL 2D 引擎:一场创意与技术的融合 1.引言 在当今数字化时代,网页的功能越来越丰富,已经远远超越了传统的文本和图片呈现.我们生活在一个充满交互性和视觉魅力的网络世界.每天都会遇到 ...
- quarkus实战之七:使用配置
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus实战>系列 ...
- 渗透小tis
知己知彼,百战不殆 1.如果提示缺少参数,如{msg:params error},可尝使用字典模糊测试构造参数,进一步攻击. 2.程序溢出,int最大值为2147483647,可尝试使用该值进行整数溢 ...
- Sealos 国内集群正式上线,可一键运行 LLama2 中文版大模型!
2023 年 7 月 19 日,MetaAI 宣布开源旗下的 LLama2 大模型,Meta 首席科学家.图灵奖得主 Yann LeCun 在推特上表示 Meta 此举可能将改变大模型行业的竞争格局. ...
- 记一次weak_up函数绕过
2023 蓝帽杯CTF LovePHP 因为比赛已经结束,所以复现环境是从本地进行复现,这次比赛本来排名挺靠前的,原本总排名是60多名,赛区排名30多名,本来是以为有希望进入半决赛的,但是没想到比赛结 ...