后缀。这使得它们的使用对于每个POSIX对象都很常见,正如您在本章前面已经观察到的那样。
值得快速提及的是,存在更低级别的信号量 ,其中系统调用与操作系统类型密切相关或基于直接操作系统信号操纵。这种方法实施和维护起来复杂,因为它们是特定的,被认为是微调。欢迎您进一步研究您自己的系统。
C++信号量
考虑到这一点,我们希望继续提高抽象层次,因此我们将讨论C++中的信号量对象。这是C++20中的一个新特性,当你想让代码更具系统通用性时非常有用。让我们通过生产者-消费者 问题来检查它。我们需要一个在进程范围内可见并被多个线程修改的变量:atomic shared_resource
。正如本节开头提到的,信号量有助于任务同步,但我们需要一个数据竞争防护。atomic
类型确保我们遵循C++内存模型,并且编译器将根据std::memory_order
保持CPU指令序列。您可以重新访问[第6章 ]以了解数据竞争的解释。
我们将继续创建两个全局的binary_semaphore
对象,以适当地同步访问(就像乒乓球一样)。binary_semaphore
对象是最大值为1
的counting_semaphore
对象的别名。我们需要一个程序结束规则,所以我们将定义一个迭代限制。我们将要求编译器通过constexpr
关键字尽可能将其设为常量。最后但同样重要的是,我们将创建两个线程,分别扮演生产者(递增共享资源)和消费者(递减它)。让我们看看代码示例:
...
uint32_t shared_resource = 0;
binary_semaphore sem_to_produce(0);
binary_semaphore sem_to_consume(0);
constexpr uint32_t limit = 65536;
信号量被构造和初始化。我们继续进行线程。release()
函数递增一个内部计数器,向其他线程发出信号(在以下代码中标记为{2}
,类似于sem_post()
)。我们使用osyncstream(cout)
来构建非交错输出。以下是生产者线程:
void producer() {
for (auto i = 0; i <= limit; i++) {
sem_to_produce.acquire(); // {1}
++shared_resource;
osyncstream(cout) << "Before: "
<< shared_resource << endl;
sem_to_consume.release(); // {2}
osyncstream(cout) << "Producer finished!" << endl;
}
}
以下是消费者线程:
void consumer() {
for (auto i = 0; i <= limit; i++) {
osyncstream(cout) << "Waiting for data..."
<< endl;
sem_to_consume.acquire();
--shared_resource;
osyncstream(cout) << "After: "
<< shared_resource << endl;
sem_to_produce.release();
osyncstream(cout) << "Consumer finished!" << endl;
} }
int main() {
sem_to_produce.release();
jthread t1(producer); jthread t2(consumer);
t1.join(); t2.join();}
当我们反复执行这个过程时,我们会根据limit
多次看到这样的输出:
Waiting for data...
Before: 1
Producer finished!
After: 0
Consumer finished!
...
回到代码逻辑,我们必须强调,C++中的信号量被认为是轻量级的,并允许多个并发访问共享资源。但要小心:提供的代码使用了acquire()
(标记{1}
,类似于sem_wait()
),这是一个阻塞调用——例如,您的任务将被阻塞,直到信号量被释放。您可以使用try_acquire()
进行非阻塞操作。我们依赖于这两个信号量来创建可预测的操作序列。我们启动进程(例如,主线程)通过释放生产者信号量,以便首先发出信号给生产者开始。
这段代码可以通过移除C++原语并在代码中相同的位置添加前面提到的系统调用,改为使用POSIX信号量。此外,我们鼓励您尝试使用一个信号量实现相同的效果。考虑使用一个辅助变量或条件变量。请记住,这样的操作使得同步变得异构且在大规模上难以管理。
当前代码显然无法像命名信号量 那样同步多个进程,所以它并不真正是那里的替代品。我们也可能希望在并发环境中更严格地控制对共享资源的访问——例如,在某一时刻只有一个访问权限。然后,我们需要互斥锁的帮助,如下一节所述。
互斥锁
互斥锁是来自操作系统操作的一种机制。被称为临界区 的共享资源需要在没有竞态条件风险的情况下被访问。一种机制,允许在给定时刻只有一个任务修改临界区,排除其他所有任务的相同请求,被称为互斥排斥 或互斥锁 。互斥锁由操作系统内部实现,并对用户空间保持隐藏。它们提供锁定-解锁 访问功能,并被认为比信号量更严格,尽管它们被控制为二进制信号量。
重要说明
调用线程锁定资源并有义务解锁它。没有保证系统层次结构中的更高实体能够覆盖锁并解除并行功能的阻塞。建议每个锁尽快释放,以允许系统线程扩展并节省空闲时间。
POSIX互斥锁的创建和使用与未命名信号量的方式大致相同:
pthread_mutex_t global_lock;
pthread_mutex_init(&global_lock, NULL);
pthread_mutex_destroy(&global_lock);
pthread_mutex_lock(&global_lock);
pthread_mutex_unlock(&global_lock);
再次遵循函数名称的模式,因此让我们关注pthread_mutex_lock()
和pthread_mutex_unlock()
。我们使用它们来锁定和解锁临界区以进行操作,但它们无法帮助我们进行事件顺序安排。锁定资源只保证没有竞态条件。如果需要,正确的事件顺序由系统程序员设计。错误的顺序可能导致死锁 和活锁 :
死锁:一个或多个线程被阻塞,无法改变它们的状态,因为它们在等待一个永远不会发生的事件。一个常见的错误是两个(或更多)线程被相互循环——例如,一个线程在持有共享资源B的锁时等待共享资源A,而第二个线程持有A的锁但会在B解锁时解锁。由于没有人愿意首先放弃资源,两者都将保持阻塞。即使没有互斥锁,也可能发生这种行为。另一个错误是两次锁定一个互斥锁,在Linux的情况下,操作系统可以检测到。有解决死锁的算法,其中锁定多个互斥锁首次不会成功,因为死锁,但在有限次尝试后将成功。在前面的代码片段中,我们将互斥锁属性设置为NULL
,但我们可以使用它们来决定互斥锁的类型。默认的互斥锁,被称为快速互斥锁 ,不是死锁安全的。递归互斥锁 类型不会导致死锁;它将计算同一线程的锁定请求次数。错误检查互斥锁 将检测并标记双重锁定。我们鼓励您尝试使用它们。
活锁 :线程没有被阻塞,但同样无法改变它们的状态,因为它们需要共享资源才能继续前进。一个很好的现实世界例子是两个人在入口处面对面相遇。出于礼貌,他们都会让开,但他们最有可能像对方一样移动。如果发生这种情况,他们一直这样做,那么没有人会被阻塞,但同时他们也无法继续前进。
这两类错误都很常见,可以用信号量复现,因为它们也是阻塞的,在小规模系统上很少发生,容易调试。只有几个线程时,跟踪代码逻辑是琐碎的,进程是可管理的。拥有成千上万个线程的大规模系统同时执行大量的锁。错误复现通常是由于糟糕的时机和模糊的任务序列。因此,它们很难捕捉和调试,我们建议您在锁定临界区时要小心。
C++提供了灵活的锁接口。它不断升级,我们现在有几种行为可供选择。让我们并行递增一个变量。我们使用increment()
线程过程来实现清晰性,类似于之前的代码,但我们用一个互斥锁替换信号量。您可能已经猜到,代码将被保护免受竞态条件,但线程执行的顺序是未定义的。我们可以通过额外的标志、条件变量或简单的睡眠来安排这个顺序,但为了实验,让我们保持这种方式。更新的代码片段如下:
...
uint32_t shared_resource = 0;
mutex shres_guard;
constexpr uint32_t limit = INT_MAX;
我们定义了共享资源和互斥锁。让我们看看递增是如何发生的:
void increment() {
for (auto i = 0; i < limit; i++) {
lock_guard< mutex> lock(shres_guard); // {1}
++shared_resource;
}
cout << "\nIncrement finished!" << endl;
}
...
观察到的输出如下:
$ time ./test
Increment finished!
Increment finished!
real 0m0,003s
user 0m0,002s
sys 0m0,000s
正如您所看到的,仅通过移除互斥锁,就可以显著提高时间效率。为了论证,您可以将信号量重新添加,您仍然会观察到比互斥锁更快的执行速度。我们建议您查看这三种情况的代码反汇编——只使用atomic
变量、使用互斥锁和使用信号量。您会观察到atomic
对象在指令上非常简单,并在用户级别执行。由于它是真正的原子操作,CPU(或其核心)在递增期间将保持忙碌。请记住,解决数据竞争的任何技术本质上都会带来性能成本。通过最小化需要同步原语的地方及其范围,可以实现最佳性能。
重要说明
C++20为并发执行提供了令人兴奋的特性,如jthread 、协程 、更新的原子类型 和合作取消 。除了第一个之外,我们将在本书后面部分讨论其他特性。除此之外,Linux还有系统调用用于使用IPC实体,这些实体是为多进程数据交换目的而构建的。也就是说,在您尝试结合互斥锁、信号量、标志和条件变量之前,我们建议您考虑使用已有的异步工作机制。所有这些C++和Linux特性都旨在稳定地扩展,并为您节省解决方案设计时间。
到目前为止,我们所做的一切都是为了确保我们对临界区有原子访问。原子性、互斥锁和信号量将为您提供这一点——一种指导CPU关于指令范围的方法。但还有两个问题:我们能做得更快更轻吗?原子性是否意味着我们保持了指令的顺序?对第一个问题的回答是可能 。对第二个问题的回答是不 !现在我们有动力深入研究C++的内存模型 和内存顺序 。如果您对此感兴趣,我们邀请您跳转到[第9章 ],在那里我们将讨论更多有趣的并发任务。现在,我们将继续通过shmem IPC 机制讨论共享资源的主题。
使用共享内存
就像管道一样,MQ数据一旦被消费就会丢失。双工消息数据复制会增加用户空间-内核空间的调用,因此预期会有开销。shmem 机制是快速的。正如您在上一章和上一节中了解到的,数据访问的同步是必须由系统程序员解决的问题,尤其是在涉及竞态条件时。
重要的是要注意,共享内存 这个术语本身就很模糊。它是两个线程可以同时访问的全局变量吗?还是多个CPU核心使用作为共同基础的RAM的共享区域来相互传输数据?它是许多进程修改的文件系统中的文件吗?非常好的问题——谢谢您的提问!通常,所有这些都是共享资源的种类,但当我们谈论内存 这个术语时,我们真的应该考虑到主内存中对许多进程可见的区域,多个任务可以在其中交换和修改数据。不仅是任务,还有不同的处理器核心和核心复合体(如ARM),如果它们能够访问同一预定义的内存区域。这样的技术需要一个特定的配置文件——内存映射,它严格依赖于处理器,并且是实现特定的。它提供了使用例如紧耦合内存 (TCM )的机会,甚至可以加快频繁使用的代码和数据部分的速度,或使用RAM的一部分作为shmem来在核心之间交换数据。由于这太依赖于处理器,我们不打算继续讨论它。相反,我们将继续讨论Linux的shmem IPC 机制。
重要说明
进程分配它们的虚拟内存 的一部分作为共享段。传统上,操作系统禁止进程访问彼此的内存区域,但shmem是进程请求在shmem的边界内删除这一限制的机制。我们使用它来通过简单的读写操作或POSIX中已提供的函数快速摄取和修改大量数据。通过MQ或管道无法实现这样的功能。
与MQ不同,这里没有序列化或同步。系统程序员负责管理IPC的数据传输策略(再次)。但由于共享区域位于RAM中,我们有更少的上下文切换,从而减少了开销。我们可以通过以下图表来可视化它:
图7.3 - 通过进程的内存段展示shmem
shmem区域通常描绘在两个进程的地址空间之间。这样做的目的是强调该空间确实在进程间共享。实际上,这是特定于实现的,我们将其留给内核处理——我们关心的是shmem段本身的映射。它允许两个进程同时观察到相同的内容。那么,让我们开始吧。
了解mmap()和shm_open()
创建shmem映射的初始系统调用是shmget()
。这适用于任何基于Unix的操作系统,但对于符合POSIX的系统,有更舒适的方法。如果我们想象在进程的地址空间和一个文件之间进行映射,那么mmap()
函数将完成这项工作。它符合POSIX标准,并在需要时执行读操作。您可以简单地使用mmap()
指向一个常规文件,但在进程完成工作后数据将仍然存在。还记得[第3章 ]中的管道吗?这里的情况类似。有匿名管道 ,需要两个进程有家族关系 ,或者您可以有命名管道 ,允许两个无关的进程共享和传输数据。shmem 解决了类似的问题,只是不通过相同的技术。使用shmem进行IPC意味着可能不需要数据持久性——所有其他机制在消费后都会销毁数据。但如果您想要持久性,那就没问题——您可以自由地使用mmap()
系统调用与fork()
结合使用。
如果您有独立的进程,那么它们唯一知道如何定位共享区域的方式是通过其路径名。shm_open()
函数将提供一个具有名称的文件,就像mq_open()
一样——您可以在/dev/shm
中观察到它。它也需要librt
。了解这一点,您会直观地认识到我们限制了由于文件系统操作而产生的I/O开销和上下文切换,因为这个文件位于RAM中。最后但同样重要的是,这种共享内存在大小上是灵活的,可以在需要时扩展到数千兆字节。其限制取决于系统。
...
string_view SHM_ID = "/test_shm";
string_view SEM_PROD_ID = "/test_sem_prod";
string_view SEM_CONS_ID = "/test_sem_cons";
constexpr auto SHM_SIZE = 1024;
sem_t *sem_prod; sem_t *sem_cons;
void process_creator() {
...
if (int pid = fork(); pid == 0) {
// Child - used for consuming data.
if (fd = shm_open(SHM_ID.data(),
O_RDONLY,
0700); // {1}
fd == -1) {
....
这个示例非常具体,因为我们故意使用进程而不是线程。这使我们能够演示shm_open()
的使用(标记{1}
),因为不同的进程使用shmem的路径名(在编译时已知)来访问它。让我们继续阅读数据:
shm_addr = mmap(NULL, SHM_SIZE,
PROT_READ, MAP_SHARED,
fd, 0); // {2}
if (shm_addr == MAP_FAILED) {
...
}
array< char, SHM_SIZE > buffer{};
我们可以使用互斥锁,但目前我们只需要一个进程向另一个进程发出信号,表示其工作完成,因此我们应用信号量(在前一个代码块中的标记{3}
和{7}
)如下:
sem_wait(sem_cons);
memcpy(buffer.data(),
shm_addr,
buffer.size()); // {3}
if(strlen(buffer.data()) != 0) {
cout << "PID : " << getpid()
<< "consumed: " << buffer.data();
}
sem_post(sem_prod); exit(EXIT_SUCCESS);
为了使内存区域共享,我们使用mmap()
函数并选择MAP_SHARED
选项,并通过以下页面设置相应地标记读取器和写入器的凭据:PROT_READ
和PROT_WRITE
(标记{2}
和{6}
)。我们还使用ftruncate()
函数设置区域的大小(标记{5}
)。在给定的示例中,信息被写入shmem,有人必须读取它。这是一种单次射击的生产者-消费者,因为写入完成后,写入者给读取者一些时间(标记{8}
),然后将shmem设置为零(标记{9}
)并删除(标记{10}
)。现在,让我们继续进行父代码 - 数据的生产者:
else if (pid > 0) {
// Parent - used for producing data.
fd = shm_open(SHM_ID.data(),
O_CREAT | O_RDWR,
0700); // {4}
if (fd == -1) {
...
res = ftruncate(fd, SHM_SIZE); // {5}
再次,shmem区域被映射:
if (res == -1) {
...
shm_addr = mmap(NULL, SHM_SIZE,
PROT_WRITE, MAP_SHARED,
fd, 0); // {6}
if (shm_addr == MAP_FAILED) {
...
sem_wait(sem_prod);
string_view produced_data
{"Some test data, coming!"};
memcpy(shm_addr,
produced_data.data(),
produced_data.size());
sem_post(sem_cons); // {7}
waitpid(pid, NULL, 0); // {8}
res = munmap(shm_addr, SHM_SIZE); // {9}
if (res == -1) {
...
fd = shm_unlink(SHM_ID.data()); //{10}
if (fd == -1) {
如前所述,我们使用命名信号量sem_open()
(标记{11}
)来允许两个进程同步。我们无法通过本章早期讨论的信号量来实现这一点,因为它们没有名称,只在单个进程的上下文中已知。最后,我们还将信号量从文件系统中移除(标记{12}
),如下所示:
...
}
int main() {
sem_prod = sem_open(SEM_PROD_ID.data(),
O_CREAT, 0644, 0); // {11}
...
sem_post(sem_prod);
process_creator();
sem_close(sem_prod); // {12}
sem_close(sem_cons);
sem_unlink(SEM_PROD_ID.data());
sem_unlink(SEM_CONS_ID.data());
return 0;
}
程序的结果如下:
PID 3530: consumed: "Some test data, coming!"
Shmem(共享内存)是一个引人入胜的话题,我们将在[第9章 ]回到这个讨论。其中一个原因是C++允许我们适当地封装POSIX代码,并使代码更安全。与[第3章 ]中讨论的类似,将系统调用与C++代码混合使用需要经过周密的考虑。但是,探讨条件变量 机制和讨论读/写锁 是值得的。我们还将深入探讨一些memory_order
的使用案例。如果jthreads 或协程 不适用于您的用例,那么当前讨论的同步机制,连同智能指针 ,为您设计系统的最佳可能解决方案提供了灵活性。但在我们深入探讨之前,我们首先需要讨论另一个话题。让我们继续探讨计算机系统之间的通信。
通过网络与套接字进行通信
如果管道、消息队列(MQs)和共享内存(shmem)能够共同克服它们的问题,那么我们为什么还需要套接字呢?这是一个很好的问题,答案很简单——我们需要它们来在网络上不同系统之间进行通信。有了这些,我们就拥有了完整的数据交换工具集。在我们理解套接字之前,我们需要快速了解一下网络通信。无论网络类型或其介质如何,我们都必须遵循开放系统互连 (OSI )基本参考模型 所建立的设计。如今,几乎所有的操作系统都支持互联网协议 (IP )族。使用这些协议是建立与其他计算机系统通信的最简单方式。它们遵循ISO-OSI 模型中描述的分层,现在我们将快速了解一下。
OSI模型概述
OSI模型通常如下表所示表示。系统程序员通常需要它来分析他们的通信在哪里被干扰。尽管套接字旨在执行网络数据传输,但它们也适用于本地IPC。其中一个原因是,特别是在大型系统上,通信层是独立的实用程序或应用程序之上的抽象层。由于我们希望使它们对环境无感,这意味着我们不关心数据是在本地传输还是通过互联网传输,那么套接字就非常合适。即便如此,我们必须意识到我们使用的通道以及我们的数据被传输到哪里。让我们来看一看:
图7.4 - 以表格形式表示的OSI模型
全球网络通信,特别是互联网,是一个广泛且复杂的话题,我们无法在书的单个章节中完全掌握。但是,思考一下您的系统——它具有什么类型的网络通信硬件;也许您应该考虑检查一下物理层 和数据链路层 。一个简单的练习是自己配置您的家庭网络——连接设备、路由器等。系统能否被外部安全可靠地访问(如果需要的话)?然后检查网络层 、表示层 和应用层 。尝试一些端口转发 并创建一个带有数据交换加密的应用程序。软件是否能够以当前的带宽和速度快速扩展?让我们看看会话层 和传输层 能提供什么——我们将在下一段中讨论。它是否健壮并且在受到攻击时仍然可用?那么请重新审视所有层。当然,这些是简单且片面的观察,但它们可以让您重新检查您的需求。
所以,如果我们忽略硬件的角色,只专注于建立连接,我们可以回到套接字和相应的会话层 。您可能已经注意到,有些网站在一段时间后会自动将您登出。曾经想过为什么吗?嗯,会话 是在设备或端之间建立的用于信息交换的双向链接。强烈建议为会话应用时间限制并要求会话被销毁。打开的连接不仅意味着攻击者可以进行嗅探的开放通道,而且也是服务器端使用的资源。这需要计算能力,可以重定向到其他地方。服务器通常保存当前状态和会话历史,因此我们将这种通信标记为有状态的 ——至少有一台设备保持状态。但如果我们设法处理请求而不需要知道和保留以前的数据,我们可以进行无状态 的通信。尽管如此,我们仍然需要会话来建立面向连接的数据交换。一个已知的、用于该工作的协议位于传输层 ——传输控制协议 (TCP )。如果我们不想建立双向信息传输通道,只是想实现广播应用程序,那么我们可以继续进行通过用户数据报协议 (UDP )提供的无连接通信。让我们在以下章节中检查它们。
通过UDP熟悉网络
正如我们所说,这个协议可以实现无连接通信,尽管这并不意味着端点之间没有连接。这意味着它们不需要不断地保持连接来维护数据传输并在各自端口解释它。换句话说,丢失一些数据包(例如,在在线会议中导致通话时听不清某人)可能对系统本身的行为不会是决定性的。这对您来说可能很关键,但让我们诚实地对此,我们敢打赌,您更需要高速度,而这是有代价的。像域名系统 (DNS )、动态主机配置协议 (DHCP )、音视频流媒体平台等网络应用程序使用UDP。数据包的差异和丢失通常通过数据重传来处理,但这在应用层 实现,并取决于程序员的实现。从概念上讲,建立此类连接的系统调用如下:
图7.5 - UDP系统调用实现
如您所见,这实际上非常简单——通信的双方(或更多方)只需遵循该序列即可。该协议不会强制您遵循消息顺序或传输质量,它只是快速的。让我们看看以下示例,请求从套接字N 次掷骰子。代码的完整版本可以在找到:
...
constexpr auto PORT = 8080;
constexpr auto BUF_SIZE = 16;
auto die_roll() {
...
void process_creator() {
auto sockfd = 0;
array< char, BUF_SIZE > buffer{};
string_view stop{ "No more requests!" };
string_view request{ "Throw dice!" };
struct sockaddr_in servaddr {};
struct sockaddr_in cliaddr {};
如您所见,通信配置相当简单——一方必须绑定到一个地址,以便知道从哪里接收数据(标记{3}
),而另一方只需直接向套接字写入数据。套接字配置在标记{1}
处描述:
servaddr.sin_family = AF_INET; // {1}
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
if (int pid = fork(); pid == 0) {
// Child
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0))
< 0) {
const auto ecode
{ make_error_code(errc{errno}) };
cerr << "Error opening socket!";
system_error exception{ ecode };
throw exception;
} // {2}
if (bind(sockfd,
(const struct sockaddr*)&servaddr,
sizeof(servaddr)) < 0) {
const auto ecode
{ make_error_code(errc{errno}) };
cerr << "Bind failed!";
system_error exception{ ecode };
throw exception;
} // {3}
地址族被定义为AF_INET
,意味着我们将依赖于符合IPv4的地址。我们可以使用AF_INET6
表示IPv6,或者AF_BLUETOOTH
表示蓝牙。我们通过套接字的SOCK_DGRAM
设置使用UDP(标记{2}
和{10}
)。通过这种方式,我们在一个进程和另一个进程之间传输一个数字。您可以将它们想象为服务器和客户端:
socklen_t len = sizeof(cliaddr);
for (;;) {
if (auto bytes_received =
recvfrom(sockfd, buffer.data(),
buffer.size(),
MSG_WAITALL,
(struct sockaddr*)&cliaddr,
&len);
bytes_received >= 0) { // {4}
buffer.data()[bytes_received] = '\0';
cout << "Request received: "
<< buffer.data() << endl;
if (request.compare(0,
bytes_received,
buffer.data()) == 0) {
// {5}
string_view res_data
{ to_string(die_roll()) };
接收到新的掷骰子请求(标记{4}
),并打印出请求数据。然后,将请求字符串与不变的字符串进行比较,以便知道这个请求只是为了掷骰子(标记{5}
)。如您所见,我们使用了MSG_WAITALL
设置,这意味着套接字操作将阻塞调用进程——通常在没有传入数据时。此外,这是一种UDP通信,因此数据包的顺序可能不会被遵循,通过recvfrom()
接收0
字节是一个有效的用例。正因如此,我们使用额外的消息标记通信的结束(标记{6}
和{14}
)。为了简单起见,如果request.compare()
的结果不是0
,则结束通信。尽管如此,可以添加对多个选项的额外检查。我们可以使用类似的握手来首先开始通信——这取决于系统程序员的决定和应用程序的要求。继续进行客户端的功能:
sendto(sockfd, res_data.data(),
res_data.size(),
MSG_WAITALL,
(struct sockaddr*)&cliaddr,
len);
}
else break; // {6}
...
}
if (auto res = close(sockfd); res == -1) { // {8}
const auto ecode
{ make_error_code(errc{errno}) };
cerr << "Error closing socket!";
system_error exception{ ecode };
throw exception;
}
exit(EXIT_SUCCESS);
为dice_rolls
次数调用die_roll()
函数(标记{10}
和{11}
),并通过套接字发送结果(标记{12}
)。收到结果后(标记{13}
),发送结束消息(标记{14}
)。在这个示例中,我们主要使用了MSG_CONFIRM
,但您必须小心这个标志。它应该在您期望从发送的同一对等方获得响应时使用。它告诉OSI模型的数据链路层有一个成功的回复。我们可以将recvfrom()
的设置更改为MSG_DONTWAIT
,如标记{12}
所示,但实现我们自己的重试机制或切换到TCP会是一个好主意:
for (auto i = 1; i <= dice_rolls; i++) { // {11}
if (auto b_sent = sendto(sockfd,
request.data(),
request.size(),
MSG_DONTWAIT,
(const struct
sockaddr*)&servaddr,
sizeof(servaddr));
b_sent >= 0) { // {12}
...
if (auto b_recv =
recvfrom(sockfd,
buffer.data(),
buffer.size(),
MSG_WAITALL,
... { // {13}
buffer.data()[b_recv] = '\0';
cout << "Dice roll result for throw number"
<< i << " is "
<< buffer.data() << endl;
}
我们在关闭语句后关闭通信(标记{8}
和{15}
):
sendto(sockfd,
stop.data(),
stop.size(),
MSG_CONFIRM,
(const struct sockaddr*)&servaddr,
sizeof(servaddr)); // {14}
if (auto res = close(sockfd); res == -1) {
const auto ecode
{ make_error_code(errc{errno}) };
cerr << "Error closing socket!";
system_error exception{ ecode };
throw exception; // {15}
}
...
输出的缩写版本如下所示:
Choose a number of dice throws between 1 and 256.
5
Request received: Throw dice!
Dice roll result for throw number 1 is 2
....
Dice roll result for throw number 5 is 6
Request received: No more requests
我们必须设置我们的服务器可以从哪个地址和端口访问。通常,服务器计算机有许多应用程序不断运行,其中一些执行客户服务。这些服务与服务器的端口绑定,用户可以调用它们来完成一些工作——获取在线商店的内容、检查天气、获取一些银行详情、可视化图形网站等。一次只有一个应用程序(服务)可以使用给定的端口。如果您尝试在第一个激活时用另一个使用它,您将得到Address already in use
错误(或类似的)。目前,我们正在使用端口8080
,它通常为TCP/UDP(和HTTP)打开。您也可以尝试80
,但在Linux上,非root用户没有这个能力——您需要更高的用户权限来使用小于1000
的端口。最后但同样重要的是,IP地址被设置为INADDR_ANY
。当我们在单个系统上进行通信并且不关心其地址时,这通常被使用。尽管如此,如果我们想要,我们可以在运行以下命令的结果中获取它:
$ ip addr show
1: lo: < LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens32: < BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:94:a5:25 brd ff:ff:ff:ff:ff:ff
inet 192.168.136.128/24 brd 192.168.136.255 scope global dynamic noprefixroute ens32
valid_lft 1345sec preferred_lft 1345sec
inet6 fe80::b11f:c011:ba44:35e5/64 scope link noprefixroute
valid_lft forever preferred_lft forever...
在我们的例子中,这个地址是192.168.136.128
。我们可以按照下面的方式更新代码中的标记{1}
:
servaddr.sin_addr.s_addr = inet_addr("192.168.136.128");
另一个选择是使用本地主机地址——127.0.0.1
,与回环设备地址:INADDR_LOOPBACK
一起使用。我们通常用它来运行本地服务器,主要是出于测试目的。但如果我们使用确切的IP地址,那么当我们需要非常具体地指定应用程序的终点时就会这样做,如果IP地址是静态的,我们期望本地网络上的其他人能够调用它。如果我们想将其暴露给外部世界,使我们的服务可供他人使用(比如说我们拥有一个在线商店,并希望向世界提供我们的购物服务),那么我们必须考虑端口转发 。
重要提示
如今,仅仅暴露端口被认为是不安全的,因为任何人都可以访问该设备。相反,服务不仅受到防火墙、加密机制等的保护,而且还部署在虚拟机上。这为安全性增加了一个额外的层次,因为攻击者永远无法访问真实的设备,只能访问它的一个非常有限的版本。这样的决策还提高了可用性,因为被攻击的表面可以立即被移除,系统管理员可以从健康的快照中启动一个新的虚拟机,使服务再次可用。根据实现的不同,这也可以是自动化的。
最后一点——如果我们传输较大量的数据,文件的内容可能会放错位置。这再次是UDP的预期表现,如前所述,是因为数据包的排序问题。如果这不符合您的目的,并且您需要更加健壮的实现,那么您应该在下一节中检查TCP的描述。
通过TCP考虑健壮性
UDP的替代方案是TCP。它被认为是可靠的——消息是有序的,它是面向连接的,并且延迟较长。世界范围的网络 (WWW )、电子邮件、远程管理应用等都基于这个协议。您可能已经注意到(并且将在图7.6 中观察到)的是,相应的系统调用序列相同,并且名称与其他编程语言中的名称类似。这有助于不同专业领域的人们在设计网络应用时有一个共同的基础,并且能够轻松理解事件序列。这是一种非常简单的方式,帮助他们遵循OSI模型中的协议,使用这些名称作为当前通信所处位置的提示。正如我们在前一节中已经提到的,套接字用于环境无关的解决方案,其中系统有不同的操作系统,而通信应用使用不同的编程语言。例如,它们可以用C、C++、Java或Python实现,而它们的客户端可能是PHP、JavaScript等。
TCP通信的系统调用在下图中表示:
图7.6 - TCP系统调用实现
如您所见,与UDP相比,TCP更为复杂,这是预料之中的。为什么呢?好吧,我们需要保持一个已建立的连接,而内核会确认数据包传输。如果您还记得,在[第1章 ]和[第2章 ]中,我们讨论过套接字也是文件,我们可以将它们当作文件来处理。您可以简单地进行write()
和read()
调用,而不是进行send()
和recv()
调用。前者专用于网络通信,而后者通常用于所有文件。使用read()
和write()
调用将类似于通过管道进行通信,但在计算机系统之间,因此它再次取决于您的需求。
让我们看看以下示例——一个简单的请求-响应交换,我们将在本地网络的不同机器上执行,因为前面的IP地址仅对我们的内部网络有效。首先,让我们看看我们是否可以ping通服务器:
$ ping 192.168.136.128
Pinging 192.168.136.128 with 32 bytes of data:
Reply from 192.168.136.128: bytes=32 time<1ms TTL=64
Reply from 192.168.136.128: bytes=32 time<1ms TTL=64
Reply from 192.168.136.128: bytes=32 time<1ms TTL=64
所以,我们可以访问这台机器。现在,让我们运行服务器作为一个单独的应用程序
...
constexpr auto PORT = 8080;
constexpr auto BUF_SIZE = 256;
constexpr auto BACKLOG = 5;
constexpr auto SIG_MAX = 128;
void exitHandler(int sig) {
cerr << "Exit command called - terminating server!"
<< endl;
exit(SIG_MAX + sig);
}
int main() {
signal(SIGINT, exitHandler);
constexpr auto ip = "192.168.136.128";
...
我们打开套接字:
if (auto server_sock =
socket(AF_INET, SOCK_STREAM, 0);
server_sock < 0) {
我们使用SOCK_STREAM
来指示这是一个TCP连接。我们还使用硬编码的IP。在我们绑定到地址之后,我们需要监听BACKLOG
数量的活动连接。如果连接数小于BACKLOG
值,通常可以接受每个新连接:
...
server_addr.sin_addr.s_addr = inet_addr(ip);
result = bind(server_sock,
(struct sockaddr*)&server_addr,
sizeof(server_addr));
...
result = listen(server_sock, BACKLOG);
if (result != 0) {
cerr << "Cannot accept connection";
}
cout << "Listening..." << endl;
for (;;) {
addr_size = sizeof(client_addr);
client_sock =
accept(server_sock,
(struct sockaddr*)&client_addr,
&addr_size);
直到这一点,我们只有以下情况:
$ ./server
Listening...
现在,让我们准备接受客户端并处理其请求。我们使用MSG_PEEK
标志来检查传入消息,并使用MSG_DONTWAIT
发送消息。为了简单和可读性,我们不检查sendto()
的结果:
if (client_sock > 0) {
cout << "Client connected." << endl;
array< char, BUF_SIZE > buffer{};
if (auto b_recv = recv(client_sock,
buffer.data(),
buffer.size(),
MSG_PEEK);
b_recv > 0) {
buffer.data()[b_recv] = '\0';
cout << "Client request: "
<< buffer.data() << endl;
string_view response =
{ to_string(getpid()) };
cout << "Server response: "
<< response << endl;
send(client_sock,
response.data(),
response.size(),
MSG_DONTWAIT);
}
套接字在最后关闭:
...
if (auto res =
close(client_sock); res == -1) {
...
现在,让我们从另一个系统连接一个客户端。它的实现类似于UDP,只是必须调用connect()
并且必须成功:
...
if (auto res =
connect(serv_sock,
(struct sockaddr*)&addr,
sizeof(addr)); res == -1) {
const auto ecode
{ make_error_code(errc{errno}) };
cerr << "Error connecting to socket!";
system_error exception{ ecode };
throw exception;
}
string_view req = { to_string(getpid()) };
cout << "Client request: " << req << endl;
服务器的输出随之变化:
$ ./server
Listening...
Client connected.
Client request: 12502
Server response: 12501
让我们继续通信,发送信息回来:
if (auto res =
send(serv_sock,
req.data(),
req.size(),
MSG_DONTWAIT);
res >= 0) {
array< char, BUF_SIZE > buffer{};
if (auto b_recv =
recv(serv_sock,
buffer.data(),
buffer.size(),
MSG_PEEK);
res > 0) {
buffer.data()[b_recv] = '\0';
cout << "Server response: "
<< buffer.data();
...
if (auto res = close(serv_sock); res == -1) {
...
cout << "\nJob done! Disconnecting." << endl;
我们正在关闭客户端的通信,包括套接字。客户端的输出如下:
$ ./client
Client request: 12502
Server response: 12501
Job done! Disconnecting.
随着客户端的任务完成,进程终止,套接字关闭,但服务器保持活动状态以接受其他客户端,因此如果我们多次从不同的shell调用客户端,服务器的输出将如下:
Listening...
Client connected.
Client request: 12502
Server response: 12501
Client connected.
Client request: 12503
Server response: 12501
服务器将在其待处理队列中处理多达五个客户端会话。如果客户端不关闭其套接字,或者服务器在某个超时后不强制终止它们的连接,它将无法接受新客户端,将观察到Client connection failed
消息。在下一章中,我们将讨论不同的基于时间的技术,因此考虑将它们与您的实现相结合,以提供有意义的会话超时。
如果我们想优雅地处理服务器终止,我们可以简单地实现一个信号处理程序,就像我们在[第3章 ]中所做的那样。这次,我们将处理Ctrl + C 键组合,导致以下输出:
...
Client request: 12503
Server response: 12501
^CExit command called - terminating server!
正如前所述,服务器和客户端的不优雅终止可能会导致套接字挂起和端口开放。这将为系统带来问题,因为简单的应用程序重启会因“地址已被使用”(Address already in use
)而失败。如果发生这种情况,请通过ps
命令仔细检查剩余的进程。您可以通过kill
命令终止正在运行的进程,正如您在[第1章 ]和[第2章 ]中学到的。有时,这还不够,服务器也不应该那么容易就被终止。因此,您可以在检查哪些端口已经开放后更换端口。您可以通过以下命令来完成这一操作:
$ ss -tnlp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 192.168.136.128:8080 0.0.0.0:* users:(("server",pid=9965,fd=3))
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 5 127.0.0.1:631 0.0.0.0:*
LISTEN 0 5 [::1]:631 [::]:*
您可以看到服务器正在相应的地址和端口上运行:192.168.136.128:8080
。我们还可以使用以下方法检查某个端口的连接情况:
$ lsof -P -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
server 10116 oem 3u IPv4 94617 0t0 TCP oem-virtual-machine:8080 (LISTEN)
如今,随着多种在线服务的出现,我们无法避免网络编程。我们鼓励您使用这些示例作为起点进行简单的应用开发。同时,花一些时间了解更多关于多种套接字设置的信息也很重要,因为它们将帮助您满足您的特定需求。
总结
在本章中,您了解了执行IPC的各种方式。您熟悉了MQs作为简单、实时且可靠的工具,用于发送小块数据。我们还深入探讨了基本的同步机制,如信号量和互斥锁,以及它们在C++20中的接口。结合shmem,您观察到我们如何能够快速交换大量数据。最后,通过主要协议UDP和TCP向您介绍了通过套接字进行的网络通信。
复杂的应用程序通常依赖于多种IPC技术来实现它们的目标。了解它们的优点和缺点非常重要。这将帮助您决定您的特定实现。大多数时候,我们在IPC解决方案之上构建层,以确保应用程序的健壮性——例如,通过重试机制、轮询、事件驱动设计等。我们将在[第9章 ]中重新审视这些主题。下一章将为您提供自我监控可用性和性能的工具,通过不同的计时器。
你可能感兴趣的:(Linux系统的C++编程,c++)
python 获取软件的信息
隔壁小红馆
python python
根据软件名称,获取对应软件的详情fromwin32com.clientimportGetObjectdefwmi_sql_all_name(pname):_wmi=GetObject('winmgmts:')processes=_wmi.ExecQuery("Select*fromwin32_processwherename='%s'"%(pname))ifprocesses.Count:forp
c++复数加减乘除_少年,学点加减乘除呗?
健康和谐男哥
c++复数加减乘除
很多人,刚上大学或者还没到大学报到呢,就以为自己会加减乘除了。笔者本人也曾这样狂妄过~谁还没有个年少轻狂的时候啊。但是,约在十年前,也就是在本人当了十年左右的物理教授后的某一天,我发现关于加减乘除以及与这些运算相关联的数,我还真不懂。本文给即将走入或者已经入了大学的少年朋友们介绍一丁点儿关于数及其运算规则的基础知识~按说是中学的时候就该学到的,希望等到他们博士毕业的时候能自信地说:“这篇文章里的内
理解都远正态分布中指数项的精度矩阵(协方差逆矩阵)
curemoon
矩阵 算法 人工智能
之前一直不是很理解这个公式为什么用这个精度矩阵,为什么这么巧合,为什么是它,百思不得其解,最近有了一些新的理解:1.这个精度矩阵相对公平合理的用统一的方式衡量了变量间的关系,但是如果是公平合理的衡量变量间的关系,那么协方差本身就可以,那为什么又不是协方差矩阵,而是协方差矩阵的逆呢?看第二点。2.精度矩阵表征了变量之间的条件独立性,协方差矩阵是一个整体相关性的度量,比协方差矩阵更好的衡量了变量之间的
【AI大数据】数据中台的数据分析与挖掘:从数据到业务的决策
AI天才研究院
DeepSeek R1 & 大数据AI人工智能大模型 自然语言处理 人工智能 语言模型 编程实践 开发语言 架构设计
文章目录1.前言2.基本概念术语说明2.1数据模型及其实体关系实体(Entity)属性(Attribute)实体关系(EntityRelationships)2.2数据仓库2.3分析引擎2.4噪声数据2.5数据湖2.6数据总线2.7数据仓库模型3.核心算法原理和具体操作步骤以及数学公式讲解3.1数据挖掘技术概览(1)数据预处理(2)数据探查(3)数据清洗(4)数据转换(5)数据挖掘(6)知识发现(
智能汽车安全实战:车联网威胁检测从入门到精通(含CAN总线/OTA/深度学习完整代码实现)
Coderabo
DeepSeek R1模型企业级应用 汽车 安全 深度学习
车联网安全威胁检测实战:从CAN总线到OTA的全链路攻防解析(附完整Python代码)一、车联网安全威胁现状与挑战随着智能网联汽车渗透率突破60%,车端ECU数量超过150个,车载通信接口增加至8种以上,攻击面呈现指数级增长趋势。2023年某知名车企曝出的OTA升级漏洞导致50万辆汽车面临远程控制风险,凸显车联网安全检测的紧迫性。二、车联网安全检测技术框架2.1威胁检测架构设计classVehic
算法(algorithm)、CS入门技能树测评和使用体验
沉迷单车的追风少年
经验问题汇总 算法 linux 运维
目录前言CSDN技能树使用体验1、入口地址不太友好2、全面的技能点3、算法选择题里的代码单一4、知识技能学习规划5、讨论区讨论很少身边的同学反馈与建议1、对于正在找工作的同学来说一天限制6题不够练习2、评论区不活跃,有问题没有人及时讨论3、选项里语言单一,希望能多元化总结其他平台同步发布前言CSDN上线了技能树的功能,技能的范围非常全面,有算法、语言基础、数据库、Git等等,作为一名算法题和Lin
**发掘写作魅力,共创故事辉煌——计算机领域创作挑战赛**
爱编程的Loren
活动文章 活动文章
####活动介绍 亲爱的大学博主们,你们是否热爱写作,渴望展现自己的创作才华?我们为你们准备了一个绝佳的机会!这是一个为期14天的创作挑战赛,旨在鼓励大家挖掘自己的创作潜能,展现自己的写作才华。在这里,你可以自由地书写,创作出属于你的故事。 ####参与对象 热爱写作、有创作激情的大学生博主。 ####创作主题与要求 1.**内容要求**:-创作主题内容需与计算机领域相关,VIP、付
基于DeepSeek+Vue3的AI对话聊天系统开发实战
北辰alk
python 前端 AI 人工智能 ai
文章目录1.项目概述1.1项目背景1.2项目目标1.3项目功能2.技术选型与架构设计2.1技术选型3.开发环境准备3.1前端环境3.2后端环境4.DeepSeekAPI集成4.1获取API密钥4.2创建API服务4.3创建API视图5.前端页面开发5.1创建聊天组件6.前后端交互实现6.1配置Axios6.2使用Pinia管理状态7.功能扩展与优化7.1多轮对话7.2对话历史记录8.项目部署与上线
接入jest单元测试常见问题收集
佚名猫
单元测试 单元测试
前言前一段时间公司让前端写单元测试,于是乎就研究了下JEST。从安装到落地一路坎坷呀,因为淋雨过所以想为人撑伞,把自己遇到的问题和搜集到的都分享出来,欢迎大家补充内容。(技术是没边界的,即使我在公司内部也分享过,仍不影响继续分享给大家)一、安装包并运行命令后报错类问题1、未安装测试运行器运行环境jest-environment-jsdomwindowisundefined,vue-test-uti
【STM32学习记录05】STM32功能介绍—内核与存储器
触角01010001
STM32 stm32 学习 嵌入式硬件
主题内容教学目的/扩展视频STM32功能介绍(重点课程)包括ARM核心,内存,时钟,复位,电源,电压监控,看门狗,低功耗,ADC,中断,IO接口,调试模式,定时器,通信接口等功能的基础知识介绍。对单片机内部各功能有初步的认识,不要求深入了解。为未来细讲做信心上的准备。学习课程来源于洋桃电子,杜洋老师文章目录学习目标学习重点扩展相关资源学习目标对单片机内部ARM核心与内存有初步的认识,不要求深入了解
机器学习杂记
被自己蠢哭了
深度学习 机器学习
过拟合处理方法:早停正则化dropout数据增广避免局部极小值方法:以不同的初始值来训练网络,最终选取最小的。使用模拟退火技术。模拟退火在每一步都以一定的概率接受比当前解更差的结果,从而有助于跳出局部极小。在每一步迭代过程中,接受次优解的概率要随着时间的推移而逐渐降低,从而保证算法稳定。使用随机梯度下降。与标准梯度下降精确计算梯度不同,随机梯度下降算法在计算梯度时加入了随机因素。于是,即使陷入局部
使用 EXPLAIN分析结果来优化 SQL 查询
小俊学长
sql 数据库
使用EXPLAIN分析结果优化SQL查询是数据库性能调优中的一项重要技能。EXPLAIN语句能够展示数据库查询优化器对SQL查询的处理计划,从而帮助开发者识别查询中的瓶颈和低效部分。本文将详细介绍如何使用EXPLAIN分析结果来优化SQL查询。一、什么是EXPLAINEXPLAIN语句是SQL中用于显示查询执行计划的关键字。通过EXPLAIN,你可以看到数据库引擎是如何解析、优化和执行你的SQL查
Mysql-EXPLAIN分析sql语句-项目中慢SQL优化思路和示例
axiao321123
mysql
1.概述项目越来越大,业务越来越多,数据量也持续上升。这时候数据库处理的压力也在逐渐增大,所以需要对慢sql进行处理和优化。以下是项目中利用EXPLAIN分析sql语句-优化慢SQL优化思路和示例。2.示例2.1查询参数不一样,导致用到的索引不一样这是一个典型的例子,一个sql被多个地方调用,索引的情况不一样。这个sql在上次的慢sql中,已经被优化过了。这次的慢sql中又看到了它,发现这次成为慢
图像检测分析难题?三维天地引入YOLO目标检测技术带来全新解决方案!
资讯分享周
YOLO 目标检测 人工智能
在当今的检验检测认证行业,利用图像检测技术分析样本的相关指标已经成为众多检验检测领域的重要需求。无论是医学影像诊断、材料科学、食品检测还是质量控制,都依赖于精确的图像分析来提高检测的效率和准确性。然而,传统的图像处理方法面临着诸多挑战,如庞大的数据量、复杂的特征提取、漫长的模型训练周期以及复杂的公式计算等。这些问题不仅限制了检测的效率,还对结果的准确性产生了负面影响。一、实际业务操作中的工作难点1
C++ 课程设计 汇总(含源码)
三雷科技
笔记 c++ 课程设计 开发语言
C++课程设计[C++课程设计个人账务管理系统(含源码)](https://arv000.blog.csdn.net/article/details/145601695)[C++课程设计运动会分数统计(含源码)](https://arv000.blog.csdn.net/article/details/145601819)[C++课程设计打印万年历(含源码)](https://arv000.blo
OPPO手机如何正确使用金融理财计算器
资讯分享周
智能手机 金融 人工智能
OPPO手机金融理财计算器是一款在金融领域不可或缺的工具,它能够帮助我们快速准确地进行各种复杂的财务计算,为投资者和财务分析师提供重要的决策依据。无论是评估投资项目的可行性,还是计算贷款的还款金额、投资的未来价值等,金融理财计算器都能发挥重要作用,帮助我们更好地管理个人财务和进行投资分析.内部收益率(IRR)的概念内部收益率(InternalRateofReturn,简称IRR)是指使项目净现值(
徐州排名前十的小学
资讯分享周
人工智能
徐州高考连续很多年全省倒数第一,很多人都误认为徐州教育不行,其实不然,高考成绩不行,这只能说明徐州高中质量水平还有待提高。其实徐州小学、初中徐州教育还是非常不错的。今天,随记小编介绍一下徐州最新排名前10的十所重点小学,或许它们就是你的母校。1.大学路实验学校理由:学校合格率占比97.7%,在全市公办学校位居第一。(官方权威消息)只可惜办学主体是南京师范大学,徐州教学主体还需努力。2.青年路小学理
手把手教你玩转DeepSeek!100个超实用提示词免费领!
硅基打工人
AI 经验分享 笔记
大家好,我是硅基打工人呀!今天给大家送上一份超硬核干货!无论你是刚接触AI的萌新,还是想提升效率的职场达人,这100个DeepSeek专属提示词都能让你一键解锁AI的隐藏技能!文末免费领取方式,看到就是赚到~为什么你需要这100个提示词?实测案例:社区宝妈@小雨用提示词3分钟生成孩子专属睡前故事,告别灵感枯竭!职场新人@阿杰靠数据分析模板拿下转正答辩最高分!创业店主@老王用爆款文案公式让店铺销量翻
基于旭日派的Ros系统小车的再开发——使用python脚本Astra调用深度相机(学习笔记)
Z._ Yang
python 嵌入式硬件 个人开发 python
1、Ros系统的简要介绍:ROS是你的机器人的操作系统。它运行在各种不同类型的计算机上的标准Linux系统之上,如树莓派或其他的一些单片机、以及笔记本电脑或台式电脑。ROS中可执行的程序的基本单位是:节点(node)节点之间通过消息机制进行通信,这就组成了:算图(abac)节点之间通过收发消息进行通信,消息的收发机制分为:话题(topic)、服务(service)和动作(action)1.ROS提
南凌科技接入deepseek大模型,提升云网智安服务能力
NOVAnet2023
科技
南凌科技自成立以来,始终秉持创新驱动的理念,积极探索并运用新兴的人工智能技术,赋能公司服务能力和运营效率提升。2024年,南凌科技便已接入各类大模型,包含智谱、通义千问等大模型。在2024年10月的“AI+安全”研讨大会上,南凌科技CTO鲁子奕博士就已向客户、媒体等展示了南凌科技运用AI大模型进行数据处理、客服问答等场景。如今,DeepSeek以其开源特性崭露头角,不仅展现出高度的灵活性与可定制性
58同城深度学习推理平台:基于Istio的云原生网关实践解析
ITPUB-微风
云原生 深度学习 istio
在当今数字化时代,深度学习技术的快速发展为各行各业带来了革命性的变化。作为国内领先的分类信息网站,58同城一直致力于通过技术创新提升服务质量和用户体验。近期,58同城AILab推出了一项重要的技术革新——基于Istio的云原生网关深度学习推理平台。本文将从技术角度深入解析这一创新实践,探讨其架构设计、应用效果以及未来发展方向。一、深度学习推理平台的重要性深度学习推理平台在58同城的业务中扮演着至关
网易严选DevOps实践:从传统到云原生的演进
ITPUB-微风
devops 云原生 运维
在互联网行业的快速变革中,网易严选面临着前所未有的挑战和机遇。为了提升产品研发运营效率,降低创新成本,网易严选积极拥抱DevOps文化,并在其实践过程中积累了宝贵的经验。本文将深入探讨网易严选在DevOps领域的实践之旅,从传统虚拟机架构到云原生架构的演进,以及在这一过程中所面临的挑战和解决方案。一、DevOps的定义与理解DevOps,作为一种重视软件开发人员(Dev)和IT运维技术人员(Ops
数据类型与变量
5xidixi
java 开发语言
数据类型与变量1.字面常量常量即程序运行期间,固定不变的量称为常量publicclassMain{publicstaticvoidmain(String[]args){System.out.println("helloworld!");System.out.println(10);System.out.println(1.11);System.out.println('A');System.out
【Nginx学习】深入 Nginx:5步揭秘 Nginx 事件驱动架构的奥秘
墨瑾轩
一起学学Nginx【一】 架构 nginx 学习
关注墨瑾轩,带你探索编程的奥秘!超萌技术攻略,轻松晋级编程高手技术宝库已备好,就等你来挖掘订阅墨瑾轩,智趣学习不孤单即刻启航,编程之旅更有趣深入Nginx:5步揭秘Nginx事件驱动架构的奥秘!引言Nginx作为高性能的HTTP和反向代理服务器,其事件驱动架构是其高效处理高并发请求的关键。本文将带你深入了解Nginx的事件驱动架构,通过代码和详细解析,让你在阅读后能够轻松上手Nginx的核心机制。
MySQL 如何使用EXPLAIN工具优化SQL
谢同学咯
SQL MySQL学习 mysql sql 数据库
EXPLAIN是SQL查询优化中的一个重要工具,主要用于分析和诊断查询执行计划。通过EXPLAIN,我们可以了解数据库引擎(如MySQL、PostgreSQL等)是如何执行特定的查询语句的,包括是否使用了索引、表连接的方式、扫描的行数等信息。这对于优化查询性能非常有帮助。主要用途查看索引使用情况:确认查询是否正确使用了索引。评估查询效率:了解查询的执行路径和成本。优化查询语句:根据EXPLAIN的
DeepSeek赋能智慧文旅:新一代解决方案,重构文旅发展的底层逻辑
百家方案
解决方案 DeepSeek 智慧文旅
DeepSeek作为一款前沿的人工智能大模型,凭借其强大的多模态理解、知识推理和内容生成能力,正在重构文旅产业的发展逻辑,推动行业从传统的经验驱动向数据驱动、从人力密集型向智能协同型转变。一、智能服务重构:打造全域感知的智慧服务体系DeepSeek通过整合物联网、传感器、摄像头和智能设备,打破信息孤岛,实现多源数据的采集与共享。例如,故宫博物院利用自然语言处理技术,实现了128种语言的实时互译,极
Vue 状态管理
二川bro
前端 vue.js
Vue状态管理引言在现代前端开发中,随着应用规模的增大,组件之间的状态管理变得越来越复杂。Vue.js作为一个流行的前端框架,提供了多种状态管理方案,其中最常用的是Vuex。Vuex是Vue官方推荐的状态管理库,它通过集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。本文将深入探讨Vue状态管理的各个方面,从基础概念到高级用法,帮助读者全面掌握Vuex的使用技巧
内容中台重构智能服务:人工智能技术驱动精准决策
清风徐徐de来
其他
内容概要现代企业数字化转型进程中,内容中台与人工智能技术的深度融合正在重构智能服务的基础架构。通过整合自然语言处理、知识图谱构建与深度学习算法三大技术模块,该架构实现了从数据采集到决策输出的全链路智能化。在数据层,系统可对接CRM、ERP等企业软件,通过标准化接口完成多源异构数据的实时清洗与结构化处理,例如某金融科技平台利用动态知识图谱技术,将分散的客户行为数据与市场情报进行语义关联,形成可解释的
centos 安装alien
编程日记
Linux
出处:http://linux4you.in/install-netapp-oncommand-system-manger-on-centos/1.在root权限下执行命令$sudosu2.安装alien需要的依赖包#yum-yinstallPythonrpm-buildmakem4gcc-c++autoconfautomakeredhat-rpm-configmod_dav_svnmod_ssl
基于ffmpeg+openGL ES实现的视频编辑工具-字幕添加(六)
编程日记
ffmpeg 音视频
在视频编辑领域,字幕的添加是一项极为重要的功能,它能够极大地丰富视频内容,提升观众的观看体验。当我们深入探究如何实现这一功能时,FreeType开源库成为了强大助力。本文将详细阐述借助FreeType库生成字幕数据的过程,以及如何实现字幕的缩放、移动、旋转、颜色修改、对齐、字体切换等多样化编辑操作。一、生成字幕FreeType是一个高度可定制的开源字体引擎,它能够高效地处理各种字体格式,如True
SAX解析xml文件
小猪猪08
xml
1.创建SAXParserFactory实例
2.通过SAXParserFactory对象获取SAXParser实例
3.创建一个类SAXParserHander继续DefaultHandler,并且实例化这个类
4.SAXParser实例的parse来获取文件
public static void main(String[] args) {
//
为什么mysql里的ibdata1文件不断的增长?
brotherlamp
linux linux运维 linux资料 linux视频 linux运维自学
我们在 Percona 支持栏目经常收到关于 MySQL 的 ibdata1 文件的这个问题。
当监控服务器发送一个关于 MySQL 服务器存储的报警时,恐慌就开始了 —— 就是说磁盘快要满了。
一番调查后你意识到大多数地盘空间被 InnoDB 的共享表空间 ibdata1 使用。而你已经启用了 innodbfileper_table,所以问题是:
ibdata1存了什么?
当你启用了 i
Quartz-quartz.properties配置
eksliang
quartz
其实Quartz JAR文件的org.quartz包下就包含了一个quartz.properties属性配置文件并提供了默认设置。如果需要调整默认配置,可以在类路径下建立一个新的quartz.properties,它将自动被Quartz加载并覆盖默认的设置。
下面是这些默认值的解释
#-----集群的配置
org.quartz.scheduler.instanceName =
informatica session的使用
18289753290
workflow session log Informatica
如果希望workflow存储最近20次的log,在session里的Config Object设置,log options做配置,save session log :sessions run ;savesessio log for these runs:20
session下面的source 里面有个tracing
Scrapy抓取网页时出现CRC check failed 0x471e6e9a != 0x7c07b839L的错误
酷的飞上天空
scrapy
Scrapy版本0.14.4
出现问题现象:
ERROR: Error downloading <GET http://xxxxx CRC check failed
解决方法
1.设置网络请求时的header中的属性'Accept-Encoding': '*;q=0'
明确表示不支持任何形式的压缩格式,避免程序的解压
java Swing小集锦
永夜-极光
java swing
1.关闭窗体弹出确认对话框
1.1 this.setDefaultCloseOperation (JFrame.DO_NOTHING_ON_CLOSE);
1.2
this.addWindowListener (
new WindowAdapter () {
public void windo
强制删除.svn文件夹
随便小屋
java
在windows上,从别处复制的项目中可能带有.svn文件夹,手动删除太麻烦,并且每个文件夹下都有。所以写了个程序进行删除。因为.svn文件夹在windows上是只读的,所以用File中的delete()和deleteOnExist()方法都不能将其删除,所以只能采用windows命令方式进行删除
GET和POST有什么区别?及为什么网上的多数答案都是错的。
aijuans
get post
如果有人问你,GET和POST,有什么区别?你会如何回答? 我的经历
前几天有人问我这个问题。我说GET是用于获取数据的,POST,一般用于将数据发给服务器之用。
这个答案好像并不是他想要的。于是他继续追问有没有别的区别?我说这就是个名字而已,如果服务器支持,他完全可以把G
谈谈新浪微博背后的那些算法
aoyouzi
谈谈新浪微博背后的那些算法
本文对微博中常见的问题的对应算法进行了简单的介绍,在实际应用中的算法比介绍的要复杂的多。当然,本文覆盖的主题并不全,比如好友推荐、热点跟踪等就没有涉及到。但古人云“窥一斑而见全豹”,希望本文的介绍能帮助大家更好的理解微博这样的社交网络应用。
微博是一个很多人都在用的社交应用。天天刷微博的人每天都会进行着这样几个操作:原创、转发、回复、阅读、关注、@等。其中,前四个是针对短博文,最后的关注和@则针
Connection reset 连接被重置的解决方法
百合不是茶
java 字符流 连接被重置
流是java的核心部分,,昨天在做android服务器连接服务器的时候出了问题,就将代码放到java中执行,结果还是一样连接被重置
被重置的代码如下;
客户端代码;
package 通信软件服务器;
import java.io.BufferedWriter;
import java.io.OutputStream;
import java.io.O
web.xml配置详解之filter
bijian1013
java web.xml filter
一.定义
<filter>
<filter-name>encodingfilter</filter-name>
<filter-class>com.my.app.EncodingFilter</filter-class>
<init-param>
<param-name>encoding<
Heritrix
Bill_chen
多线程 xml 算法 制造 配置管理
作为纯Java语言开发的、功能强大的网络爬虫Heritrix,其功能极其强大,且扩展性良好,深受热爱搜索技术的盆友们的喜爱,但它配置较为复杂,且源码不好理解,最近又使劲看了下,结合自己的学习和理解,跟大家分享Heritrix的点点滴滴。
Heritrix的下载(http://sourceforge.net/projects/archive-crawler/)安装、配置,就不罗嗦了,可以自己找找资
【Zookeeper】FAQ
bit1129
zookeeper
1.脱离IDE,运行简单的Java客户端程序
#ZkClient是简单的Zookeeper~$ java -cp "./:zookeeper-3.4.6.jar:./lib/*" ZKClient
1. Zookeeper是的Watcher回调是同步操作,需要添加异步处理的代码
2. 如果Zookeeper集群跨越多个机房,那么Leader/
The user specified as a definer ('aaa'@'localhost') does not exist
白糖_
localhost
今天遇到一个客户BUG,当前的jdbc连接用户是root,然后部分删除操作都会报下面这个错误:The user specified as a definer ('aaa'@'localhost') does not exist
最后找原因发现删除操作做了触发器,而触发器里面有这样一句
/*!50017 DEFINER = ''aaa@'localhost' */
原来最初
javascript中showModelDialog刷新父页面
bozch
JavaScript 刷新父页面 showModalDialog
在页面中使用showModalDialog打开模式子页面窗口的时候,如果想在子页面中操作父页面中的某个节点,可以通过如下的进行:
window.showModalDialog('url',self,‘status...’); // 首先中间参数使用self
在子页面使用w
编程之美-买书折扣
bylijinnan
编程之美
import java.util.Arrays;
public class BookDiscount {
/**编程之美 买书折扣
书上的贪心算法的分析很有意思,我看了半天看不懂,结果作者说,贪心算法在这个问题上是不适用的。。
下面用动态规划实现。
哈利波特这本书一共有五卷,每卷都是8欧元,如果读者一次购买不同的两卷可扣除5%的折扣,三卷10%,四卷20%,五卷
关于struts2.3.4项目跨站执行脚本以及远程执行漏洞修复概要
chenbowen00
struts WEB安全
因为近期负责的几个银行系统软件,需要交付客户,因此客户专门请了安全公司对系统进行了安全评测,结果发现了诸如跨站执行脚本,远程执行漏洞以及弱口令等问题。
下面记录下本次解决的过程以便后续
1、首先从最简单的开始处理,服务器的弱口令问题,首先根据安全工具提供的测试描述中发现应用服务器中存在一个匿名用户,默认是不需要密码的,经过分析发现服务器使用了FTP协议,
而使用ftp协议默认会产生一个匿名用
[电力与暖气]煤炭燃烧与电力加温
comsci
在宇宙中,用贝塔射线观测地球某个部分,看上去,好像一个个马蜂窝,又像珊瑚礁一样,原来是某个国家的采煤区.....
不过,这个采煤区的煤炭看来是要用完了.....那么依赖将起燃烧并取暖的城市,在极度严寒的季节中...该怎么办呢?
&nbs
oracle O7_DICTIONARY_ACCESSIBILITY参数
daizj
oracle
O7_DICTIONARY_ACCESSIBILITY参数控制对数据字典的访问.设置为true,如果用户被授予了如select any table等any table权限,用户即使不是dba或sysdba用户也可以访问数据字典.在9i及以上版本默认为false,8i及以前版本默认为true.如果设置为true就可能会带来安全上的一些问题.这也就为什么O7_DICTIONARY_ACCESSIBIL
比较全面的MySQL优化参考
dengkane
mysql
本文整理了一些MySQL的通用优化方法,做个简单的总结分享,旨在帮助那些没有专职MySQL DBA的企业做好基本的优化工作,至于具体的SQL优化,大部分通过加适当的索引即可达到效果,更复杂的就需要具体分析了,可以参考本站的一些优化案例或者联系我,下方有我的联系方式。这是上篇。
1、硬件层相关优化
1.1、CPU相关
在服务器的BIOS设置中,可
C语言homework2,有一个逆序打印数字的小算法
dcj3sjt126com
c
#h1#
0、完成课堂例子
1、将一个四位数逆序打印
1234 ==> 4321
实现方法一:
# include <stdio.h>
int main(void)
{
int i = 1234;
int one = i%10;
int two = i / 10 % 10;
int three = i / 100 % 10;
apacheBench对网站进行压力测试
dcj3sjt126com
apachebench
ab 的全称是 ApacheBench , 是 Apache 附带的一个小工具 , 专门用于 HTTP Server 的 benchmark testing , 可以同时模拟多个并发请求。前段时间看到公司的开发人员也在用它作一些测试,看起来也不错,很简单,也很容易使用,所以今天花一点时间看了一下。
通过下面的一个简单的例子和注释,相信大家可以更容易理解这个工具的使用。
2种办法让HashMap线程安全
flyfoxs
java jdk jni
多线程之--2种办法让HashMap线程安全
多线程之--synchronized 和reentrantlock的优缺点
多线程之--2种JAVA乐观锁的比较( NonfairSync VS. FairSync)
HashMap不是线程安全的,往往在写程序时需要通过一些方法来回避.其实JDK原生的提供了2种方法让HashMap支持线程安全.
Spring Security(04)——认证简介
234390216
Spring Security 认证 过程
认证简介
目录
1.1 认证过程
1.2 Web应用的认证过程
1.2.1 ExceptionTranslationFilter
1.2.2 在request之间共享SecurityContext
1
Java 位运算
Javahuhui
java 位运算
// 左移( << ) 低位补0
// 0000 0000 0000 0000 0000 0000 0000 0110 然后左移2位后,低位补0:
// 0000 0000 0000 0000 0000 0000 0001 1000
System.out.println(6 << 2);// 运行结果是24
// 右移( >> ) 高位补"
mysql免安装版配置
ldzyz007
mysql
1、my-small.ini是为了小型数据库而设计的。不应该把这个模型用于含有一些常用项目的数据库。
2、my-medium.ini是为中等规模的数据库而设计的。如果你正在企业中使用RHEL,可能会比这个操作系统的最小RAM需求(256MB)明显多得多的物理内存。由此可见,如果有那么多RAM内存可以使用,自然可以在同一台机器上运行其它服务。
3、my-large.ini是为专用于一个SQL数据
MFC和ado数据库使用时遇到的问题
你不认识的休道人
sql C++ mfc
===================================================================
第一个
===================================================================
try{
CString sql;
sql.Format("select * from p
表单重复提交Double Submits
rensanning
double
可能发生的场景:
*多次点击提交按钮
*刷新页面
*点击浏览器回退按钮
*直接访问收藏夹中的地址
*重复发送HTTP请求(Ajax)
(1)点击按钮后disable该按钮一会儿,这样能避免急躁的用户频繁点击按钮。
这种方法确实有些粗暴,友好一点的可以把按钮的文字变一下做个提示,比如Bootstrap的做法:
http://getbootstrap.co
Java String 十大常见问题
tomcat_oracle
java 正则表达式
1.字符串比较,使用“==”还是equals()? "=="判断两个引用的是不是同一个内存地址(同一个物理对象)。 equals()判断两个字符串的值是否相等。 除非你想判断两个string引用是否同一个对象,否则应该总是使用equals()方法。 如果你了解字符串的驻留(String Interning)则会更好地理解这个问题。
SpringMVC 登陆拦截器实现登陆控制
xp9802
springMVC
思路,先登陆后,将登陆信息存储在session中,然后通过拦截器,对系统中的页面和资源进行访问拦截,同时对于登陆本身相关的页面和资源不拦截。
实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23