微软是如何解决 PC 端程序多开问题的——内部实现

前言

上次,我们通过《引用 Microsoft.VisualBasic 解决程序多开的问题》。

虽然它非常简单,但是仅适用于 WinForm 应用程序,而且还需要引用不常用的Microsoft.VisualBasic类库。

因此,我们决定深挖一下,看看具体是如何实现的。

原理

通过查看WindowsFormsApplicationBaseRun方法实现(代码有删减):

Public Sub Run(commandLine As String())If Not IsSingleInstance ThenDoApplicationModel()Else' This is a Single-Instance applicationDim pipeServer As NamedPipeServerStream = NothingIf TryCreatePipeServer(ApplicationInstanceID, pipeServer) Then' --- This is the first instance of a single-instance application to run.Using pipeServerWaitForClientConnectionsAsync(pipeServer, AddressOf OnStartupNextInstanceMarshallingAdaptor, cancellationToken:=tokenSource.Token)DoApplicationModel()End UsingElseDim awaitable = SendSecondInstanceArgsAsync(ApplicationInstanceID, commandLine, cancellationToken:=tokenSource.Token).ConfigureAwait(False)awaitable.GetAwaiter().GetResult()End IfEnd If 'Single-Instance application
End Sub

可以分析出整个流程如下:

  1. 创建一个NamedPipeServerStream实例

  2. 如果创建成功,则用WaitForClientConnectionsAsync等待第 2 个应用实例进行连接

  3. 如果创建失败,则用SendSecondInstanceArgsAsync向第 1 个应用实例发送数据

NamedPipeServerStream

使用NamedPipeServerStream类可以创建命名管道。

命名管道在管道服务器和一个或多个管道客户端之间提供进程间通信。命名管道可以是单向的,也可以是双向的。它们支持基于消息的通信,并允许多个客户端使用相同的管道名称同时连接到服务器进程。

详细使用说明,请参阅官方文档《使用命名管道进行网络进程间通信》[1]

实现

下面我们用控制台程序进行演示:

const string pipeName = "MyIO";
const PipeOptions NamedPipeOptions = PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly;static async Task Main(string[] args)
{try{using (var pipeServer = new NamedPipeServerStream(pipeName: pipeName,direction: PipeDirection.In,maxNumberOfServerInstances: 1,transmissionMode: PipeTransmissionMode.Byte,options: NamedPipeOptions)){WaitForClientConnectionsAsync(pipeServer,str => Console.WriteLine(str));Console.WriteLine($"start server {args[0]}");Console.ReadKey();}}catch{await SendSecondInstanceArgsAsync(()=> $"call from {args[0]}").ConfigureAwait(false);}
}

需要注意的是,WaitForClientConnectionsAsync不能加await,否则后续代码不能执行。

WaitForClientConnectionsAsync

实现代码如下:

private static async Task WaitForClientConnectionsAsync(NamedPipeServerStream pipeServer, Action<string> callback)
{CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();while (true){await pipeServer.WaitForConnectionAsync(cancellationTokenSource.Token).ConfigureAwait(false);try{const int bufferLength = 1024;var buffer = new byte[bufferLength];using (var stream = new MemoryStream()){while (true){var bytesRead = await pipeServer.ReadAsync(buffer.AsMemory(0, bufferLength), cancellationTokenSource.Token).ConfigureAwait(false);if (bytesRead == 0){break;}stream.Write(buffer, 0, bytesRead);}stream.Seek(0, SeekOrigin.Begin);callback(Encoding.UTF8.GetString(stream.ToArray()));}}finally{pipeServer.Disconnect();}}
}
  • 循环等待客户端连接

  • 读取客户端发送的数据,转换成字符串

  • 调用callback处理字符串,这里是str => Console.WriteLine(str)

  • 断开客户端连接

SendSecondInstanceArgsAsync

实现代码如下:

private static async Task SendSecondInstanceArgsAsync(Func<string> func)
{using (var pipeClient = new NamedPipeClientStream(serverName: ".",pipeName: pipeName,direction: PipeDirection.Out,options: NamedPipeOptions)){CancellationTokenSource cancellationTokenSource2 = new CancellationTokenSource();cancellationTokenSource2.CancelAfter(2500);await pipeClient.ConnectAsync(cancellationTokenSource2.Token).ConfigureAwait(false);await pipeClient.WriteAsync(Encoding.UTF8.GetBytes(func()), cancellationTokenSource2.Token).ConfigureAwait(false);}
}
  • 创建客户端连接本地管道服务

  • 向服务端发送func产生的数据,,这里是()=> $"call from {args[0]}"

Demo

创建多开脚本:

start " " "ConsoleApp1.exe" firstInstancestart " " "ConsoleApp1.exe" secondInstancestart " " "ConsoleApp1.exe" thirdInstance

执行后,我们发现程序只能打开一次。

并且收到了其它多开应用发过来的数据:

5e0b7c1a1ac4c3c1174cb939ca824477.png

结论

使用NamedPipeServerStream相对互斥锁Mutex的实现要复杂。

但是由于可以进行通讯,因此可以做到更灵活的控制。

比如,应用定时启动自己的另一个实例去下载更新,下载完成后通知当前应用提示用户是否更新。

想了解更多内容,请关注我的个人公众号”My IO“

参考资料

[1]

《使用命名管道进行网络进程间通信》: https://docs.microsoft.com/zh-cn/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication?WT.mc_id=DT-MVP-38491

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288882.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

svn之回滚到指定版本

1 问题 我们用svn下载了最新的代码&#xff0c;但是我们需要回退到制定的版本 2 操作 我们先svn showlog 找到对应的版本号 如果用的是svn图形界面客户端 我们点击关联svn项目的鼠标右键&#xff0c;然后有个update to reversion 写上相应的版本即可

【C语言简单说】十二:逻辑运算符

**总觉得今天更两节就好了。 (&#xffe3;.&#xffe3;) ** 困。。。(&#xffe3;.&#xffe3;) \ 今天我们来加深if的使用&#xff0c;我们来讲解运算符&&和||&#xff0c;一个叫做 与&& 一个叫做 或&&&#xff1b;我们先来说 && 我们…

正则匹配字符串无匹配不到_实现简单正则表达式匹配

这是facebook的面试题实现包含以下特殊字符的正则表达匹配“.” 匹配任意一个字符“*” 匹配任意多个前面的字符例如给定正则表达“ra.”和字符串“ray”你的函数应该返回true&#xff0c;但是仍用这个正则匹配另一个字符串“raymond”应当返回false给定正则表达“.*at”和字符…

【C语言简单说】十三:逻辑运算符||

&#xff08;﹏&#xff09;~ 更完睡觉。 这一节我们来说说逻辑或||&#xff0c;其实很简单的&#xff0c;既然你们理解了第一个逻辑与&#xff0c;那么逻辑或就没什么难度了。 我们说过逻辑与就像我们的并列关系&#xff0c;例如我们吃了苹果和李子。我们现在的逻辑或呢&a…

HQ-day2 C#语言基础

今天主要学习了C#的一些语言基础。 主要有&#xff1a;输入、输出。 一丶输入Console.readline(); 输入语句 输出console.write(); 输出语句 Console.writeline(); 输出语句自动换行 注释 CtrlK 然后 按C &#xff0c;注释选中部分 CtrlK 然后 按U&#xff0c; 取消注释 二、…

【C语言简单说】十三:变量的生命周期

这次我们就来说说生命周期的问题。其实声明周期的意思就是他这个变量的作用范围&#xff0c;啥是作用范围&#xff1f;唔。。。看我举例子吧&#xff0c;意会&#xff0c;意会。。。 首先&#xff0c;我想问一下你们&#xff0c;如果你们校长叫做小明&#xff0c;你们班也有一…

ASP.NET 6 中间件系列 - 条件中间件

这篇文章是 ASP.NET 6 中间件系列文章的第 4 部分。到目前为止&#xff0c;我们已经介绍了 ASP.NET 6 中间件的基础知识&#xff0c;展示了如何创建自定义中间件类&#xff0c;并讨论了中间件执行顺序的重要性。在本系列的最后一部分中&#xff0c;我们将展示在管道中有条件地执…

【C语言简单说】十四:for循环

说到了循环&#xff0c;我们的内容也就差不多了&#xff0c;此内容并不深入讲解太多东西&#xff0c;大家学习完&#xff0c;如果要深入的话可以查看相关书籍&#xff0c;详细大家学习完后可能会觉得看书上说的可能就看懂了。 可能会有人说为什么不说进制&#xff1f;或者说位…

使用RxJava从多个数据源获取数据

试想&#xff0c;需要一些动态数据的时候&#xff0c;只要每次都请求网络就可以了。但是&#xff0c;更有效率的做法是&#xff0c;把联网得到的数据&#xff0c;缓存到磁盘或内存。 具体的说&#xff0c;计划如下&#xff1a; 偶尔的联网操作&#xff0c;只为获取最新数据。 …

centos 零碎学习小记 2.

1.具体实验&#xff08;让网卡ethX 里设置的DNS&#xff0c;不影响DNS配置文件 /etc/resolv.conf&#xff09;1.装完系统的人都想把机器连接外网&#xff0c;都会先看看自己网卡。那么我们用神马呢?简单实用 #ifconfig -a 看看自己网卡信息2.网卡看了开始设置您的网络吧&#…

AspNetCore配置多环境log4net配置文件

前言在之前的文章中有讲到AspNetCore多环境配置文件的应用&#xff0c;我们根据自己多种环境分别配置多个appsettings.$EnvironmentName.json文件。在实际的开发中我们可能会遇到不只一个配置文件&#xff0c;如当我们使用log4net日志库时&#xff0c;喜欢使用单独的log4net.co…

tcp udp区别优缺点_一文搞懂TCP与UDP的区别

一、TCP协议&#xff1a;位于传输层&#xff0c; 提供可靠的字节流服务。所谓的字节流服务(Byte Stream Service) 是指&#xff0c; 为了方便传输&#xff0c; 将大块数据分割成以报文段(segment) 为单位的数据包进行管理。而可靠的传输服务是指&#xff0c; 能够把数据准确可靠…

《企业级ios应用开发实战》一导读

前 言 为什么写这本书随着我国3G网络和移动互联网的兴起&#xff0c;许多传统的企业应用正在从桌面向移动终端扩展&#xff0c;移动办公、移动营销、移动作业等需求日渐强烈。有迹象表明&#xff0c;传统的互联网正在向移动互联网发展。根据摩根士丹利发布的全球互联网发展趋势…

为什么要选择Hibernate

2019独角兽企业重金招聘Python工程师标准>>> 见&#xff1a; http://onecan.iteye.com/blog/1387920 转载于:https://my.oschina.net/sniperLi/blog/416396

苹果手机5s无需越狱免流_苹果越狱手机端自签名插件

unc0ver越狱官网&#xff1a; https://github.com/pwn20wndstuff/Undecimus (此网站ipa需要签名)已经签名网站&#xff1a; https://jailbreaks.fun/支持iOS11.0~12.1.3~12.4 支持 iPhone5S/SE/6/6P/6S/6SP…

【C语言简单说】十五:while循环

上一节说了for循环&#xff0c;那么我们说一下while循环&#xff1b;其实都是循环&#xff0c;就好比肯德基和徳啃鸡一样&#xff0c;卖的都是鸡~ ㄟ(▔&#xff3e;▔ㄟ) (╯▔&#xff3e;▔)╯ 上代码&#xff1a; #include<stdio.h> #include<stdlib.h> int…

我的世界java版月步教程_《我的世界》月步?幻影剑?大神才会的骚操作 第一个我就跪了!...

你知道《我的世界》玩家之间的差距有多大吗&#xff1f;“咦&#xff0c;我怎么自己动了&#xff1f;”还没看清楚自己怎么被人拉到跟前&#xff0c;敌人的钻石剑已经在方块菌身上戳出了数道伤痕。每次遇到一些操作大神&#xff0c;“佛系”玩家方块菌真的被打得找不到北&#…

大数据能否力挽国足败落狂澜?

叙利亚多惨&#xff0c;媒体大概已经全方位铺陈过了。作为难民数量最多的国家&#xff0c;战争冲突爆发后&#xff0c;叙利亚足球联赛被迫停摆。虽然在2014年恢复了联赛的进行&#xff0c;但观众数量锐减&#xff0c;从上万到百人&#xff0c;更要直面数以百计的叙利亚球员不得…

最通俗易懂的依赖注入之服务容器与作用域

推荐关注「码侠江湖」加星标&#xff0c;时刻不忘江湖事这篇文章是 ASP.NET 6 依赖注入系列文章的第 3 篇&#xff0c;点击上方蓝字可以阅读整个系列。在上一篇文章中&#xff0c;我们讨论依赖注入的基本用法与生命周期。接下来&#xff0c;在这篇文章中&#xff0c;我们继续深…

深度学习入门(python)考试速成均方误差

均方误差 表示神经网络的输出&#xff0c;表示监督数据&#xff0c;表示数据的维度。 这里神经网络的输出y是softmax函数的输出 数组元素的索引从第一个开始依次对应数组“0”&#xff0c;“1”&#xff0c;“2”&#xff0c;...... 由于softmax函数的输出可理解为概率 由此…