如何在 TypeScript 中创建自定义类型
作者选择了COVID-19救济基金,作为Write for Donations计划的一部分接受捐赠。
简介
TypeScript是JavaScript语言的一个扩展,它使用JavaScript的运行时与编译时类型检查器。这种组合允许开发者使用完整的JavaScript生态系统和语言特性,同时也在其之上添加了可选的静态类型检查、枚举、类和接口。
尽管TypeScript中预制的基本类型将涵盖许多用例,但基于这些基本类型创建你自己的自定义类型将允许你确保类型检查器验证特定于你的项目的数据结构。这将减少你的项目中出现错误的机会,同时也可以更好地记录整个代码中使用的数据结构。
本教程将向你展示如何使用TypeScript的自定义类型,如何用联合和交叉将这些类型组合在一起,以及如何使用实用类型来为你的自定义类型增加灵活性。它将引导你学习不同的代码样本,你可以在自己的TypeScript环境或TypeScript Playground(一个允许你直接在浏览器中编写TypeScript的在线环境)中学习。
前提条件
要学习本教程,你需要。
- 一个可以执行TypeScript程序的环境,以便跟随示例。要在你的本地机器上设置这个,你需要以下东西。
- 同时安装Node和npm(或yarn),以便运行一个处理TypeScript相关包的开发环境。本教程在Node.js 14.3.0版本和npm 6.14.5版本中进行了测试。要在macOS或Ubuntu 18.04上安装,请按照《如何在macOS上安装Node.js并创建本地开发环境》中的步骤,或者按照《如何在Ubuntu 18.04上安装Node.js》中的《使用PPA安装》部分进行安装。如果你使用Windows Subsystem for Linux (WSL),这也同样适用。
- 此外,你将需要在你的机器上安装TypeScript编译器(
tsc
)。要做到这一点,请参考TypeScript 官方网站。
- 如果你不希望在你的本地机器上创建TypeScript环境,你可以使用官方的TypeScript Playground来进行学习。
- 你需要有足够的JavaScript知识,特别是ES6+的语法,如结构化、休息运算符和导入/导出。如果你需要关于这些主题的更多信息,建议阅读我们的《如何用JavaScript编程》系列。
- 本教程将参考支持TypeScript的文本编辑器的各个方面,并显示行内错误。这并不是使用TypeScript的必要条件,但确实能更多地利用TypeScript的特性。为了获得这些好处,你可以使用像Visual Studio Code这样的文本编辑器,它对TypeScript有开箱即用的全面支持。你也可以在TypeScript Playground中尝试这些优势。
本教程中显示的所有示例都是使用TypeScript 4.2.2版本创建的。
创建自定义类型
在程序具有复杂数据结构的情况下,使用 TypeScript 的基本类型可能无法完全描述你所使用的数据结构。在这些情况下,声明你自己的类型将帮助你解决复杂性。在本节中,您将创建可用于描述您在代码中需要使用的任何对象形状的类型。
自定义类型语法
在TypeScript中,创建自定义类型的语法是使用type
关键字,后面是类型名称,然后是对带有类型属性的{}
块的赋值。请看下面的例子。
type Programmer = {
name: string;
knownFor: string[];
};
语法类似于一个对象字面,其中键是属性的名称,值是这个属性应该有的类型。这就定义了一个类型Programmer
,它必须是一个对象,name
的键是持有一个字符串值,knownFor
的键是持有一个字符串的数组。
正如前面的例子所示,你可以用;
作为每个属性之间的分隔符。也可以使用逗号,,
,或者完全省略分隔符,如图所示。
type Programmer = {
name: string
knownFor: string[]
};
使用你的自定义类型与使用任何基本类型是一样的。添加一个双冒号,然后添加你的类型名称。
type Programmer = {
name: string;
knownFor: string[];
};
const ada: Programmer = {
name: 'Ada Lovelace',
knownFor: ['Mathematics', 'Computing', 'First Programmer']
};
ada
常量现在将通过类型检查器,而不会出现错误。
如果你在任何完全支持TypeScript的编辑器中写这个例子,比如在TypeScript Playground中,编辑器会提示该对象所期望的字段和它们的类型,如下面的动画所示。
如果你使用TSDoc格式为字段添加注释,这是一种流行的TypeScript注释文档风格,它们也会在代码完成中被建议。以下面的代码为例,在注释中进行解释。
type Programmer = {
/**
* The full name of the Programmer
*/
name: string;
/**
* This Programmer is known for what?
*/
knownFor: string[];
};
const ada: Programmer = {
name: 'Ada Lovelace',
knownFor: ['Mathematics', 'Computing', 'First Programmer']
};
注释的说明现在会和字段建议一起出现。
当用自定义类型Programmer
,创建一个对象时,如果你给任何属性分配一个意外类型的值,TypeScript将抛出一个错误。以下面的代码块为例,有一个突出显示的行没有遵守类型声明。
type Programmer = {
name: string;
knownFor: string[];
};
const ada: Programmer = {
name: true,
knownFor: ['Mathematics', 'Computing', 'First Programmer']
};
TypeScript编译器(tsc
)将显示错误2322
。
OutputType 'boolean' is not assignable to type 'string'. (2322)
如果你省略了你的类型所要求的任何属性,像下面这样。
type Programmer = {
name: string;
knownFor: string[];
};
const ada: Programmer = {
name: 'Ada Lovelace'
};
TypeScript编译器将给出错误2741
。
OutputProperty 'knownFor' is missing in type '{ name: string; }' but required in type 'Programmer'. (2741)
添加一个在原始类型中没有指定的新属性也会导致错误。
type Programmer = {
name: string;
knownFor: string[];
};
const ada: Programmer = {
name: "Ada Lovelace",
knownFor: ['Mathematics', 'Computing', 'First Programmer'],
age: 36
};
在这种情况下,显示的错误是2322
。
OutputType '{ name: string; knownFor: string[]; age: number; }' is not assignable to type 'Programmer'.
Object literal may only specify known properties, and 'age' does not exist in type 'Programmer'.(2322)
嵌套的自定义类型
你也可以将自定义类型嵌套在一起。想象一下,你有一个Company
类型,该类型有一个manager
字段,该字段依附于一个Person
类型。你可以像这样创建这些类型。
type Person = {
name: string;
};
type Company = {
name: string;
manager: Person;
};
然后你可以像这样创建一个类型为Company
的值。
const manager: Person = {
name: 'John Doe',
}
const company: Company = {
name: 'ACME',
manager,
}
这段代码将通过类型检查器,因为manager
常量符合指定给manager
字段的类型。注意,这使用了对象属性的速记法来声明manager
。
你可以省略manager
常量中的类型,因为它的形状与Person
类型相同。当你使用一个与manager
属性的类型所期望的形状相同的对象时,TypeScript不会引发错误,即使它没有明确设置为具有Person
的类型
下面的内容也不会产生错误。
const manager = {
name: 'John Doe'
}
const company: Company = {
name: 'ACME',
manager
}
你甚至可以更进一步,直接在这个company
对象字面上设置manager
。
const company: Company = {
name: 'ACME',
manager: {
name: 'John Doe'
}
};
所有这些情况都是有效的。
如果在一个支持TypeScript的编辑器中编写这些例子,你会发现编辑器会使用可用的类型信息来记录自己。对于前面的例子,只要你打开{}
对象字面的manager
,编辑器将期待一个name
类型的属性string
。
现在你已经经历了一些创建你自己的具有固定数量属性的自定义类型的例子,接下来你将尝试向你的类型添加可选属性。
可选属性
有了前面几节的自定义类型声明,当用该类型创建一个值时,你不能省略任何属性。然而,有一些情况需要可选属性,无论是否有值都可以通过类型检查器。在本节中,你将声明这些可选属性。
要向一个类型添加可选属性,请向该属性添加?
修饰符。使用前几节中的Programmer
类型,通过添加下面的高亮字符,将knownFor
属性变成一个可选属性。
type Programmer = {
name: string;
knownFor?: string[];
};
这里你要在属性名后面添加?
修饰符。这使得TypeScript认为这个属性是可选的,当你省略该属性时不会引发错误。
type Programmer = {
name: string;
knownFor?: string[];
};
const ada: Programmer = {
name: 'Ada Lovelace'
};
这将通过,没有错误。
现在你知道了如何向一个类型添加可选属性,现在是时候学习如何创建一个可以容纳无限多字段的类型了。
可索引类型
前面的例子表明,如果一个给定的类型在声明时没有指定这些属性,你就不能向该类型的值添加属性。在本节中,你将创建_可索引类型_,这是一种允许任何数量的字段的类型,如果它们遵循该类型的索引签名。
想象一下,你有一个Data
类型,以容纳无限数量的any
类型的属性。你可以像这样声明这个类型。
type Data = {
[key: string]: any;
};
这里你创建了一个普通的类型,类型定义块在大括号里 ({}
),然后添加一个特殊的属性,格式为 [key: typeOfKeys]: typeOfValues
,其中typeOfKeys
是该对象的键应该具有的类型,而typeOfValues
是这些键的值应该具有的类型。
然后你可以像其他类型一样正常使用它。
type Data = {
[key: string]: any;
};
const someData: Data = {
someBooleanKey: true,
someStringKey: 'text goes here'
// ...
}
使用可索引类型,你可以分配无限数量的属性,只要它们符合_索引签名_,_索引签名_是用来描述可索引类型的键和值的类型的名称。在这种情况下,键有一个string
类型,而值有any
类型。
也可以向你的可索引类型添加始终需要的特定属性,就像你可以向普通类型添加一样。在下面突出显示的代码中,你正在向你的Data
类型添加status
属性。
type Data = {
status: boolean;
[key: string]: any;
};
const someData: Data = {
status: true,
someBooleanKey: true,
someStringKey: 'text goes here'
// ...
}
这将意味着一个Data
类型的对象必须有一个status
的键和一个boolean
的值才能通过类型检查器。
现在你可以创建一个具有不同数量元素的对象,你可以继续学习TypeScript中的数组,它可以有一个自定义数量的元素或更多。
创建有元素数或更多的数组
使用TypeScript中的数组和元组基本类型,你可以为数组创建自定义类型,这些数组应该有最小数量的元素。在这一节中,你将使用TypeScript的休息操作符 ...
来实现这一目的。
想象一下,你有一个负责合并多个字符串的函数。这个函数将接受一个单一的数组参数。这个数组必须至少有两个元素,每个元素都应该是字符串。你可以用下面的方法创建一个这样的类型。
type MergeStringsArray = [string, string, ...string[]];
MergeStringsArray
类型是利用了你可以对数组类型使用rest operator这一事实,并将其结果作为一个元组的第三个元素。这意味着前两个字符串是必须的,但之后的其他字符串元素就不需要了。
如果一个数组的字符串元素少于两个,它将是无效的,就像下面这样。
const invalidArray: MergeStringsArray = ['some-string']
TypeScript编译器在检查这个数组时将会给出错误2322
。
OutputType '[string]' is not assignable to type 'MergeStringsArray'.
Source has 1 element(s) but target requires 2. (2322)
到此为止,你已经从基本类型的组合中创建了你自己的自定义类型。在下一节中,你将通过将两个或更多的自定义类型组合在一起来制造一个新的类型。
组合类型
本节将介绍两种可以将类型组合在一起的方法。它们将使用_联合运算符_来传递符合一个或另一个类型的任何数据,并使用_交集运算符_来传递满足两个类型中所有条件的数据。
联合体
联盟是使用|
(管道)运算符创建的,它表示一个可以拥有联盟中任何类型的值。以下面的例子为例。
type ProductCode = number | string
在这段代码中,ProductCode
可以是一个string
,也可以是一个number
。下面的代码将通过类型检查器。
type ProductCode = number | string;
const productCodeA: ProductCode = 'this-works';
const productCodeB: ProductCode = 1024;
一个联合类型可以从任何有效的TypeScript类型的联合中创建。
交叉点
你可以使用交集类型来创建一个全新的类型,它拥有所有被交集的类型的所有属性。
例如,设想你有一些总是出现在你的API调用的响应中的普通字段,然后是一些端点的特定字段。
type StatusResponse = {
status: number;
isValid: boolean;
};
type User = {
name: string;
};
type GetUserResponse = {
user: User;
};
在这种情况下,所有的响应都会有status
和isValid
属性,但只有用户响应会有额外的user
字段。要创建使用交叉类型的特定API用户调用的结果响应,请将StatusResponse
和GetUserResponse
两个类型结合起来。
type ApiGetUserResponse = StatusResponse & GetUserResponse;
类型ApiGetUserResponse
,要有StatusResponse
中的所有属性和GetUserResponse
中的可用属性。这意味着数据只有在满足两种类型的所有条件时才能通过类型检查器。下面的例子就可以了。
let response: ApiGetUserResponse = {
status: 200,
isValid: true,
user: {
name: 'Sammy'
}
}
另一个例子是数据库客户端为一个包含连接的查询返回的行的类型。你将能够使用一个交集类型来指定这样一个查询的结果。
type UserRoleRow = {
role: string;
}
type UserRow = {
name: string;
};
type UserWithRoleRow = UserRow & UserRoleRow;
后来,如果你使用一个像下面这样的fetchRowsFromDatabase()
函数。
const joinedRows: UserWithRoleRow = fetchRowsFromDatabase()
由此产生的常量joinedRows
必须有一个role
属性和一个name
属性,这两个属性都持有字符串值,以便通过类型检查器。
使用模板字符串类型
从TypeScript 4.1开始,可以使用模板字符串类型来创建类型。这将允许你创建检查特定字符串格式的类型,并为你的TypeScript项目添加更多的定制。
要创建模板字符串类型,你使用的语法几乎与你创建模板字符串字面意义时使用的相同。但你将在字符串模板内使用其他类型,而不是数值。
想象一下,你想创建一个类型,传递所有以get
开始的字符串。你将能够使用模板字符串类型来做到这一点。
type StringThatStartsWithGet = `get${string}`;
const myString: StringThatStartsWithGet = 'getAbc';
myString
将在这里通过类型检查器,因为该字符串以get
开始,然后后面是一个额外的字符串。
如果你给你的类型传递了一个无效的值,比如下面的invalidStringValue
。
type StringThatStartsWithGet = `get${string}`;
const invalidStringValue: StringThatStartsWithGet = 'something';
TypeScript编译器会给你一个错误2322
。
OutputType '"something"' is not assignable to type '`get${string}`'. (2322)
用模板字符串制作类型有助于你根据项目的具体需要定制你的类型。在下一节中,你将尝试使用类型断言,它可以为其他没有类型的数据添加一个类型。
使用类型断言
any 类型可以被用作任何值的类型,这往往不能提供获得TypeScript全部好处所需的强类型化。但有时你可能最终会有一些变量被绑定到any
,而这些变量是你无法控制的。如果你使用不是用TypeScript编写的外部依赖,或者没有可用的类型声明,就会发生这种情况。
如果你想在这些情况下使你的代码类型安全,你可以使用类型断言,这是一种将一个变量的类型改为另一种类型的方法。类型断言是通过在变量后面添加 as NewType
在你的变量后面。这将改变变量的类型为as
关键字后面指定的类型。
以下面的例子为例。
const valueA: any = 'something';
const valueB = valueA as string;
valueA
的类型是any
,但是,使用as
关键字,这段代码强迫valueB
的类型是string
。
**注意:**要断言一个TypeA
的变量具有TypeB
的类型,TypeB
必须是TypeA
的一个子类型。几乎所有的TypeScript类型,除了never
,都是any
的子类型,包括unknown
。
实用类型
在前面的章节中,你回顾了从基本类型中创建自定义类型的多种方法。但有时你并不想从头开始创建一个全新的类型。有些时候,最好是使用现有类型的一些属性,或者甚至创建一个新的类型,其形状与另一个类型相同,但所有的属性都设置为可选。
所有这些都可以使用TypeScript现有的实用类型。本节将介绍其中一些实用类型;对于所有可用类型的完整列表,请看TypeScript手册的实用类型部分。
所有的工具类型都是_通用类型_,你可以把它看作是一个接受其他类型作为参数的类型。一个通用类型可以通过使用<TypeA, TypeB, ...>
语法向它传递类型参数来识别。
Record<Key, Value>
Record
实用类型可以用来创建一个可索引类型,其方式比之前涉及的使用索引签名的方式更干净。
在你的可索引类型的例子中,你有以下类型。
type Data = {
[key: string]: any;
};
你可以使用Record
实用类型来代替这样的可索引类型。
type Data = Record<string, any>;
Record
通用的第一个类型参数是每个key
的类型。在下面的例子中,所有的键必须是字符串。
type Data = Record<string, any>
第二个类型参数是这些键的每个value
的类型。下面将允许这些值是any
。
type Data = Record<string, any>
Omit<Type, Fields>
Omit
实用类型对于在另一个类型的基础上创建一个新的类型是很有用的,同时排除一些你不希望出现在结果类型中的属性。
想象一下,你有以下类型来表示数据库中用户行的类型。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
如果在你的代码中,你正在检索所有的字段,但除了addressId
,你可以使用Omit
来创建一个没有该字段的新类型。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowWithoutAddressId = Omit<UserRow, 'addressId'>;
Omit
的第一个参数是你要建立的新类型的基础。第二个参数是你想省略的字段。
如果你在代码编辑器中把鼠标移到UserRowWithoutAddressId
上,你会发现它具有UserRow
类型的所有属性,但你省略了那些。
你可以使用字符串的联合体向第二个类型参数传递多个字段。假设你还想省略id
字段,你可以这样做。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowWithoutIds = Omit<UserRow, 'id' | 'addressId'>;
Pick<Type, Fields>
Pick
实用类型与Omit
类型完全相反。你不说你想省略的字段,而是指定你想从另一个类型中使用的字段。
使用你之前使用的相同的UserRow
。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
想象一下,你需要从数据库行中只选择email
键。你可以像这样使用Pick
来创建这样一个类型。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowWithEmailOnly = Pick<UserRow, 'email'>;
这里Pick
的第一个参数指定了你要建立的新类型的基础。第二个参数是你想包括的键。
这将相当于以下的内容。
type UserRowWithEmailOnly = {
email: string;
}
你也能够使用字符串的联合体来挑选多个字段。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowWithEmailOnly = Pick<UserRow, 'name' | 'email'>;
Partial<Type>
使用同样的UserRow
,想象一下你想创建一个新的类型,与你的数据库客户端可以用来向你的用户表中插入新数据的对象相匹配,但有一个小细节。你的数据库对所有字段都有默认值,所以你不需要传递任何字段。为了做到这一点,你可以使用Partial
实用类型来选择性地包括基本类型的所有字段。
你现有的类型,UserRow
,有所有需要的属性。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
要创建一个所有属性都是可选的新类型,你可以像下面这样使用Partial<Type>
实用类型。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowInsert = Partial<UserRow>;
这与你的UserRowInsert
,完全一样,像这样。
type UserRow = {
id: number;
name: string;
email: string;
addressId: string;
};
type UserRowInsert = {
id?: number | undefined;
name?: string | undefined;
email?: string | undefined;
addressId?: string | undefined;
};
实用类型是一个很好的资源,因为它们提供了一个更快的方式来建立类型,而不是从TypeScript的基本类型中创建它们。
总结
创建你自己的自定义类型来表示你自己代码中使用的数据结构,可以为你的项目提供一个灵活和有用的TypeScript解决方案。除了提高你自己的代码整体的类型安全,让你自己的业务对象在代码中作为数据结构的类型,将增加代码库的整体文档,并在与队友在同一代码库中工作时改善你自己的开发者经验。
关于TypeScript的更多教程,请查看我们的How To Code in TypeScript系列页面。
推荐阅读
-
epoll简介及触发模式(accept、read、send)-epoll的简单介绍 epoll在LT和ET模式下的读写方式 一、epoll的接口非常简单,一共就三个函数:1. int epoll_create(int size);创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close关闭,否则可能导致fd被耗尽。2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);epoll的事件注册函数,它不同与select是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create的返回值,第二个参数表示动作,用三个宏来表示:EPOLL_CTL_ADD:注册新的fd到epfd中;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd;第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */};events可以是以下几个宏的集合:EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLIN事件:EPOLLIN事件则只有当对端有数据写入时才会触发,所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。否则剩下的数据只有在下次对端有写入时才能一起取出来了。现在明白为什么说epoll必须要求异步socket了吧?如果同步socket,而且要求读完所有数据,那么最终就会在堵死在阻塞里。 EPOLLOUT:表示对应的文件描述符可以写; EPOLLOUT事件:EPOLLOUT事件只有在连接时触发一次,表示可写,其他时候想要触发,那要先准备好下面条件:1.某次write,写满了发送缓冲区,返回错误码为EAGAIN。2.对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。简单地说:EPOLLOUT事件只有在不可写到可写的转变时刻,才会触发一次,所以叫边缘触发,这叫法没错的!其实,如果真的想强制触发一次,也是有办法的,直接调用epoll_ctl重新设置一下event就可以了,event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。1. 缓冲区由满变空.2.同时注册EPOLLIN | EPOLLOUT事件,也会触发一次EPOLLOUT事件这个两个也会触发EPOLLOUT事件 EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);EPOLLERR:表示对应的文件描述符发生错误;EPOLLHUP:表示对应的文件描述符被挂断;EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待事件的产生,类似于select调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。-------------------------------------------------------------------------------------------- 从man手册中,得到ET和LT的具体描述如下EPOLL事件有两种模型:Edge Triggered (ET)Level Triggered (LT)假如有这样一个例子:1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符2. 这个时候从管道的另一端被写入了2KB的数据3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作4. 然后我们读取了1KB的数据5. 调用epoll_wait(2)......Edge Triggered 工作模式:如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。 i 基于非阻塞文件句柄 ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。Level Triggered 工作模式相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。然后详细解释ET, LT:LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)。在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。(未测试)另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取: 这里只是说明思路(参考《UNIX网络编程》) while(rs) {buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);if(buflen < 0){// 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读// 在这里就当作是该次事件已处理处.if(errno == EAGAIN)break; else return; }else if(buflen == 0) { // 这里表示对端的socket已正常关闭. } if(buflen == sizeof(buf) rs = 1; // 需要再次读取 else rs = 0; } 还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send内部,当写缓冲已满(send返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send内部,但暂没有更好的办法. ssize_t socket_send(int sockfd, const char* buffer, size_t buflen) { ssize_t tmp; size_t total = buflen; const char *p = buffer; while(1) { tmp = send(sockfd, p, total, 0); if(tmp < 0) { // 当send收到信号时,可以继续写,但这里返回-1. if(errno == EINTR) return -1; // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满, // 在这里做延时后再重试. if(errno == EAGAIN) { usleep(1000); continue; } return -1; } if((size_t)tmp == total) return buflen; total -= tmp; p += tmp; } return tmp; } 二、epoll在LT和ET模式下的读写方式 在一个非阻塞的socket上调用read/write函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK) 从字面上看, 意思是: * EAGAIN: 再试一次 * EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block * perror输出: Resource temporarily unavailable 总结: 这个错误表示资源暂时不够, 可能read时, 读缓冲区没有数据, 或者, write时,写缓冲区满了 。 遇到这种情况, 如果是阻塞socket, read/write就要阻塞掉。 而如果是非阻塞socket, read/write立即返回-1, 同 时errno设置为EAGAIN. 所以, 对于阻塞socket, read/write返回-1代表网络出错了. 但对于非阻塞socket, read/write返回-1不一定网络真的出错了. 可能是Resource temporarily unavailable. 这时你应该再试, 直到Resource available. 综上, 对于non-blocking的socket, 正确的读写操作为: 读: 忽略掉errno = EAGAIN的错误, 下次继续读 写: 忽略掉errno = EAGAIN的错误, 下次继续写 对于select和epoll的LT模式, 这种读写方式是没有问题的. 但对于epoll的ET模式, 这种方式还有漏洞. epoll的两种模式 LT 和 ET
-
如何在 TypeScript 中创建自定义类型
-
41 个下载免费 3D 模型的最佳网站-使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 17. Clara.io Clara.io 是一个创建 3D 内容的全球平台,也是一个培养新 3D 艺术家的社区。Clara.io 提供+100,000个免费的3D模型,包括OBJ,Blend,STL,FBX,DAE,Babylon.JS,Three.JS格式,用于 Clara.io,Unity 3D,Blender,Sketchup,Cinema 4D,3DS Max和Maya。 使用说明:免费,标准和专业帐户仅供个人使用,如果您需要将 clara.io 用于商业用途,请与销售团队联系。 18. 3DExport 3DExport是一个市场,您可以在其中购买和销售用于CG项目的3D模型,3D打印模型和纹理。它提供15 +不同的3D格式供下载,如3DS MAX(.max),Cinema4D(.c4d),Maya(.mb,.ma),Lightwave(.lwo),Softimage(.xsi),Wavefront OBJ(.obj),Autodesk FBX(.fbx)等。它还提供15种不同的语言! 使用说明:免费下载仅供个人和非商业用途。 19. 3D Warehouse 3D Warehouse是一个开放的库,允许用户共享和下载SketchUp 3D模型,用于建筑,设计,施工和娱乐!任何人都可以免费制作,修改和重新上传内容到3D仓库,您可以找到任何您能想到的东西,如家具,电子产品,室内产品等。 使用说明:3D Warehouse中的所有模型都是免费的,因此任何人都可以下载文件以用于SketchUp甚至其他软件,如AutoCAD,Revit和ArchiCAD。 20. CadNav.com CadNav是CGI平面设计师和CAD / CAM / CAE工程师的在线3D模型库,我们提供超过50000 +免费3D模型和CAD模型下载。在CadNav网站上,您可以下载高质量的多边形网格3D模型,3D CAD实体对象,纹理,Vray材料,3D作品,CAD图纸等。 使用说明:免费下载仅供个人和非商业用途。 21. All3dfree.net 就像网站名称一样,它提供免费的3D模型,还包括Vray材料,CAD块,2d和3d纹理集合,无需注册即可免费下载。它是不断更新的,因此您可以查找或请求3DS,MAX,C4D,skp,OBJ,FBX,MTL等格式的模型。 使用说明:所有资源均不允许用于商业用途,否则您将承担责任。 22. Hum3D 自2005年以来,Hum3D帮助来自3多个国家的80D艺术家节省3D建模时间,并制作逼真的3D模型,用于电影,视频游戏,AR应用程序和可视化。所有模型均由首席3D艺术家进行验证,他们检查其是否符合专业要求和最新的3D建模标准。 使用说明:免费下载仅供个人和非商业用途。 23. Artist-3D.com 艺术家-3D 库存的免费 3D 模型下载按通用类别排序。它为人体解剖学、汽车、家具、火箭、卫星等模型提供 AutoDesk 3DS Max 格式。您还可以在浏览他们的网站时找到教程和类似类型的建模。 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 24. Free the models 就像本网站的标题一样,它为3d应用程序和3d游戏引擎提供免费的内容模型。您可以为您的任何项目找到许多有趣且有用的模型!它提供3ds,wavefront,bryce,poser,lightwave,md2和unity3d格式的模型。还有一个很棒的纹理集合,可以在您最喜欢的建模和渲染程序中使用。 使用说明:您从这里下载的所有内容都可以免费使用,除非它不能包含在另一个免费的网络或CD收藏中,也不能单独出售。否则,您可以在商业游戏,3D应用程序或渲染作品中使用它。您不必提供信用,但如果您这样做,那就太好了。 25. Resources.blogscopia 本网站由一家名为Scopia的公司创建。他们制作3D图像和视频,您可以找到许多为CGI工作的信息架构设计的模型,所有这些都可以在现实生活中使用。您可以免费下载它们,但是,如果您想一次下载它们,您可以支付 3 到 9 欧元。 使用说明:您可以免费下载模型部分的所有文件。每个压缩文件都包含您也可以在此处找到的许可证。基本上,您可以对文件执行任何操作。唯一的限制是不归属于Scopia的重新分发。 26.ambientCG 1000+公共领域PBR材料适合所有人!环境CG是使用许多不同的方法和资产类型创建的,例如照片纹理(PBR),贴花(PBR),图集(PBR),照片纹理(普通),物质存档(SBSAR),雕刻画笔,3D模型和地形。您可以在所有项目中*使用它们! 使用说明:在 ambientCG 上提供下载的所有 PBR 材料、画笔、照片和 3D 模型均根据知识共享 CC0 1.0 通用许可提供。您可以复制、修改、分发和执行作品,即使是出于商业目的,也无需征得许可。信用将不胜感激。 不要满足于平庸的大理石纹理 - 立即使用我们的免费PBR大理石纹理升级您的3D设计。 27.Pixar One Twenty Eight 这是一个提供官方动画行业经典纹理的网站:皮克斯,创建于 1993 年,该纹理库包括 128 个重复纹理,现在免费提供。 它包含您来到的纹理,包括砖块和动物毛皮。肯定会有一些你可以使用的东西。 使用说明:皮克斯动画工作室的《Pixar One Twenty Eight》根据知识共享署名4.0国际许可协议进行许可。即使出于商业目的,您也可以重新混合、调整和构建您的作品,只要您以相同的条款对新创作进行信用和许可。 访问数以千计的免费纹理并提升您的设计游戏 - 立即开始下载! 28. 3DXO 即使有近 620 个免费贴纸可供下载,3DXO 也不是最大的资源,但它的内容非常有用,不需要注册。无论是简单的墙壁或地板,还是一些奇怪的小东西,您都需要的纹理都可以在此网站上看到。 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 29. 3DModelsCC0 3DModelsCC0 与其他产品的不同之处在于它包含超过 250+ 个高质量 3D 模型,并且本网站上的所有内容都是免费的,完全是公共领域!使用我们的模型时无需信用或归属! 使用说明:为每个人提供完全免费的公共领域内容。 30.Sketch up texture club Sketchup Texture Club是一个非营利性的教育和信息门户网站,由3D社区的图像促进协会管理,特别强调面向学生和建筑和室内设计专业人士的可视化和渲染技术,以及所有正在学习3D可视化的人。 使用说明:您无需支付版税或使用费。纹理可以免费下载和使用。不允许将纹理作为竞争产品出售或重新分发,即使图像被修改也是如此。 31. FlippedNormals FlippedNormal 是一个提供计算机图形和 3D 资产的市场,您可以找到许多用于雕刻、建模、纹理、概念艺术、3D 模型、游戏资产或课程的高级资产! 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 32. NASA 3D NASA 3D网站是一个在线门户,提供与太空和各种NASA任务相关的大量三维模型和模拟。该网站是用户友好的,并提供有关每个型号的详细信息。该网站允许用户探索和下载几种不同格式的模型,包括 OBJ、STL 和 FBX,只需单击下载按钮即可。 使用说明: 要下载模型,只需单击模型页面上的下载按钮并选择所需的格式。 33. 3DAGOGO (Astroprint) 3DAGOGO 是一个提供广泛 3D 模型的网站,包括角色、车辆和建筑物。3DAGOGO 的独特功能之一是它专注于适合 3D 打印的模型,使其成为希望创建物理原型或模型的设计师的绝佳资源。要使用 3DAGOGO,设计师只需在网站上搜索他们正在寻找的模型类型,然后下载 STL 格式的文件。 使用说明: 要使用 3DAGOGO,只需搜索所需的 3D 模型类型并下载 STL 格式的文件。根据需要自定义模型,并确保在将其用于商业目的之前检查使用权限。 34. FreeCAD FreeCAD是一款了不起的3D建模软件,可让您在计算机上创建令人难以置信的3D设计。该软件可免费下载和使用,它提供了广泛的工具和功能,可用于创建用于各种目的的3D模型。 该网站易于浏览,您可以找到开始使用FreeCAD的所有必要信息。此外,该网站还提供一系列教程和指南,可帮助您了解 3D 建模的来龙去脉。 使用说明: 要下载模型,请访问网站并从库中选择所需的模型。该网站还提供了一系列使用该软件的教程和指南。 35. Pinshape Pinshape是一个提供一系列3D打印模型的网站。网站上提供的型号质量很高,因此您可以确保您的最终印刷产品看起来很棒。该网站提供了广泛的模型,包括从家居用品到小雕像和珠宝的所有物品。 但这还不是Pinshape所能提供的全部!该网站还允许用户上传和共享自己的3D模型。这意味着您不仅可以下载出色的模型,还可以通过分享自己的设计为社区做出贡献。此外,Pinshape 提供了一系列自定义选项,因此您可以调整和调整模型以满足您的特定需求。 使用说明: 要下载模型,请在网站上创建一个帐户,搜索所需的模型,然后单击下载按钮。该网站还为每种型号提供了一系列定制选项。 36.Yeggi Yeggi 提供了大量免费的 3D 模型,您可以下载各种格式的模型,例如 STL、OBJ 和 FBX。该网站易于使用,您可以按关键字、类别或特定网站搜索模型。 Yeggi 对于任何寻找 3D 模型的人来说都是一个很好的资源。它提供了大量的模型集合,从日常物品到复杂的机械,以及介于两者之间的一切。该网站的收藏量在不断增长,每天都有新的型号增加。 使用说明: 要下载模型,请在网站上搜索所需的模型,然后单击下载按钮。该网站还提供指向托管模型的原始网站的链接。 37. Open3DModel 来自开放3D模型的图像 Open3DModel具有各种类别的模型,包括建筑,车辆和角色。无论您需要建筑物,汽车还是人的3D模型,都可以在此网站上找到。 该网站易于浏览,您可以按类别或关键字搜索模型。每个模型都附带预览图像和详细信息,例如文件格式、大小和多边形数量。此信息可以帮助您选择适合您需求的模型。 使用说明: 要下载模型,请访问网站,从库中选择所需的模型,然后单击下载按钮。 使用最好的 3D 资产管理工具简化您的 3D 制作流程。立即试用它们,将您的 3D 项目提升到一个新的水平! 38. 3DExport 对于那些为其 3D 设计项目寻找 3D 模型、纹理和其他资源的人来说,该平台是一个很好的资源。该网站有大量模型可供选择,包括 3D 打印对象、游戏资产等。用户可以按类别、文件格式或价格范围浏览,以找到适合其项目的完美资源。此外,3DExport 还提供一系列教程和其他 3D 资源,以帮助用户提高技能并创建更令人印象深刻的设计。 使用说明: 要使用 3DExport,只需创建一个帐户并浏览可用型号。您可以按类别、格式和价格进行搜索,以找到所需的型号。找到喜欢的模型后,只需下载它并开始在您的项目中使用它。 39.Blend Swap Blend Swap是一个社区驱动的市场,提供与Blender软件兼容的各种免费3D模型。该平台允许用户共享和下载模型、纹理和其他资产,以便在他们的项目中使用。 使用说明: 创建免费帐户后,您可以浏览社区上传的大量3D模型。当您找到要使用的一个时,只需下载它并将其导入您选择的 3D 软件即可。 40. 3DShook 3DShook 是一个高级 3D 模型市场,提供一系列用于建筑、游戏等各个行业的高质量模型。该平台提供基于订阅的模型,具有不同的定价计划,允许用户访问一系列模型。 使用说明: 注册免费帐户后,只需浏览3D模型库,选择您喜欢的模型,然后以您需要的格式下载它们。 41. Smithsonian X 3D 史密森尼 X 3D 对于正在寻找历史文物和文物的高质量 3D 模型的设计师来说,这是一个独特的资源。该平台提供了大量3D模型,这些模型是根据史密森尼博物馆和研究中心中的真实物体扫描创建的。 使用说明:
-
windows下进程间通信的(13种方法)-摘 要 本文讨论了进程间通信与应用程序间通信的含义及相应的实现技术,并对这些技术的原理、特性等进行了深入的分析和比较。 ---- 关键词 信号 管道 消息队列 共享存储段 信号灯 远程过程调用 Socket套接字 MQSeries 1 引言 ---- 进程间通信的主要目的是实现同一计算机系统内部的相互协作的进程之间的数据共享与信息交换,由于这些进程处于同一软件和硬件环境下,利用操作系统提供的的编程接口,用户可以方便地在程序中实现这种通信;应用程序间通信的主要目的是实现不同计算机系统中的相互协作的应用程序之间的数据共享与信息交换,由于应用程序分别运行在不同计算机系统中,它们之间要通过网络之间的协议才能实现数据共享与信息交换。进程间通信和应用程序间通信及相应的实现技术有许多相同之处,也各有自己的特色。即使是同一类型的通信也有多种的实现方法,以适应不同情况的需要。 ---- 为了充分认识和掌握这两种通信及相应的实现技术,本文将就以下几个方面对这两种通信进行深入的讨论:问题的由来、解决问题的策略和方法、每种方法的工作原理和实现、每种实现方法的特点和适用的范围等。 2 进程间的通信及其实现技术 ---- 用户提交给计算机的任务最终都是通过一个个的进程来完成的。在一组并发进程中的任何两个进程之间,如果都不存在公共变量,则称该组进程为不相交的。在不相交的进程组中,每个进程都独立于其它进程,它的运行环境与顺序程序一样,而且它的运行环境也不为别的进程所改变。运行的结果是确定的,不会发生与时间相关的错误。 ---- 但是,在实际中,并发进程的各个进程之间并不是完全互相独立的,它们之间往往存在着相互制约的关系。进程之间的相互制约关系表现为两种方式: ---- (1) 间接相互制约:共享CPU ---- (2) 直接相互制约:竞争和协作 ---- 竞争——进程对共享资源的竞争。为保证进程互斥地访问共享资源,各进程必须互斥地进入各自的临界段。 ---- 协作——进程之间交换数据。为完成一个共同任务而同时运行的一组进程称为同组进程,它们之间必须交换数据,以达到协作完成任务的目的,交换数据可以通知对方可以做某事或者委托对方做某事。 ---- 共享CPU问题由操作系统的进程调度来实现,进程间的竞争和协作由进程间的通信来完成。进程间的通信一般由操作系统提供编程接口,由程序员在程序中实现。UNIX在这个方面可以说最具特色,它提供了一整套进程间的数据共享与信息交换的处理方法——进程通信机制(IPC)。因此,我们就以UNIX为例来分析进程间通信的各种实现技术。 ---- 在UNIX中,文件(File)、信号(Signal)、无名管道(Unnamed Pipes)、有名管道(FIFOs)是传统IPC功能;新的IPC功能包括消息队列(Message queues)、共享存储段(Shared memory segment)和信号灯(Semapores)。 ---- (1) 信号 ---- 信号机制是UNIX为进程中断处理而设置的。它只是一组预定义的值,因此不能用于信息交换,仅用于进程中断控制。例如在发生浮点错、非法内存访问、执行无效指令、某些按键(如ctrl-c、del等)等都会产生一个信号,操作系统就会调用有关的系统调用或用户定义的处理过程来处理。 ---- 信号处理的系统调用是signal,调用形式是: ---- signal(signalno,action) ---- 其中,signalno是规定信号编号的值,action指明当特定的信号发生时所执行的动作。 ---- (2) 无名管道和有名管道 ---- 无名管道实际上是内存中的一个临时存储区,它由系统安全控制,并且独立于创建它的进程的内存区。管道对数据采用先进先出方式管理,并严格按顺序操作,例如不能对管道进行搜索,管道中的信息只能读一次。 ---- 无名管道只能用于两个相互协作的进程之间的通信,并且访问无名管道的进程必须有共同的祖先。 ---- 系统提供了许多标准管道库函数,如: pipe——打开一个可以读写的管道; close——关闭相应的管道; read——从管道中读取字符; write——向管道中写入字符; ---- 有名管道的操作和无名管道类似,不同的地方在于使用有名管道的进程不需要具有共同的祖先,其它进程,只要知道该管道的名字,就可以访问它。管道非常适合进程之间快速交换信息。 ---- (3) 消息队列(MQ) ---- 消息队列是内存中独立于生成它的进程的一段存储区,一旦创建消息队列,任何进程,只要具有正确的的访问权限,都可以访问消息队列,消息队列非常适合于在进程间交换短信息。 ---- 消息队列的每条消息由类型编号来分类,这样接收进程可以选择读取特定的消息类型——这一点与管道不同。消息队列在创建后将一直存在,直到使用msgctl系统调用或iqcrm -q命令删除它为止。 ---- 系统提供了许多有关创建、使用和管理消息队列的系统调用,如: ---- int msgget(key,flag)——创建一个具有flag权限的MQ及其相应的结构,并返回一个唯一的正整数msqid(MQ的标识符); ---- int msgsnd(msqid,msgp,msgsz,msgtyp,flag)——向队列中发送信息; ---- int msgrcv(msqid,cmd,buf)——从队列中接收信息; ---- int msgctl(msqid,cmd,buf)——对MQ的控制操作; ---- (4) 共享存储段(SM) ---- 共享存储段是主存的一部分,它由一个或多个独立的进程共享。各进程的数据段与共享存储段相关联,对每个进程来说,共享存储段有不同的虚拟地址。系统提供的有关SM的系统调用有: ---- int shmget(key,size,flag)——创建大小为size的SM段,其相应的数据结构名为key,并返回共享内存区的标识符shmid; ---- char shmat(shmid,address,flag)——将当前进程数据段的地址赋给shmget所返回的名为shmid的SM段; ---- int shmdr(address)——从进程地址空间删除SM段; ---- int shmctl (shmid,cmd,buf)——对SM的控制操作; ---- SM的大小只受主存限制,SM段的访问及进程间的信息交换可以通过同步读写来完成。同步通常由信号灯来实现。SM非常适合进程之间大量数据的共享。 ---- (5) 信号灯 ---- 在UNIX中,信号灯是一组进程共享的数据结构,当几个进程竞争同一资源时(文件、共享内存或消息队列等),它们的操作便由信号灯来同步,以防止互相干扰。 ---- 信号灯保证了某一时刻只有一个进程访问某一临界资源,所有请求该资源的其它进程都将被挂起,一旦该资源得到释放,系统才允许其它进程访问该资源。信号灯通常配对使用,以便实现资源的加锁和解锁。 ---- 进程间通信的实现技术的特点是:操作系统提供实现机制和编程接口,由用户在程序中实现,保证进程间可以进行快速的信息交换和大量数据的共享。但是,上述方式主要适合在同一台计算机系统内部的进程之间的通信。 3 应用程序间的通信及其实现技术 ---- 同进程之间的相互制约一样,不同的应用程序之间也存在竞争和协作的关系。UNIX操作系统也提供一些可用于应用程序之间实现数据共享与信息交换的编程接口,程序员可以通过自己编程来实现。如远程过程调用和基于TCP/IP协议的套接字(Socket)编程。但是,相对普通程序员来说,它们涉及的技术比较深,编程也比较复杂,实现起来困难较大。 ---- 于是,一种新的技术应运而生——通过将有关通信的细节完全掩盖在某个独立软件内部,即底层的通讯工作和相应的维护管理工作由该软件内部来实现,用户只需要将通信任务提交给该软件去完成,而不必理会它的具体工作过程——这就是所谓的中间件技术。 ---- 我们在这里分别讨论这三种常用的应用程序间通信的实现技术——远程过程调用、会话编程技术和MQSeries消息队列技术。其中远程过程调用和会话编程属于比较低级的方式,程序员参与的程度较深,而MQSeries消息队列则属于比较高级的方式,即中间件方式,程序员参与的程度较浅。 ---- 4.1 远程过程调用(RPC)
-
如何在 antdv 时间选择器 a-date-picker 中设置自定义日期选择范围(如最近一周、半月、一月)- 用 moment.js 实现
-
如何在Feign API中实现返回泛型类型的自定义解码器
-
南邮OJ Web任务大揭秘:层层挑战剖析 1. 挑战一:迷宫般的目录探索 题目作者似乎穷举了所有可能的目录组合,最终在404.php中的