[译]具有SSL的有效TCP客户端和服务器
By robot-v1.0
本文链接 https://www.kyfws.com/applications/a-working-tcp-client-and-server-with-ssl-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 34 分钟阅读 - 16871 个词 阅读量 0[译]具有SSL的有效TCP客户端和服务器
原文地址:https://www.codeproject.com/Articles/1000189/A-Working-TCP-Client-and-Server-With-SSL
原文作者:David Maw
译文由本站 robot-v1.0 翻译
前言
A working example of a Windows client and server using SSL over TCP.
使用SSL over TCP的Windows客户端和服务器的工作示例.
- 下载StreamSSL-2.1.1.zip-85.2 KB(Download StreamSSL-2.1.1.zip - 85.2 KB)
- 下载StreamSSL-2.1.1-exe.zip-79.1 KB(Download StreamSSL-2.1.1-exe.zip - 79.1 KB)
介绍(Introduction)
这是一个项目(从技术上讲是五个项目),目的是演示如何使用Microsoft的SSL实现(称为(This is a project (five, technically) to demonstrate how to use the Microsoft implementation of SSL (called) SCHANNEL
).这是使用SSL和可以连接到它的客户端的多线程服务器的工作示例.还有一个非常简单的示例客户端,它连接到商品Web服务器,只是为了显示最简单的用法-我在这里不再赘述,但是如果您愿意,可以提供示例.(). This is a working example of a multithreaded server using SSL and a client which can connect to it. There’s also a very simple example client which connects to a commodity web server, just to show the simplest possible usage - I won’t discuss it any further here but the sample is available if you want it.)
对于熟悉SSL的用户,请注意,此示例在服务器和客户端(可选)上均支持SNI和通配符SAN证书,并提供灵活的证书选择和接受方式.如果您不熟悉SSL,那么此示例将提供一些旨在易于在简单或复杂的客户端和服务器上重用的代码.(For the SSL savvy, be aware that this sample supports SNI and wildcard SAN certificates on both the server and, optionally, the client, as well as providing flexible certificate selection and acceptance. If you’re unfamiliar with the world of SSL, this sample provides some code that’s intended to be easy to reuse for a simple, or complex, client and server.)
背景(Background)
SSL是一种可以在两个端点之间安全可靠地移动数据的方法.使用它,您可以验证连接的另一端是它声称的身份,和/或在连接到两个端点之间的数据加密. TLS(SSL的后继产品)实际上是这里使用的,但是在本文档中,我使用了更为熟悉的术语SSL.(SSL is a means by which data can be moved between two endpoints securely and reliably. Using it, you can verify the other end of a connection is who it claims to be, and/or encrypt data as it passes between the two endpoints. TLS (the successor to SSL) is what is actually used here, but I use the more familiar term SSL throughout this document.)
SSL依赖于公共密钥基础结构,通常这些密钥位于证书存储区中.在Microsoft世界中,通常的存储是注册表的某些加密部分(Java和OpenSSL的做法不同).整个机器都有一套命名存储,每个用户都有一套存储,都可以使用MMC管理单元查看.(SSL relies on a public key infrastructure, and generally those keys live in a certificate store. In the Microsoft world, the usual store is some encrypted portions of the registry (Java and OpenSSL do it differently). There’s a set of named stores for the whole machine and a set for each user, all viewable using an MMC snap-in.)
通常,服务器从称为"个人"(有时在代码中称为"我的")的机器存储中获取其证书(包含公钥并指向私有密钥),而客户端从名为"个人"的每用户存储中获取证书. “个人”(同样在代码中为"我的").客户端通常根本不使用证书,因为客户端证书是可选的,除非服务器说它需要一个证书.此示例服务器默认情况下确实需要一个,但是示例客户端可以根据需要创建一个合适的服务器.服务器证书标识系统,客户端证书(如果有)通常标识特定用户.这是最常见的设置,但最低要求仅是服务器提供证书.有关更多详细信息,请参见(Generally, servers get their certificate (which contains the public key and points to a private one) from the machine store called “personal” (oddly, it’s called “my” in code) and clients get their certificate from the per-user store called “personal” (likewise “my” in code). Clients generally do not use certificates at all, since client certificates are optional unless the server says it requires one; this sample server does require one by default, but the sample client can create a suitable one if necessary. The server certificate identifies the system, and the client certificate (if there is one) usually identifies a particular user. That is the most common setup, but the minimum requirement is merely that the server supply a certificate. For more details, see) 创建数字证书(Creating Digital Certificates) .(.)
客户概况(Client Overview)
客户端尝试使用命令行参数(名称默认为本地主机的DNS名称)在您指定的主机上打开TCP套接字,以端口号41000打开.打开该套接字后,客户端将启动SSL握手(初始数据交换,以商定用于通信的选项)并验证服务器提供的证书.服务器请求一个客户端证书,因此客户端将尽可能选择一个可能由服务器信任的证书,否则,它将从用户个人商店中选择一个证书(任意选择),如果找不到,则创建一个证书.客户端是一个控制台应用程序,因此它在控制台上显示进度.如果您在调试器中运行调试版本,则输出窗口中还会有很多详细信息.(The client tries to open a TCP socket to port number 41000 on a host you specify using a command line parameter (the name defaults to the DNS name of the local host). Once that socket is open, the client initiates an SSL handshake (an initial exchange of data to agree on the options used for communication) and validates the certificate the server provides. The server requests a client certificate so the client selects one likely to be trusted by the server if possible, otherwise it selects one (somewhat arbitrarily) from the user personal store and if it cannot find one, it creates one. The client is a console application, so it displays progress on the console. If you run a debug version in the debugger, there’s also a lot of detailed information in the output window.)
服务器概述(Server Overview)
服务器在端口号41000上等待传入连接,当服务器到达端口时,它将用于连接的套接字移交给新启动的线程进行处理(这是服务器的一种常见模式,对于每个新套接字都需要做大量工作).与客户端一样,它是一个控制台应用程序,因此它在控制台上显示进度.如果您运行调试版本并附加调试器,也会有很多详细的输出.(The server waits on an incoming connection on port number 41000, and when one arrives, it hands the socket for the connection to a newly initiated thread for processing (this is a common pattern for servers which do significant work for each new socket). Like the client, it is a console application, so it displays progress on the console. If you run a debug build and attach a debugger, there’s also a lot of detailed output.)
线程启动后,它将等待套接字另一端的客户端启动SSL握手(或直到其最终超时).作为握手的一部分,客户端将使用SNI告诉服务器它正尝试连接到哪个服务器名称,并使用该信息进行武装,每个线程将在计算机存储中查找适当命名的证书(后续的连接请求相同的名称)主机名使用相同的证书).所选证书还必须满足其他要求,例如具有私钥并将其标记为可用于服务器标识.服务器在将连接标记为成功之前从客户端请求证书并进行验证.(Once a thread is initiated, it waits for the client at the other end of the socket to initiate an SSL handshake (or until it eventually times out). As part of the handshake, the client will use SNI to tell the server what server name it is trying to connect to, and armed with that information, each thread will look for an appropriately named certificate in the machine store (subsequent connections requesting the same host name use the same certificate). The chosen certificate must meet other requirements too, such as having a private key and being marked as usable for server identification. The server requests a certificate from the client and validates that before marking the connection as successful.)
服务器末尾有一些代码可以自动启动几个客户端实例,其中一个连接到服务器名称" localhost",另一个仅允许默认为本地主机名.这使测试更加容易,并允许您查看正在选择或创建的证书.(The server has a bit of code at the end to automatically initiate a couple of client instances, one connecting to the server name “localhost” the other is just allowed to default to the local host name. This makes testing a bit easier and allows you to see certificates being selected or created.)
搭建环境(Build Environment)
大多数代码在Visual Studio 2010\2012\2013和2015下编译,但是当我添加SAN和通配符证书匹配时,我使用了现代代码,至少需要VS2013,并升级了项目以使用VS 2015(版本140)工具链,2018年9月版使用VS 2017(版本141工具链),但可以在VS 2015中进行较小的更改进行编译.它是32位Unicode版本,在该版本中没有ANSI替代方法,尽管传输的样本数据是字节流.我希望它可以在Windows Vista以外的任何Windows版本上运行. 2019年7月更新包括可选的64位版本,以及8月(2.1.0版)更新升级到VS2019和142版工具链(尽管仍可与2017年和141版一起使用).(Most of the code compiles under Visual Studio 2010, 2012, 2013 and 2015, but when I added the SAN and wildcard certificate matching, I used modern code, requiring at least VS2013, and upgraded the project to use the VS 2015 (version 140) toolchain, the September 2018 version uses VS 2017 (the version 141 toolchain) but could be compiled with VS 2015 with minor changes. It is a 32 bit Unicode build, in that release there is no ANSI alternative although the sample data transferred is a byte stream. I’d expect it to run on any Windows version beyond Windows Vista. The July 2019 updates include an optional 64 bit build and the August (version 2.1.0) update upgrades to VS2019 and the version 142 toolchain (though it will still work with 2017 and 141).)
在2020年1月,有人需要将SSL集成到一些现有的ANSI(又称为多字节)代码中,因此我创建了ANSI构建,并取消了使用MFC来使代码在2.1.1版中更广泛地兼容. SSLClient和SSLServer代码始终为Unicode,但是如有必要,可以将生成的库和头文件与多字节调用方一起使用. Unicode接口不变.(In January 2020 someone needed to integrate SSL into some existing ANSI (aka multibyte) code, so I created an ANSI build and eliminated the use of MFC to make the code more broadly compatible in version 2.1.1. The SSLClient and SSLServer code is always Unicode but the resulting library and header files can be used with multibyte callers if necessary. The Unicode interfaces are unchanged.)
源代码使用一些ATL实用程序功能和类,但客户端和服务器都不会整体使用ATL框架.(The source uses some ATL utility functions and classes but neither the client nor the server use the ATL framework as a whole.)
创建数字证书(Creating Digital Certificates)
在生产环境中,您将从可信任的机构(“证书机构"或CA)获得证书. CA将负责确保您被授权请求您要求的证书.例如,如果您正在请求SSL的服务器证书,则CA将确保您是服务器所在域的所有者.此类证书通常会通过其完全限定的域名(例如hostname.companyname)来标识服务器. com.例如,如果一个随机的人在” microsoft.com"域中请求证书,则负责的CA不应颁发证书.尽管偶尔会发生错误,但这种错误很少发生,您通常可以信任合法CA颁发的证书.(In production environments, you would get a certificate from a trusted authority (a “Certificate Authority” or CA). The CA would be responsible for ensuring that you were authorized to request the certificate you asked for. For example, if you were requesting a server certificate for SSL, the CA would ensure you were the owner of the domain the server was in. Such a certificate would usually identify the server by its fully qualified domain name, something like hostname.companyname.com. If a random person asks for a certificate in the “microsoft.com” domain, for example, a responsible CA should not issue one. Although mistakes do occasionally happen, they are rare and you can generally trust certificates issued by a legitimate CA.)
每个证书都有许多属性,但是对于SSL最重要的属性是"主题名称"(证书所针对的实体,例如特定服务器或特定用户),“密钥用法"和"增强密钥用法"打算如何使用证书(例如,“服务器身份验证”),以及是否具有证书的私钥或仅具有公钥.在此样本中,其他有效期或签发者名称之类的属性无关紧要,但在生产中就无关紧要了(因为这样您就可以关心证书的颁发者以及当前是否有效).客户端证书通常标识特定的用户,因此它们通常使用电子邮件地址作为主题名称.(Each certificate has many attributes, but the ones of most importance for SSL are the “Subject Name” (what entity the certificate is for, like a specific server or a particular user), the “Key Usage” and “Enhanced Key Usage” describing how the certificate is intended to be used (“Server Authentication” for example), and whether you have a private key for the certificate or just a public one. Other attributes like the validity period or issuer name don’t matter much in this sample, but would in production (because then you’d care about who issued the certificate and whether it was currently valid). Client certificates usually identify a particular user, so they often have an e-mail address as a subject name.)
较新的证书标准允许在每个证书上使用主题备用名称(SAN),这允许将同一证书用于名称列表.允许使用多个名称的另一种方法是"通配符"证书,该证书允许同一域中的多个服务器受到同一证书的保护.对于此示例,仅支持格式为*.<域名>的通配符.(*Newer certificate standards allow for a Subject Alternative Name (SAN) on each certificate, which allows the same certificate to be used for a list of names. Another way of allowing for multiple names is “wildcard” certificates, which allow multiple servers in the same domain to be protected by the same certificate. For this example only wildcards of the form *. are supported.*)
出于测试目的,您不需要CA颁发的证书;您可以使用自己生成的证书(所谓的"自签名证书”).如果找不到合适的服务器和/或客户端证书,此示例将使用它提供的主题名称(基本上是主机名和用户名)为您提供证书,执行此操作的代码在(For test purposes, you don’t need CA issued certificates; you can use ones you generate yourself (so-called “self signed certificates”). If it doesn’t find suitable server and/or client certificates, this sample will make them for you using subject names it provides (the host name and user name basically), the code to do this is in) CreateCertificate
在(in)CertHelper.cpp(CertHelper.cpp).如果您希望提供自己的证书,最简单的方法是要求IIS为您创建一个证书或使用以下命令在PowerShell中创建一个证书:(. If you prefer to provide your own certificate, the easiest way is to ask IIS to create one for you or create one in PowerShell using the) New-SelfSignedCertificate
命令(请参阅(command (see) https://blog.davidchristiansen.com/2016/09/howto-create-self-signed-certificates-with-powershell/(https://blog.davidchristiansen.com/2016/09/howto-create-self-signed-certificates-with-powershell/) 对于高级示例),Leon Finker的文章(见下文)描述了我没有尝试过的其他替代方法.对于客户端,此示例代码将使用可以找到的具有私钥的任何证书,或者必须使用一个证书.(for advanced examples) Leon Finker’s article (see below) describes other alternatives that I have not tried. For the client end, this sample code will use any certificate with a private key it can find, or make one if it has to.)
客户资料(Client Details)
客户端应用程序称为(The client application is called) StreamClient
它使用的基本流程非常简单:(and the basic flow it uses is fairly simple:)
- 声明一个TCP连接(一个套接字)(declare a TCP connection (a socket))
- 连接它(connect it)
- 通过TCP连接协商SSL(negotiate SSL over the TCP connection)
- 发送和接收一些测试消息(send and receive a couple of test messages)
- 停止使用SSL,但保持TCP连接打开(stop using SSL, but keep the TCP connection open)
- 发送一些纯文本测试消息,它们之间有延迟(send a couple of plaintext test messages with a delay between them)
- 关闭SSL连接(shut down the SSL connection)
- 发送未加密的消息(Send an unencrypted message)
- 关闭插座(shut down the socket) 如果您需要的只是一个简单的客户端,并且您不太担心SSL或TCP的详细信息,请阅读下面的"主程序",然后跳至(If all you need is a straightforward client and you are not too concerned with the details of SSL or TCP, just read “Main Program” below, then skip forward to) 服务器详细信息(Server Details) 如果您也需要服务器.(if you need a server too.)
主程序(Main Program)
这是使连接正常工作所需的全部.在里面(This is all you really need to make a connection work. It’s in)**StreamClient.cpp(StreamClient.cpp)**并声明一些对象,打开一个TCP连接,并将其传递给实现的对象(and just declares a few objects, opens a TCP connection, and passes it to an object that implements) ISocketStream
这样就可以完成连接.代码的本质如下所示:(so that the connection can be completed. The essence of the code looks like this:)
CActiveSock * pActiveSock = new CActiveSock(ShutDownEvent);
CSSLClient * pSSLClient = nullptr;
pActiveSock->SetRecvTimeoutSeconds(30);
pActiveSock->SetSendTimeoutSeconds(60);
bool b = pActiveSock->Connect(HostName.c_str(), Port);
if (b)
{
char Msg[100];
pSSLClient = new CSSLClient(pActiveSock);
b = SUCCEEDED(pSSLClient->Initialize(HostName));
if (b)
{
cout << "Connected, cert name matches=" << pSSLClient->getServerCertNameMatches()
<< ", cert is trusted=" << pSSLClient->getServerCertTrusted() << endl;
if (pSSLClient->Send("Hello from client", 17) != 17)
cout << "Wrong number of characters sent" << endl;
int len = 0;
while (0 < (len = pSSLClient->Recv(Msg, sizeof(Msg))))
cout << "Received " << CStringA(Msg, len) << endl;
}
else
cout << "SSL client initialize failed" << endl;
::SetEvent(ShutDownEvent);
pSSLClient->Close();
}
注意(Notice the) getServerCertNameMatches
和(and) getServerCertTrusted
方法;他们告诉客户证书多少信任.(methods; they tell the client how much trust to put in the certificate.)
如果编译并运行客户端,它将连接到服务器并在控制台窗口中显示如下内容:(If you compile up the client and run it, it will connect to the server and show something like this in a console window:)
Connecting to localhost:41000
Socket connected to server, initializing SSL
Optional client certificate requested (without issuer list), no certificate found.
Client certificate required, issuer list is empty, selected name: david.maw@unisys.com
A trusted server certificate called "localhost" was returned with a name match
Connected, cert name matches=1, cert is trusted=1
Sending greeting
Sending second greeting
Listening for message from server
Received 'Hello from server'
Received 'Goodbye from server'
Shutting down SSL
Sending first unencrypted data message
Sleeping before sending second unencrypted data message
Sending second unencrypted data message
Sleeping before sending termination to give the last message time to arrive
Press any key to pause, Q to exit immediately
调试版本也可以更改为显示接收到的服务器证书的详细信息.它看起来像这样:(A debug build can also be changed to display the details of the received server certificate. It will look something like this:)
要使其显示证书,请将示例客户端更改为g_ShowCertInfo,它将在以下位置显示信息:(To make it display the certificate, change the sample client to set g_ShowCertInfo and it will show the info in:)
if (g_ShowCertInfo && debug && pCertContext)
ShowCertInfo(pCertContext, L"Client Received Server Certificate");
如果您只需要一个简单的SSL客户端,则只需更改此代码即可完成您想要的操作,并且不再赘述.如果您对更多控制或其所有功能感兴趣,请继续阅读…(If all you need is a simple SSL client, just alter this code so it does what you want and go no further. If you are interested in more control or how it all works, read on…)
控制证书(Controlling Certificates)
您可以通过以下几项操作来更好地控制连接,您可以提供(There are a couple of things you can do to give you more control over the connection, you can provide a) CertAcceptable
函数评估服务器提供的证书,如果您不喜欢它则将其拒绝(这将关闭连接).您还可以提供(function to evaluate the certificate provided by the server and reject it if you do not like it (which closes the connection). You can also provide a) SelectClientCertificate
功能可让您控制将哪些客户端证书(如果有)发送到服务器.使用这些代码的代码如下所示:(function to allow you to control what client certificate (if any) is sent to the server. The code to use these looks like this:)
pSSLClient->ServerCertAcceptable = CertAcceptable;
pSSLClient->SelectClientCertificate = SelectClientCertificate;
这两个示例均在示例源中,但您无需使用它们中的任何一个,如果这样做,则可以分配lambda表达式,也可以像示例中那样使用适当的参数定义函数,然后仅分配它们.(Examples of both of these are in the sample source, but you need not use either of them, if you do, you can assign lambda expressions, or, as the sample does, define functions with appropriate parameters, and just assign those.)
TCP客户端(CActiveSock)(TCP Client (CActiveSock))
TCP客户端连接被抽象为一个名为(The TCP client connection is abstracted into a class called) CActiveSock
定义于(defined in)**ActiveSock.cpp(ActiveSock.cpp)**并在(and declared in)ActiveSock.h(ActiveSock.h).此类实现了一些简单的功能((. This class implements a few simple functions () Connect
,(,) Disconnect
,(,) Send
和(and) Receive
(例如),它抽象出了与WinSock接口的细节,因此主调用者无需处理它们.要使用它,您需要声明一个(, for example) that abstract away the details of interfacing with WinSock so that the main caller need not deal with them. To use it, you declare a) CActiveSock
对象,可以选择在其上设置一些超时,而不是要求该对象连接到特定端点(主机名和端口号).(object, optionally set some timeouts on it, than ask it to connect to a particular endpoint (a hostname and port number).)
SSL客户端(CSSLClient)(SSL Client (CSSLClient))
实现SSL连接的代码位于(The code to implement the SSL connection is within the) CSSLClient
具有一些辅助功能的对象(object with some helper functions in)SSLHelper.cpp(SSLHelper.cpp).的(. The) CSSLClient
构造函数需要连接(constructor needs a connected) CActiveSock
为它提供一个交流渠道.构造完成后,您将其称为(to provide it with a channel over which to communicate. Once constructed, you call its) Initialize
方法,一旦成功完成,您将拥有一个开放渠道,可以使用(method, and once that completes successfully, you have an open channel over which you can communicate using) Send
和(and) Recv
. TCP可能不会发送您请求的整个消息(例如,它可能会耗尽缓冲区空间)或传递您在单个消息中发送的字节(消息可以拆分或合并),但是(. TCP may not send the whole message you request (it may run out of buffer space, for example) or deliver the bytes you sent in a single message (messages can be split or joined), but) Send
和(and) Recv
如有必要,请使用多个网络呼叫来解决此问题.实际上,SSL不显示此属性-SSL在单个消息中发送您所请求的内容,并在单个消息中完全传递所发送的内容.(take care of that using multiple network calls if necessary. In practice, SSL does not exhibit this property – it sends what you request in a single message and delivers exactly what’s sent in a single message.)
验证服务器证书(Validating the Server Certificate)
作为SSL协商的一部分,客户端使用称为Server Name Indication或SNI的机制(它是TLS协议的功能)(从技术上讲,它提供了一个列表,但仅提供了一个列表)来告知服务器它认为要连接的服务器名称.列表中的任何一个).一旦服务器知道客户端要查找的名称(并且可能没有名称,在这种情况下将使用本地主机名),它就可以提供具有匹配主题名称的证书.此示例代码默认情况下会执行此操作,但是如果找不到匹配的证书,则将使用名称错误的自签名证书.每次建立连接时,代码都会在哈希表中查找以查看其是否已使用该服务器名称的证书(请参见(As part of the SSL negotiation, the client tells the server what server name it thinks it is connecting to using a mechanism called Server Name Indication or SNI, which is a feature of the TLS protocol (technically, it provides a list, but there’s only ever one name in the list). Once the server knows what name the client is looking for (and there might not be one, in which case the local host name is used), it can provide a certificate with a matching Subject Name. This sample code does that by default, but if no matching certificate is found, it will use a self signed one with the wrong name if there is one. Each time a connection is made, the code looks in a hash table to see if it already has used a certificate for that server name (see) GetCredHandleFor
)()),如果有一个证书,则相同的证书上下文将重新用于连接.如果以前没有看到服务器名称,则从证书存储中选择一个证书,并将其添加到哈希表中,以便下次使用.(, if it has one, the same certificate context will be reused for the connection. If the server name has not been seen before then a certificate is selected from the certificate store and also added to the hash table so it can be used next time.)
客户端必须对服务器提供的证书进行重要评估,包括"证书是否有效"(例如,未过期),“证书是否由受信任的CA颁发"以及"主题名称是否与期望的名称匹配”. SSL握手完成后,每个(The important assessments the client must make about the certificate the server provides are “is it valid” (not expired, for example), “was it issued by a trusted CA”, and “does the subject name match what’s expected”. Once the SSL handshake completes, each) CSSLClient
对象实现了调用者可以用来回答这些问题的两种方法:(object implements two methods the caller can use to answer these questions:) getServerCertTrusted
和(and) getServerCertNameMatches
.通常,除非两个都返回,否则您将不希望使用连接(. Generally, you won’t want to use the connection unless both return) true
,因为否则可能会损害通信通道(即在到达所需的端点之前先进行读取,更改或什至重定向).(, because otherwise the communication channel might be compromised (that is, read, changed or even redirected before reaching the desired endpoint).)
或者,您可以提供自己的(As an alternative, you can provide your own) CertAcceptable
函数评估服务器提供的证书,如果您不喜欢它则将其拒绝(这将关闭连接).如果确实决定提供该功能,则会为它提供一个指向服务器句柄的指针,以及两个布尔值,以指示证书是否受信任并且其主题名称与客户端所请求的主题名称匹配.(function to evaluate the certificate provided by the server and reject it if you do not like it (which closes the connection). If you do decide to provide that function, it gets given a pointer to a server handle, and two boolean values to indicate if the certificate is trusted and has a subject name matching the one that the client requested.)
选择客户证书(Selecting a Client Certificate)
作为SSL协商的一部分,服务器会向客户端发送可接受的证书颁发者列表,除非设置了特定的注册表项(请参阅下文).这允许客户端对可能的证书列表进行排序,以从服务器可以接受的发行者中找到证书,然后将该证书提供给服务器.如果找不到可接受的证书,则该示例将使用它可以找到的任何证书,或者,如果找不到该证书,则甚至创建一个证书.可以轻松更改服务器代码以显示它从客户端接收到的证书(如果获得的话),以使其显示证书,然后进行更改(As part of the SSL negotiation, the server sends a list of acceptable certificate issuers to the client unless a particular registry key is set (see below) . This allows the client to sort through a list of possible certificates to find one from an issuer that is acceptable to the server, and then offer that certificate to the server. If no acceptable certificate can be found, the sample uses any certificate it can find, or even creates one if it cannot find one. The server code can easily be changed to display the certificate it received from the client if it gets one, to make it display the certificate, change) ClientCertAcceptable
在示例服务器中插入:(in the sample server to insert:)
ShowCertInfo(pCertContext, _T("Server Received Client Certificate"));
(可选)您可以提供自己的(Optionally, you can provide your own) SelectClientCertificate
客户端中的"证书"功能,以允许您控制将什么客户端证书(如果有)发送到服务器.(function in the client to allow you to control what client certificate (if any) is sent to the server.)
有趣的是,此函数被调用两次,一次在协商的早期,如果需要,可以选择提供客户端证书-如果服务器批准,则完成了.如果需要客户端证书而您不提供,则连接将失败,因此可能会有第二次呼叫要求提供证书-此呼叫可能附带可接受的证书颁发者列表.(Interestingly, this function is called twice, once early in the negotiation to optionally provide a client certificate if you want to - if the server approves of this, you’re done. If a client certificate is required and you don’t provide one, the connection will fail, so there may be a second call requiring a certificate to be provided - this call may come with a list of acceptable certificate issuers.)
谨防(BEWARE)-如果注册表项,服务器将不会发送"可接受的颁发者"列表(- The server will NOT send an “acceptable issuer” list if the registry key)**HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ SecurityProviders \ SCHANNEL(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL)**有个(has a) DWORD
值称为(value called) SendTrustedIssuerList
设置(set to) 0
.(.)
服务器详细信息(Server Details)
服务器要比客户端复杂得多-SSL部分几乎是相同的,但是允许它接受TCP上的多个同时连接要复杂得多.(The server is considerably more complex than the client - the SSL part is mostly the same, but there’s a lot more complexity in allowing it to accept multiple simultaneous connections over TCP.)
主程序(Main Program)
就像客户端一样,这是使连接正常工作所需的全部内容.在里面(Just as for the client, this is all you really need to make a connection work; it’s in)**StreamServer.cpp(StreamServer.cpp)**并声明一个(and just declares a) CListener
然后告诉它开始侦听特定端口并定义建立连接后将要发生的情况.您可以在lambda表达式中进行定义,然后可以使用(then tells it to start listening on a particular port and define what’s going to happen once a connection is made. You define that in a lambda expression, and you can use the) ISocketStream
为通过SSL连接发送或接收消息而传递给它的接口指针.简化的示例代码如下所示(完整的示例稍微复杂一点,因为它可以处理正在使用的套接字上的SSL关闭):(interface pointer that’s passed to it in order to send or receive messages across the SSL connection. Simplified sample code looks as follows (the full sample is a bit more complex because it handles turning off SSL on an in-use socket) :)
Listener->Initialize(Port);
Listener->BeginListening([](ISocketStream * const StreamSock){
// This is the code to be executed each time a socket is opened
StreamSock->Send("Hello from server", 17);
int len = StreamSock->Recv(MsgText, sizeof(MsgText) - 1);
if (len > 0)
StreamSock->Send("Goodbye from server", 19);
});
Listener->EndListening();
如果编译并运行服务器,它将等待客户端的连接(它将运行几个来简化测试)并显示这种控制台输出(此示例显示两个客户端连续连接):(If you compile up the server and run it, it will wait for connections from clients (it will run a couple to simplify testing) and show this sort of console output (this example shows two clients connecting in succession):)
WARNING: The server is not running as an administrator.
Starting to listen on port 41000, will find certificate for first connection.
Listening for client connections.
Initiating a client instance for testing.
Waiting on StreamClient to localhost
Server certificate requested for localhost, found "localhost"
An untrusted client certificate was returned for "david.maw@unisys.com"
A connection has been made, worker started, sending 'Hello from server'
Received Hello from client
Sending 'Goodbye from server' and listening for client messages
Waited 1 seconds
Received 'Hello again from client'
Recv returned notification that SSL shut down
Received plaintext 'First block of unencrypted data from client'
Waited 4 seconds
Initial receive timed out, retrying
Waited 2 seconds
Received plaintext 'Second block of unencrypted data from client'
Waited 3 seconds
socket shutting down
Exiting worker
Listening for client connections, press enter key to terminate.
Client completed.
Initiating a client instance for testing.
Additional test clients initiated, press enter key to terminate server.
Server certificate requested for usmv-dgm-home, found "SSLStream Testing"
An untrusted client certificate was returned for "david.maw@unisys.com"
A connection has been made, worker started, sending 'Hello from server'
Waited 63 seconds
Received Hello from client
Sending 'Goodbye from server' and listening for client messages
Waited 1 seconds
Received 'Hello again from client'
Recv returned notification that SSL shut down
Received plaintext 'First block of unencrypted data from client'
Waited 4 seconds
Initial receive timed out, retrying
Waited 2 seconds
Received plaintext 'Second block of unencrypted data from client'
Waited 3 seconds
socket shutting down
Exiting worker
Listening for client connections, press enter key to terminate.
可以轻松修改调试版本,以显示UI来显示接收到的客户端证书的详细信息(如果有),使其显示证书,请更改示例服务器(A debug build can be easily modified to show UI to display the details of the received client certificate, if there is one, to make it display the certificate, change the sample server) ClientCertAcceptable
功能(见下文)包括:(function (see below) to include:)
if (pCertContext)
ShowCertInfo(pCertContext, "Client Certificate Returned");
如果您只需要一个相当简单的服务器,只需修改此代码即可完成所需的工作,而忽略下面的详细内容.如果您想施加更多控制权或了解更多信息,请继续阅读…(If all you need is a fairly simple server, just amend this code to do what you need and ignore the gory detail below. If you want to exert more control or know more, read on…)
控制证书(Controlling Certificates)
您可以通过以下几项操作来更好地控制连接,您可以提供(There are a couple of things you can do to give you more control over the connection, you can provide a) ClientCertAcceptable
函数评估客户端提供的证书(如果有),如果您不喜欢它则将其拒绝(这将关闭连接).您还可以提供(function to evaluate the certificate provided by the client (if any) and reject it if you do not like it (which closes the connection). You can also provide a) SelectServerCert
功能可让您控制将什么服务器证书发送到客户端.使用这些代码的代码如下所示:(function to allow you to control what server certificate is sent to the client. The code to use these looks like this:)
Listener->SelectServerCert = SelectServerCert;
Listener->ClientCertAcceptable = ClientCertAcceptable;
这两个示例均在示例源中,但您无需使用它们中的任何一个,如果这样做,则可以分配lambda表达式,也可以像示例中那样使用适当的参数定义函数,然后仅分配它们.(Examples of both of these are in the sample source, but you need not use either of them, if you do, you can assign lambda expressions, or, as the sample does, define functions with appropriate parameters, and just assign those.)
样品(The sample) SelectServerCert
功能利用(function makes use of) CertFindServerByName
为了选择匹配的证书,依次调用(in order to select a matching certificate, that, in its turn, calls) MatchCertHostName
为了检查证书SAN或Subject字段,然后调用(in order to examine the certificate SAN or Subject fields, and then it calls) HostNameMatches
验证证书中的名称与请求的主机名匹配.(to validate that a name from the certificate matches the hostname being requested.)
TCP侦听器(CListen)(TCP Listener (CListen))
通常,在服务器上需要做的第一件事就是弄清楚您将在哪个端口上监听连接,并告诉Winsock开始监听该端口.您可能要侦听哪些协议(通常是通过IPv4和/或IPv6的TCP),以及如何在等待下一个套接字打开时处理阻塞线程的问题,这有点复杂(示例通过运行"侦听"来处理) “在单独的线程上循环).(One of the first things you usually need to do in a server is figure out what port you’ll be listening on for connections, and tell Winsock to start listening on that port. It’s complicated a bit by what protocols you might want to listen on (TCP over IPv4 and/or IPv6 usually) and how you want to handle blocking a thread while waiting for the next socket to open (the sample handles this by running the “listen” loop on a separate thread).)
这一切都在一个(That’s all handled in a) CListen
实现多线程侦听器的类.打开连接时,它可以做一些简单的事情,例如启动一个线程来处理它,但随后需要告知服务器实际的工作. lambda表达式传递到(class which implements a multi threaded listener. It can do some simple things when a connection is opened, such as start up a thread to process it, but then it needs to be told what the server actually does. The lambda expression passed into) BeginListening
提供这些信息的是什么,但通常有一个(is what provides that information, but normally there’s a) CSSLServer
在基本TCP传输之上添加SSL功能的路径中.调用中的Lambda表达式(in the path to add SSL capability on top of the basic TCP transport. The lambda expression in the call on) BeginListening
通过了(gets passed an) ISocketstream
可以用来发送和接收消息的接口以及一个(interface it can use to send and receive messages and a) CSSLServer
可以提供该接口的支持SSL的版本,而不是(can provide an SSL capable version of that interface, as opposed to a) CPassiveSock
只能提供未加密的(which can only provide an unencrypted one.)
TCP服务器(CPassiveSock)(TCP Server (CPassiveSock))
TCP服务器连接由一个名为的类定义(The TCP server connection is defined by a class called) CPassiveSock,
定义于(defined in)**PassiveSock.h(PassiveSock.h)**并在(and declared in)PassiveSock.cpp(PassiveSock.cpp)-此类实现了一些简单的功能((- this class implements a few simple functions () Disconnect
,(,) Send
和(and) Receive
(例如)),抽象出与WinSock接口的细节,以便调用者无需处理它们.注意没有"连接”.这是因为此类仅处理由于客户端建立的连接而传递的已打开的套接字,而该套接字是由客户端介导的(, for example) that abstract away the details of interfacing with WinSock so that the caller need not deal with them. Notice there’s no “connect”; that’s because this class only deals with already-open sockets delivered as a result of a connection made by a client, mediated by a) CListen
目的.(object.)
SSL服务器(CSSLServer)(SSL Server (CSSLServer))
实现SSL连接的代码位于(The code to implement the SSL connection is within the) CSSLServer
目的.它的构造函数需要一个(object. Its constructor needs a) ISocketstream
为它提供一个交流渠道.构造完成后,您将其称为(to provide it with a channel over which to communicate. Once constructed, you call its) Initialize
方法,一旦成功完成,您将拥有一个开放渠道,您可以通过该渠道进行交流.的(method, and once that completes successfully, you have an open channel over which you will communicate. The) CSSLServer
对象是(object is)**SSLServer.cpp(SSLServer.cpp)**并且非常重,因此一些工作被卸载到了(and is fairly heavyweight, so some of the work is offloaded to code in)SSLHelper.cpp(SSLHelper.cpp).(.)
从简单的TCP服务器到具有SSL功能的TCP服务器的过渡由服务器处理.(The transition from a simple TCP server to a TCP server with SSL capability is handled by a) CSSLServer
目的.一种(object. A) CListener
需要(takes the) SOCKET
Winsock交付的对象(object delivered by Winsock) accept
和要求(and requests a) CSSLServer
为此,(for it, the) CSSLServer
创建一个(creates a) CPassiveSock
来自(from the) SOCKET
然后将其附加到(and then attaches that to the) CSSLServer
为了创建服务器端SSL套接字.的(in order to create a server-side SSL socket. The) CSSLServer
提供一个(makes available an) ISocketStream
传递给最初传递给的lambda函数的接口(interface which is passed to the lambda function that was originally passed into) BeginListening
.(.)
的(The) ISocketStream interface
主要存在,因此可以选择不使用SSL并仍然拥有(exists mostly so that it is possible to choose NOT to use SSL and still have) Clistener::Work
使用相同(use the same) interface
(因为无论通道是否加密,服务器都可能不会更改其行为).((because the server likely does not change its behavior whether the channel is encrypted or not).) ISocketStream
还提取了实现的详细信息,以使lambda不会因意外或有意使它们混乱.在该示例中,没有逻辑可以支持完全不使用SSL来运行,但是如果您要这样做,这是一个相当简单的更改(also abstracts away the details of the implementation so the lambda cannot mess with them, either accidentally or on purpose. There’s no logic to support running completely without SSL in the example, but if you wanted to do it, it’s a fairly simple change to) CListener
不使用(to not use) CSSLServer
完全没有(at all.)
那SCHANNEL呢?(What About SCHANNEL?)
Microsoft SCHANNEL SSL实现的实际接口有点不可思议,部分是因为使用同一接口(安全支持提供程序接口-SSPI)来访问可替换的提供程序,该提供程序可能会做很多不同的事情.因此,您要做的第一件事几乎就是(The actual interface to the Microsoft SCHANNEL implementation of SSL is a bit arcane, partly because the same interface (the Security Support Provider Interface - SSPI) is used to reach a replaceable provider which might do many different things. So pretty much the first thing you must do is get a) PSecurityFunctionTable
对象通过调用指向SSPI实现(object pointed at an SSPI implementation by calling) InitSecurityInterface
.完成此操作后,大约有20种方法可用,例如(. Once you do that, around 20 methods become available, such as) EncryptMessage
要么(or) QuerySecurityContext
.(.)
各种SSPI函数的许多参数在SCHANNEL实现中没有意义,必须(Many of the parameters to the various SSPI functions have no meaning in the SCHANNEL implementation and must be) NULL
.(.)
SSPI接口被设计为供C使用,而不需要C ++,这使它使用起来很麻烦.它还提供了对缓冲区使用情况的严格控制,这进一步增加了复杂性.(The SSPI interface is designed to be consumed by C rather than requiring C++, which makes it rather cumbersome to use. It also provides pretty tight control over buffer usage, which furthers adds to the complexity.)
基本的SSL握手(The Basic SSL Handshake)
为了启动SSL会话,第一步是在客户端和服务器之间建立通信通道(大多数情况下为TCP连接),然后在该通道上执行" SSL握手"(Google" SSL握手",有很多很好的解释).握手由客户端向服务器发送消息,服务器回复并提供证书,客户端响应(可能带有其自己的证书),协商密码套件,协商SSL/TLS级别等启动. .两端发送完消息后,便建立了安全连接.从客户端或服务器的角度来看,这种握手的细节是什么(In order to start an SSL session, the first step is to establish a communication channel between the client and the server (a TCP connection in most cases) and then perform an “SSL Handshake” over the channel (Google “SSL Handshake”, there are lots of good explanations). The handshake is initiated by the client sending a message to the server, the server replies and provides a certificate, the client responds (possibly with a certificate of its own), cipher suites are negotiated, SSL/TLS levels are negotiated, and so on. After both ends have sent their messages, a secure connection is made. The details of this handshake either from a client or server perspective are what) SCHANNEL
实施.在此示例中,服务器实现位于(implements. In this example, the server implementation is in) CSSLServer
和客户的在(and the client’s is in) CSSLClient
.(.)
呼叫者提供什么(What the Caller Provides)
SSPI((The SSPI () SCHANNEL
)调用者必须提供一种发送和接收消息的方式,一些内存缓冲区以及一个状态机,以处理单步握手,直到SSPI报告握手已完成或失败为止.您可以在中看到此逻辑() caller has to provide a means to send and receive messages, some memory buffers, and a state machine to handle stepping through the handshake until SSPI reports that the handshake is complete or has failed. You can see this logic in) CSSLServer::SSPINegotiateLoop,
基本上只是不断调用SSPI(which basically just keeps calling the SSPI) AcceptSecurityContext
直到返回的过程(procedure until it returns) SEC_E_OK
.其他返回值表示各种情况,例如需要更多数据,应发送的响应或失败.(. Other return values indicate a variety of things, such as a need for more data, a response that should be sent, or a failure.)
握手完成后,您将拥有一个可用于发送或接收操作的加密连接.(Once the handshake is complete, you have an encrypted connection that can be used for send or receive operations.) Send
最终称为SSPI(ultimately calls SSPI) EncryptMessage
在一个类似的循环(in a similar loop to the one in) SSPINegotiate
和(, and) Receive
致电SSPI(calls SSPI) DecryptMessage
以同样的方式.的(in the same way. The) 文章(article) 下面提到的Leon Finker撰写的这篇文章对此进行了很好的概述,如果您对更多详细信息感兴趣,可以参考一些进一步的参考.(by Leon Finker referred to below has a nice overview of this, and some further references if you’re interested in more details.)
控制SSL协议版本(Controlling SSL Protocol Versions)
多年来,SSL经历了许多协议版本的演变:SSL 1.0\2.0\3.0,然后是TLS 1.0\1.1和1.2(在撰写本文时,这是最新的通用协议).如果可以避免,请不要使用TLS 1.0之前的任何版本. SSL 3.0和更早版本已通过多种方式受到损害. SSL握手应该协商客户端和服务器都可以处理的机制(例如最新协议级别或加密算法).本示例仅使用TLS 1.2,建议使用TLS 1.2以获得最大的安全性,但是如果要更改此设置,请查找设置为(SSL has evolved through many protocol versions over the years: SSL 1.0, 2.0, 3.0, then TLS 1.0, 1.1 and 1.2 (which is the latest in general use at the time of writing). Do not use anything earlier than TLS 1.0 if you can possibly avoid it. SSL 3.0 and earlier have been compromised in a variety of ways. The SSL handshake is supposed to negotiate mechanisms (like the latest protocol level, or encryption algorithms) that both client and server can handle. This sample uses only TLS 1.2, which is what is recommended for maximum security, but if you want to change that, look for a setting of) SchannelCred.grbitEnabledProtocols
(这将是类似((it will be something like) SP_PROT_TLS1_2_CLIENT
)并将其更改为您需要的任何内容.() and change it to whatever you need.)
实施注意事项(Some Notes on Implementation)
最初,客户端和服务器之间复制了许多文件,这些文件往往有所不同,但只是略有不同,因此在2018年9月,我忍无可忍,将所有共享文件移到了树的服务器部分,并将客户端更新为指出他们,在2019年7月,他们再次搬到一个共同的地方(Originally, a number of files were duplicated between the client and server, these tended to differ, but only slightly, so in September of 2018 I bit the bullet and moved all the shared files into the server part of the tree and updated the client to point to them, in July 2019 they were moved again to a common) ..\Common\
夹.唯一需要注意的事实是#include首先与源文件位于同一目录中,因此,例如,当StreamClient.cpp使用CertRAII.cpp时,它将选择.. \ Common \ CertRAII.cpp,并且该引用pch .h-将是.. \ SteamClient \ pch.h.(folder. The only thing to look out for is the fact that #include first looks in the same directory as the source file, so when StreamClient.cpp uses CertRAII.cpp for example it picks up ..\Common\CertRAII.cpp and THAT references pch.h - which will be ..\SteamClient\pch.h.)
该服务器曾经使用MFC,但从2020年(2.1.1版本)开始,它不再使用.(The server used to use MFC but as of 2020 (the 2.1.1 release) it does not.)
此实现演示了如何停止使用SSL并仍将基础连接用于未加密的消息.为了说明这一点,它将未加密的消息从客户端发送到服务器.我从未见过像这样的实际实现(在活动连接上关闭SSL,将其打开很常见,而很少关闭它).因此,此代码不支持将未加密的消息从服务器发送到客户端-如果您有一个有效的用例,则相对容易实现,但我没有,所以请问.(This implementation demonstrates how to stop using SSL and still use the underlying connection for unencrypted messages. To demonstrate this it sends an unencrypted message from the client to the server. I’ve never seen a real implementation that works like this (turning off SSL on an active connection, turning it ON is common, it’s turning it off that is rare). So this code does not support sending unencrypted messages from the server to the client - if you have a valid use case for this it’s relatively easy to implement, I just haven’t, so ask.)
超时处理似乎过于复杂.它的基本设置是这样,无论TCP可能选择将消息传递到的段数为多少,单个接收都在同一时间超时.请注意,如果接收超时,调用方可以选择重试(该示例显示了此用法) ).(The timeout handling might seem overly complex. It’s basically set up so a single receive times out at the same time regardless of the number of segments TCP might choose to deliver the message in. Note also that callers can elect to try again if a receive times out (the sample shows this in use).)
开放式问题(Open Issues)
- 缓冲区大小都是固定的(请参阅(Buffer sizes are all fixed (see)
MaxMsgSize
当前设置为16000-当前(which is currently set to 16000 – the current)SCHANNEL
限制为(limit is)16384
);如果它们是可变的,那会更好.(); it would be better if they were variable.) - 利用现代C ++中的Async支持将线程逻辑移出该代码并利用系统提供的服务可能会有所帮助.(It might be helpful to make use of the Async support in modern C++ to move the threading logic out of this code and take advantage of services provided by the system instead.)
致谢(Acknowledgements)
此代码的SSL部分受Leon Finker于2003年在CodeProject上发表的一篇文章的启发((The SSL portion of this code was inspired by an article by Leon Finker published on CodeProject in 2003 (it is) 这里(here) ),对于那些想进一步了解SSL和SCHANNEL的人,我建议以此为起点.它也受到旧的Microsoft SDK示例(我再也找不到)的启发,该示例显示了如何调用SCHANNEL.() which I recommend as a starting point for someone who wants to know more about SSL and SCHANNEL. It was also inspired by an old Microsoft SDK sample (which I can no longer find) showing how to call SCHANNEL.)
创建证书的代码基于Alejandro Campos Magencio博客中的示例"如何使用CryptoAPI创建自签名证书"代码,可以找到它.(The code to create a certificate is based on the sample “How to create a self-signed certificate with CryptoAPI” code in the blog of Alejandro Campos Magencio, it can be found) 这里(here) .(.)
在2019年7月和8月,Thomas Hasse提供了一些修复程序,64位版本和一些清理代码,以及重构某些代码的动力.(In July and August 2019 Thomas Hasse provided a number of fixes, a 64 bit build, and some cleanup code, as well as the impetus to refactor some of the code.)
历史(History)
该代码在GitHub上维护,网址为(This code is maintained in GitHub at) https://github.com/david-maw/StreamSSL(https://github.com/david-maw/StreamSSL) 通常,一旦进行了重大更改或有一些积累,我通常会先在GitHub上更新代码,然后在本文中进行更新.因此,如果您需要绝对最新的源代码,请查看GitHub,网址为(I usually update the code on GitHub first, and then this article, once a significant change is made or a few accumulate. So, if you want the absolute latest source, look on GitHub at) https://github.com/david-maw/StreamSSL/releases(https://github.com/david-maw/StreamSSL/releases) .这是主要变更的简短时间表:(. Here’s a brief timeline of major changes:)
-
2015年6月发表原始文章(June 2015 Original article published)
-
2015年6月27日-来源已添加到(June 27. 2015 - Source added to) 的GitHub(GitHub)
-
2015年7月6日-为客户端和服务器代码提供了对所使用证书的更多控制以及拒绝证书的能力(July 6, 2015 - Provided the client and server code with more control over the certificates being used as well as the ability to reject them)
-
2016年1月-添加代码以创建客户端证书(如果没有可用的证书),默认情况下使用主机名而非" localhost"(January 2016 - Added code to create a client certificate if none is available and use the host name rather than “localhost” by default)
-
2016年2月-更新了源代码,使其可以与VS2015工具链以及VS2010一起编译(February 2016 - Updated the source so it compiles with the VS2015 toolchain as well as VS2010)
-
2016年4月9日-更新了代码以处理SAN和通配符证书,要求使用VS 2015 Unicode构建(April 9, 2016 - Updated the code to handle SAN and Wildcard certificates, require a VS 2015 Unicode build)
-
2016年9月15日-修复了一些语法和错别字(September 15, 2016 - Fixed some grammar and typos)
-
2018年8月26日-新关键字和固定的混淆计时代码(虽然有效,但偶然)(August 26, 2018 - New keywords and fixed confusing timing code (it worked, but by accident))
-
2018年9月-整个RAII,大量清理和新证书缓存(September 2018 - RAII throughout, much cleanup and a new certificate cache)
-
2019年6月-删除所有w4警告并将警告设置为错误,实施正确的SSL关闭.(June 2019 - Remove all the w4 warnings and set warnings as errors, implement correct SSL shutdown.)
-
2019年7月-更新和重构(July 2019 - Update and refactoring)
- 增强示例以显示停止SSL但继续未加密的连接.(Enhance sample to show stopping SSL but continuing unencrypted connection.)
- 使用更现代的C ++(使用nullptr,const和true/false,消除"(void)" …)(Use more modern C++ (use nullptr, const and true/false, eliminate “(void)”…))
- 重构CActiveSock和CPassiveSock以从通用Base Shoe继承(Refactor CActiveSock and CPassiveSock to inherit from a commom CBaseSock)
- SendPartial和RecvPartial不再公开,Send和Recv替换它们(SendPartial and RecvPartial are no longer public, Send and Recv replace them)
- 更改了预编译的标头机制以匹配使用pch.h的当前实践(The precompiled header mechanism is changed to match current practice using pch.h)
- 共享文件(cpp和h)被移到共享文件夹(Shared files (cpp and h) are moved to a shared folder)
- 添加一个64位版本(Add a 64 bit build)
- 消除了CWorker类-它没有做太多事情,并使代码更难以理解(Eliminated the CWorker class - it didn’t do much and made the code harder to understand)
-
2019年8月-版本2.1.0(August 2019 - release 2.1.0)
- 将解决方案分为2个lib项目和2个使用它们的示例(在" Samples"文件夹中).然后,选择的用户可以分发lib和h文件而无需分发源.(Split solution into 2 lib projects and 2 samples which use them (in a “Samples” folder). Users who choose to can then distribute lib and h files without distributing sources.)
- 对现代C ++的各种更新,包括转换为VS2019并在构建中使用最新的C ++一致性(C ++ 20的一部分).(Various updates to modern C++ including converting to VS2019 and using the latest C++ conformance (which is parts of C++20) in builds.)
- 该代码库现在支持版本控制,并导出调用者可以使用的GetVersionText方法(示例还使用AppVersion.h中的信息来创建版本资源).(The codebase now supports versioning and exports a GetVersionText method that callers can use (the samples also use the information in AppVersion.h to create version resources).)
- 许可证文件已添加到解决方案中,并在自述文件中进行了引用.(A license file is added to the solution, and referred to in the readme.)
- 预编译的标头名称将更改为使用新创建的项目将使用的名称(pch.h).(The precompiled header names are changed to use what a newly created project would (pch.h).)
-
2019年2月-2.1.1版(February 2019 - release 2.1.1)
- 允许使用ANSI(意味着多字节,而不是Unicode)调用程序,而不必使用MFC.(Allow for an ANSI (meaning multibyte, not Unicode) caller, eliminate the use of MFC.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
XML Objective-C VisualC++ C++ Windows VS2015 Dev certificate TLS SSL 新闻 翻译