C# 软件Licence应用实例

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 我们在使用一些需要购买版权的软件产品时,或者我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件。简单的是一串序列码或者一个许可证文件,复杂的是一个定制化插件包。于是有的小伙伴就开始好奇这个许可是怎么实现的,特别是在离线情况下它是怎么给软件授权,同时又能避免被破解的。

我们在使用一些需要购买版权的软件产品时,或者我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件。简单的是一串序列码或者一个许可证文件,复杂的是一个定制化插件包。于是有的小伙伴就开始好奇这个许可是怎么实现的,特别是在离线情况下它是怎么给软件授权,同时又能避免被破解的。

 

License应用场景

 

本文主要介绍的是许可证形式的授权。

 

1. 如何控制只在指定设备上使用

 

如果不控制指定设备,那么下发了许可证,只要把软件复制多份安装则可到处使用,不利于版权维护,每个设备都有唯一标识:mac地址,ip地址,主板序列号等,在许可证中指定唯一标识则只能指定设备使用。

 

2. 如何控制软件使用期限

 

为了版权可持续性收益,对软件使用设置期限,到期续费等,则需要在许可证中配置使用起止日期。

 

Licence实现方案

 

1. 流程设计

 

  • 形式:许可证以文件形式下发,放在客户端电脑指定位置
  • 内容:以上控制内容以dom节点形式放在文件中
  • 流程:将控制项加密后写入license文件节点,部署到客户机器,客户机使用时再读取license文件内容与客户机实际参数进行匹配校验

 

2. 文件防破解

 

  • 防止篡改:文件内容加密,使用AES加密,但是AES加密解密都是使用同一个key;使用非对称公私钥(本文使用的RSA)对内容加密解密,但是对内容长度有限制;综合方案,将AES的key(内部定义)用RSA加密,公钥放在加密工具中,内部持有,私钥放在解密工具中,引入软件产品解密使用。
  • 防止修改系统时间绕过许可证使用时间:许可证带上发布时间戳,并定时修改运行时间记录到文件,如果系统时间小于这个时间戳,就算大于许可证限制的起始时间也无法使用
  • 提高破解难度:懂技术的可以将代码反编译过来修改代码文件直接绕过校验,所以需要进行代码混淆,有测试过xjar的混淆效果比较好。

 

Licence验证流程图

 

关于Licence验证软件合法性流程图,如下所示:

 

核心源码

 

本实例主要讲解Licence的实际验证过程,分为三部分:

  1. 测试客户端【LicenceTest】,主要用于模拟客户端验证Licence的过程。
  2. 生成工具【LicenceTool】,主要用于根据客户生成的电脑文件,生成对应的Licence。
  3. LicenceCommon,Licence公共通用类,主要实现电脑信息获取,非对称加密,文件保存等功能。

 

1. LicenceCommon

 

1.1 电脑信息获取

 

主要通过ManagementClass进行获取客户端电脑硬件相关配置信息,如下所示:

usingMicrosoft.Win32;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Management;
usingSystem.Net.NetworkInformation;
usingSystem.Text;
usingSystem.Threading.Tasks;
namespaceDemoLicence.Common{
publicclassComputerHelper    {
publicstaticDictionary<string,string>GetComputerInfo()
        {
varinfo=newDictionary<string,string>();
stringcpu=GetCPUInfo();
stringbaseBoard=GetBaseBoardInfo();
stringbios=GetBIOSInfo();
stringmac=GetMACInfo();
info.Add("cpu", cpu);
info.Add("baseBoard", baseBoard);
info.Add("bios", bios);
info.Add("mac", mac);
returninfo;
        }
privatestaticstringGetCPUInfo()
        {
stringinfo=string.Empty;
info=GetHardWareInfo("Win32_Processor", "ProcessorId");
returninfo;
        }
privatestaticstringGetBIOSInfo()
        {
stringinfo=string.Empty;
info=GetHardWareInfo("Win32_BIOS", "SerialNumber");
returninfo;
        }
privatestaticstringGetBaseBoardInfo()
        {
stringinfo=string.Empty;
info=GetHardWareInfo("Win32_BaseBoard", "SerialNumber");
returninfo;
        }
privatestaticstringGetMACInfo()
        {
stringinfo=string.Empty;
info=GetMacAddress();//GetHardWareInfo("Win32_NetworkAdapterConfiguration", "MACAddress");returninfo;
        }
privatestaticstringGetMacAddress()
        {
varmac="";
varmc=newManagementClass("Win32_NetworkAdapterConfiguration");
varmoc=mc.GetInstances();
foreach (varoinmoc)
            {
varmo= (ManagementObject)o;
if (!(bool)mo["IPEnabled"]) continue;
mac=mo["MacAddress"].ToString();
break;
            }
returnmac;
        }
privatestaticstringGetHardWareInfo(stringtypePath, stringkey)
        {
try            {
ManagementClassmanagementClass=newManagementClass(typePath);
ManagementObjectCollectionmn=managementClass.GetInstances();
PropertyDataCollectionproperties=managementClass.Properties;
foreach (PropertyDatapropertyinproperties)
                {
if (property.Name==key)
                    {
foreach (ManagementObjectminmn)
                        {
returnm.Properties[property.Name].Value.ToString();
                        }
                    }
                }
            }
catch (Exceptionex)
            {
//这里写异常的处理            }
returnstring.Empty;
        }
    }
}

 

1.3 RSA非对称加密

 

主要对客户端提供的电脑信息及有效期等内容,进行非对称加密,如下所示:

publicclassRSAHelper{
privatestaticstringkeyContainerName="star";
privatestaticstringm_PriKey=string.Empty;
privatestaticstringm_PubKey=string.Empty;
publicstaticstringPriKey    {
get        {
returnm_PriKey;
        }
set        {
m_PriKey=value;
        }
    }
publicstaticstringPubKey    {
get        {
returnm_PubKey;
        }
set        {
m_PubKey=value;
        }
    }
publicstaticstringEncrypto(stringsource)
    {
if (string.IsNullOrEmpty(m_PubKey) &&string.IsNullOrEmpty(m_PriKey))
        {
generateKey();
        }
returngetEncryptoInfoByRSA(source);
    }
publicstaticstringDecrypto(stringdest)
    {
if (string.IsNullOrEmpty(m_PubKey) &&string.IsNullOrEmpty(m_PriKey))
        {
generateKey();
        }
returngetDecryptoInfoByRSA(dest);
    }
publicstaticvoidgenerateKey()
    {
CspParametersm_CspParameters;
m_CspParameters=newCspParameters();
m_CspParameters.KeyContainerName=keyContainerName;
RSACryptoServiceProviderasym=newRSACryptoServiceProvider(m_CspParameters);
m_PriKey=asym.ToXmlString(true);
m_PubKey=asym.ToXmlString(false);
asym.PersistKeyInCsp=false;
asym.Clear();
    }
privatestaticstringgetEncryptoInfoByRSA(stringsource)
    {
byte[] plainByte=Encoding.ASCII.GetBytes(source);
//初始化参数RSACryptoServiceProviderasym=newRSACryptoServiceProvider();
asym.FromXmlString(m_PubKey);
intkeySize=asym.KeySize/8;//非对称加密,每次的长度不能太长,否则会报异常intbufferSize=keySize-11;
if (plainByte.Length>bufferSize)
        {
thrownewException("非对称加密最多支持【"+bufferSize+"】字节,实际长度【"+plainByte.Length+"】字节。");
        }
byte[] cryptoByte=asym.Encrypt(plainByte, false);
returnConvert.ToBase64String(cryptoByte);
    }
privatestaticstringgetDecryptoInfoByRSA(stringdest)
    {
byte[] btDest=Convert.FromBase64String(dest);
//初始化参数RSACryptoServiceProviderasym=newRSACryptoServiceProvider();
asym.FromXmlString(m_PriKey);
intkeySize=asym.KeySize/8;//非对称加密,每次的长度不能太长,否则会报异常//int bufferSize = keySize - 11;if (btDest.Length>keySize)
        {
thrownewException("非对称解密最多支持【"+keySize+"】字节,实际长度【"+btDest.Length+"】字节。");
        }
byte[] cryptoByte=asym.Decrypt(btDest, false);
returnEncoding.ASCII.GetString(cryptoByte);
    }
}

 

1.3 生成文件

 

主要是加密后的信息,和解密秘钥等内容,保存到文件中,如下所示:

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Threading.Tasks;
namespaceDemoLicence.Common{
publicclassRegistFileHelper    {
publicstaticstringComputerInfofile="ComputerInfo.key";
publicstaticstringRegistInfofile="Licence.key";
publicstaticvoidWriteRegistFile(stringinfo,stringkeyFile)
        {
stringtmp=string.IsNullOrEmpty(keyFile)?RegistInfofile:keyFile;
WriteFile(info, tmp);
        }
publicstaticvoidWriteComputerInfoFile(stringinfo)
        {
WriteFile(info, ComputerInfofile);
        }
publicstaticstringReadRegistFile(stringkeyFile)
        {
stringtmp=string.IsNullOrEmpty(keyFile) ?RegistInfofile : keyFile;
returnReadFile(tmp);
        }
publicstaticstringReadComputerInfoFile(stringfile)
        {
stringtmp=string.IsNullOrEmpty(file) ?ComputerInfofile : file;
returnReadFile(tmp);
        }
privatestaticvoidWriteFile(stringinfo, stringfileName)
        {
try            {
using (StreamWritersw=newStreamWriter(fileName, false))
                {
sw.Write(info);
sw.Close();
                }
            }
catch (Exceptionex)
            {
            }
        }
privatestaticstringReadFile(stringfileName)
        {
stringinfo=string.Empty;
try            {
using (StreamReadersr=newStreamReader(fileName))
                {
info=sr.ReadToEnd();
sr.Close();
                }
            }
catch (Exceptionex)
            {
            }
returninfo;
        }
    }
}

以上这三部分,各个功能相互独立,通过LicenceHelper相互调用,如下所示:

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Runtime.CompilerServices;
usingSystem.Text;
usingSystem.Threading.Tasks;
namespaceDemoLicence.Common{
publicclassLicenceHelper    {
/// <summary>/// 获取电脑信息,并生成文件/// </summary>publicstaticstringGetComputerInfoAndGenerateFile()
        {
stringcomputerKeyFile=string.Empty;
try            {
varinfo=GetComputerInfo();
if (info!=null&&info.Count>0)
                {
//获取到电脑信息varstrInfo=newStringBuilder();
foreach (varcomputerininfo)
                    {
strInfo.AppendLine($"{computer.Key}={computer.Value}");
                    }
RegistFileHelper.WriteComputerInfoFile(strInfo.ToString());
computerKeyFile=RegistFileHelper.ComputerInfofile;
                }
            }catch(Exceptionex)
            {
throwex;
            }
returncomputerKeyFile;
        }
publicstaticDictionary<string,string>GetComputerInfo()
        {
varinfo=ComputerHelper.GetComputerInfo();
returninfo;
        }
publicstaticboolCheckLicenceKeyIsExists()
        {
varkeyFile=RegistFileHelper.RegistInfofile;
if (File.Exists(keyFile))
            {
returntrue;
            }
else            {
returnfalse;
            }
        }
publicstaticstringGetComputerInfo(stringcomputerInfoFile)
        {
returnRegistFileHelper.ReadComputerInfoFile(computerInfoFile);
        }
publicstaticvoidGenerateLicenceKey(stringinfo,stringkeyfile)
        {
stringencrypto=RSAHelper.Encrypto(info);
stringpriKey=RSAHelper.PriKey;//公钥加密,私钥解密byte[] priKeyBytes=Encoding.ASCII.GetBytes(priKey);
stringpriKeyBase64=Convert.ToBase64String(priKeyBytes);
StringBuilderkeyInfo=newStringBuilder();
keyInfo.AppendLine($"prikey={priKeyBase64}");
keyInfo.AppendLine($"encrypto={encrypto}");
RegistFileHelper.WriteRegistFile(keyInfo.ToString(), keyfile);
        }
publicstaticstringReadLicenceKey(stringkeyfile)
        {
varkeyInfo=RegistFileHelper.ReadRegistFile(keyfile);
if (keyInfo==null)
            {
returnstring.Empty;
            }
string[] keyInfoArr=keyInfo.Split("\r\n");
varpriKeyBase64=keyInfoArr[0].Substring(keyInfoArr[0].IndexOf("=")+1);
varencrypto=keyInfoArr[1].Substring(keyInfoArr[1].IndexOf("=")+1);
varpriKeyByte=Convert.FromBase64String(priKeyBase64);
varpriKey=Encoding.ASCII.GetString(priKeyByte);
RSAHelper.PriKey=priKey;
varinfo=RSAHelper.Decrypto(encrypto);
returninfo;
        }
publicstaticstringGetDefaultRegisterFileName()
        {
returnRegistFileHelper.RegistInfofile;
        }
publicstaticstringGetDefaultComputerFileName()
        {
returnRegistFileHelper.ComputerInfofile;
        }
publicstaticstringGetPublicKey()
        {
if (string.IsNullOrEmpty(RSAHelper.PubKey))
            {
RSAHelper.generateKey();
            }
returnRSAHelper.PubKey;
        }
publicstaticstringGetPrivateKey()
        {
if (string.IsNullOrEmpty(RSAHelper.PriKey))
            {
RSAHelper.generateKey();
            }
returnRSAHelper.PriKey;
        }
    }
}

 

2. 客户端LicenceTest

 

客户端验证Licence的有效性,当Licence有效时,正常使用软件,当Licence无效时,则不能正常使用软件。如下所示:

usingDemoLicence.Common;
namespaceLicenceTest{
publicpartialclassMainForm : Form    {
publicMainForm()
        {
InitializeComponent();
        }
privatevoidMainForm_Load(objectsender, EventArgse)
        {
try            {
stringinfo=string.Empty;
stringmsg=string.Empty;
//初始化加载if (LicenceHelper.CheckLicenceKeyIsExists())
                {
stringkeyFile=LicenceHelper.GetDefaultRegisterFileName();
info=LicenceHelper.ReadLicenceKey(keyFile);
                }
else                {
vardialogResult=MessageBox.Show("没有找到默认首选文件,是否手动选择授权文件?", "询问", MessageBoxButtons.YesNo);
if (dialogResult==DialogResult.Yes)
                    {
OpenFileDialogopenFileDialog=newOpenFileDialog();
openFileDialog.Title="请选择授权文件";
openFileDialog.FileName=LicenceHelper.GetDefaultRegisterFileName();
if (openFileDialog.ShowDialog() ==DialogResult.OK)
                        {
varkeyFile=openFileDialog.FileName;
info=LicenceHelper.ReadLicenceKey(keyFile);
//验证成功后,将手动选择的文件复制到程序根目录,且修改为默认名称File.Copy(keyFile, LicenceHelper.GetDefaultRegisterFileName());
                        }
else                        {
stringcomputerFile=LicenceHelper.GetComputerInfoAndGenerateFile();
if (!string.IsNullOrEmpty(computerFile))
                            {
msg=$"您还没有被授权,请将程序根目录下的{computerFile}文件,发送到管理员,获取Licence.";
                            }
                        }
                    }
else                    {
stringcomputerFile=LicenceHelper.GetComputerInfoAndGenerateFile();
if (!string.IsNullOrEmpty(computerFile))
                        {
msg=$"您还没有被授权,请将程序根目录下的{computerFile}文件,发送到管理员,获取Licence.";
                        }
                    }
                }
if (!string.IsNullOrEmpty(info) &&string.IsNullOrEmpty(msg))
                {
string[] infos=info.Split("\r\n");
if (infos.Length>0)
                    {
vardicInfo=newDictionary<string, string>();
foreach (varinfo2ininfos)
                        {
if (string.IsNullOrEmpty(info2))
                            {
continue;
                            }
varinfo2Arr=info2.Split("=");
dicInfo.Add(info2Arr[0], info2Arr[1]);
                        }
if (dicInfo.Count>0)
                        {
stringlocalMacAddress=string.Empty;
varcomputerInfo=LicenceHelper.GetComputerInfo();
if (computerInfo!=null)
                            {
localMacAddress=computerInfo["mac"];
                            }
//比较本地信息和Licence中的信息是否一致if (localMacAddress==dicInfo["mac"])
                            {
varendTime=DateTime.Parse(dicInfo["endTime"]);
if (DateTime.Now<endTime)
                                {
//在有效期内,可以使用                                }
else                                {
msg=$"软件授权使用时间范围:[{endTime}之前],已过期";
                                }
                            }
else                            {
msg="软件Licence不匹配";
                            }
                        }
else                        {
msg=$"软件Licence非法.";
                        }
                    }
else                    {
msg=$"软件Licence非法.";
                    }
                }
if (!string.IsNullOrEmpty(msg))
                {
MessageBox.Show(msg);
foreach (varcontrolinthis.Controls)
                    {
                        (controlasControl).Enabled=false;
                    }
return;
                }
            }
catch (Exceptionex)
            {
stringerror=$"程序异常,请联系管理人员:{ex.Message}\r\n{ex.StackTrace}";
MessageBox.Show(error);
foreach (varcontrolinthis.Controls)
                {
                    (controlasControl).Enabled=false;
                }
            }
        }
    }
}

 

3. Licence生成工具

 

LicenceTool主要根据客户端提供的电脑信息,生成对应的Licence,然后再发送给客户端,以此达到客户端电脑的授权使用软件的目的。如下所示:

usingDemoLicence.Common;
usingSystem.Text;
namespaceLicenceTool{
publicpartialclassMainForm : Form    {
publicMainForm()
        {
InitializeComponent();
        }
privatevoidMainForm_Load(objectsender, EventArgse)
        {
this.txtPublicKey.Text=LicenceHelper.GetPublicKey();
this.txtPrivateKey.Text=LicenceHelper.GetPrivateKey();
        }
privatevoidbtnBrowser_Click(objectsender, EventArgse)
        {
OpenFileDialogofd=newOpenFileDialog();
ofd.Filter="电脑信息文件|*.key";
ofd.Multiselect=false;
ofd.Title="请选择电脑信息文件";
ofd.FileName=LicenceHelper.GetDefaultComputerFileName();
if (ofd.ShowDialog() ==DialogResult.OK)
            {
this.txtSourceFile.Text=ofd.FileName;
            }
        }
privatevoidbtnGenerate_Click(objectsender, EventArgse)
        {
try            {
if (string.IsNullOrEmpty(this.txtSourceFile.Text))
                {
MessageBox.Show("请先选择电脑信息文件");
return;
                }
if (File.Exists(this.txtSourceFile.Text))
                {
//读取电脑文件varinfo=LicenceHelper.GetComputerInfo(this.txtSourceFile.Text);
intdays=GetLicenceDays();
varkeyInfos=newStringBuilder(info);
varbeginTime=DateTime.Now;
varendTime=DateTime.Now.AddDays(days);
//keyInfos.AppendLine($"beginTime={beginTime.ToString("yyyy-MM-dd HH:mm:ss")}");keyInfos.AppendLine($"endTime={endTime.ToString("yyyy-MM-ddHH:mm:ss")}");
//info=keyInfos.ToString();
SaveFileDialogsaveFileDialog=newSaveFileDialog();
saveFileDialog.Title="保存生成的Licence文件";
saveFileDialog.FileName=LicenceHelper.GetDefaultRegisterFileName();
if (saveFileDialog.ShowDialog() ==DialogResult.OK)
                    {
LicenceHelper.GenerateLicenceKey(info, saveFileDialog.FileName);
MessageBox.Show("生成成功");
                    }
                }
else                {
MessageBox.Show("电脑信息文件不存在!");
return;
                }
            }catch(Exceptionex)
            {
stringerror=$"生成出错:{ex.Message}\r\n{ex.StackTrace}";
MessageBox.Show(error);
            }
        }
/// <summary>/// 获取有效期天数/// </summary>/// <returns></returns>privateintGetLicenceDays()
        {
intdays=1;
RadioButton[] rbtns=newRadioButton[] { this.rbtSeven, this.rbtnTen, this.rbtnFifteen, this.rbtnThirty, this.rbtnSixty, this.rbtnSixMonth, this.rbtnNinety, this.rbtnSixMonth, this.rbtnForver };
foreach (RadioButtonrbinrbtns)
            {
if (rb.Checked)
                {
if (!int.TryParse(rb.Tag.ToString(), outdays))
                    {
days=0;
                    }
break;
                }
            }
days=days==-1?9999 : days;//永久要转换一下returndays;
        }
    }
}

 

测试验证

 

启动软件时会进行校验,在没有Licence时,会有信息提示,且无法使用软件,如下所示:

Lincence生成工具

根据客户提供的电脑信息文件,生成对应的Licence,如下所示:

生成Licence放在客户端默认目录下,即可正常使用软件,如下所示:

注意:非对称加密每次生成的秘钥都是不同的,所以需要将解密秘钥一起保存到生成的Licence文件中,否则秘钥不同,则无法解密。

生成的电脑信息文件ComputerInfo.key示例如下所示:

生成的Licence.key文件内容,如下所示:

 

源码下载

 

源码下载可以通过以下3种方式,

 

1. 公众号关键词回复

 

关注个人公众号,回复关键字【Licence】获取源码,如下所示:

 

2. 通过gitee(码云)下载

 

本示例中相关源码,已上传至gitee(码云),链接如下:

 

3. 通过CSDN进行下载

 

通过CSDN上的资源进行付费下载,不贵不贵,也就一顿早餐的钱。

https://download.csdn.net/download/fengershishe/88294433?spm=1001.2014.3001.5501

以上就是软件Licence应用实例的全部内容,希望可以抛砖引玉,一起学习,共同进步。学习编程,从关注【老码识途】开始!!!

相关文章
|
1月前
|
存储 安全 物联网
C# 在物联网 (IoT) 应用中的应用
本文介绍了C#在物联网(IoT)应用中的应用,涵盖基础概念、优势、常见问题及其解决方法。重点讨论了网络通信、数据处理和安全问题,并提供了相应的代码示例,旨在帮助开发者更好地利用C#进行IoT开发。
55 3
|
1月前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
37 3
|
1月前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
62 1
|
1月前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
18 0
|
3月前
|
设计模式 开发框架 前端开发
MVC 模式在 C# 中的应用
MVC(Model-View-Controller)模式是广泛应用于Web应用程序开发的设计模式,将应用分为模型(存储数据及逻辑)、视图(展示数据给用户)和控制器(处理用户输入并控制模型与视图交互)三部分,有助于管理复杂应用并提高代码可读性和维护性。在C#中,ASP.NET MVC框架常用于构建基于MVC模式的Web应用,通过定义模型、控制器和视图,实现结构清晰且易维护的应用程序。
65 2
|
3月前
|
编译器 C# Android开发
Uno Platform 是一个用于构建跨平台应用程序的强大框架,它允许开发者使用 C# 和 XAML 来创建适用于多个平台的应用
Uno Platform 是一个用于构建跨平台应用程序的强大框架,它允许开发者使用 C# 和 XAML 来创建适用于多个平台的应用
367 8
|
2月前
|
消息中间件 网络协议 安全
C# 一分钟浅谈:WebSocket 协议应用
【10月更文挑战第6天】在过去的一年中,我参与了一个基于 WebSocket 的实时通信系统项目,该项目不仅提升了工作效率,还改善了用户体验。本文将分享在 C# 中应用 WebSocket 协议的经验和心得,包括基础概念、C# 实现示例、常见问题及解决方案等内容,希望能为广大开发者提供参考。
160 0
|
2月前
|
API C# Windows
【C#】在winform中如何实现嵌入第三方软件窗体
【C#】在winform中如何实现嵌入第三方软件窗体
138 0
|
2月前
|
Web App开发 网络协议 API
基于C#编写一个远程桌面应用
基于C#编写一个远程桌面应用
68 0
|
2月前
|
Java 程序员 C#
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
15 0