C#使用 SSL Socket 建立 Client 与 Server 连接
当 Client 与 Server 需要建立一个沟通的管道时可以使用 Socket 的方式建立一个信道,但是使用单纯的 Socket 联机信道可能会担心传输数据的过程中可能被截取修改因而不够安全,为了防止这种情况我们可以使用建立 SSL Socket 的方式来进行数据的传输,所以这篇文章就来说明一下该如何建立 SSL Socket 信道,说实在本人对于凭证这个东西不是很熟悉,虽然在MSDN中已经有范例指导该如何建立 SSL Socket 方法,但是还是在凭证的操作上卡了一下,所以也会将卡住的地方举出说明以免各位也卡在那儿。
范例将使用 SslStream类别 来说明建立的方法,SslStream 传输方式提供了讯息机密性和完整性检查,当使用 SslStream 时可以防止传输的信息被有心人读取或窜改,使用 SslStream 时需要配合 TcpClient 与 TcpListener 一起使用,当客户端需要与服务器建立联机时需要提供X509凭证与服务器的X509凭证进行验证,SSL 通讯协议协助为使用 SslStream 传输的讯息提供机密性和完整性检查。 当在客户端和服务器之间进行敏感信息通讯时,应当使用 SSL 连接,例如由SslStream 提供的连接。 使用 SslStream 可以协助防止任何人对在网络上传输的信息进行读取或窜改,在客户端与服务器的凭证使用上差别在客户端使用的凭证不需要包含私钥(*.cer)而服务器则需要包含私钥的凭证(*.pfx)。
建立与使用凭证
建立凭证
首先要使用SSL就需要先建立一个凭证,但该如何建立跟使用呢? 可以先参考使用凭证此篇文章。
对于凭证的建立我们可以使用 Makecert.exe 工具,如果有装 Visual Studio 则可以透过以下方式为建立,「开始」→「所有程序」→「Microsoft Visual Studio 2010」→「Visual Studio Tools」→「Visual Studio 命令提示字符 (2010)」
开启命令提示字符后输入:makecert -r -pe -n "CN=SslSocket" -ss My -sky exchange
参数说明如下:
- -r :建立自动签名的凭证。
- -pe :将产生的私钥标记为可导出。 如此可在凭证中加入私钥。
- -n :指定主体的凭证名称,使用双引号包覆名称开头必须加CN=。
- -ss :指定主体的证书存储名称,其储存输出凭证,My为证书存储的个人存放区。
- -sky exchange :指定收受者的密钥类型,必须是下列之一:signature(表示今要用于数字签名),exchange(表示密钥用于密钥加密和密钥交换),或一个代表提供程序类型的整数。
详细的参数说明可以参考此文章Makecert.exe (凭证建立工具)
导出与汇入凭证
在上个步骤中我们已经建立好之后要使用的凭证,接下来就必须将建立好的凭证导出供服务器使用以及汇入到客户端的计算机中,而详细的步骤如下。
导出凭证
- 「开始」→「执行」→「输入MMC」,开启控制台
- 「档案」→「新增或移除崁入式管理单元」,在「可用崁入式管理单元」列表中找到凭证后新增到「选取的崁入式管理单元」中
接下来就能够看到刚刚建立的凭证在个人凭证内
将此凭证导出成包含私钥凭证与不包含私钥凭证
汇入凭证
凭证产生完成后就需要将产生的凭证汇入,SslSocket.pfx 之后将提供给服务器使用,而 SslSocket.cer 将提供给客户端使用,凭证汇入的方式如下。
服务器凭证
将 SslSocket.pfx 放置在项目底下提供程序取用。
客户端凭证
将 SslSocket.cer 于客户端计算机使用以下步骤汇入。
开启「IE」→「工具」→「因特网选项」→「内容」→「凭证」→「受信任的跟证书授权单位」→「汇入」
范例
Step 1
经过以上步骤后凭证的设定已经完成接下来就是要撰写程序代码进行测试,首先建立一个 Windows Application 传案当作 Server 使用。
拉一个窗体窗口出来,如下
产生一个 SslSocket类别 加入以下程序代码
public sealed class SslSocket
{
private static TcpListener listener;
private static X509Certificate ServerCertificate = null;
private static bool IsRun = true; private static string _Certificate = string.Empty;
public static string Certificate
{
get { return _Certificate; }
set { _Certificate = value; }
} /// <summary>
/// 執行服務器監聽
/// </summary>
public static void RunServer()
{ // 建立X509憑證
ServerCertificate = new X509Certificate(Certificate, "ssl");
// 監聽任何IP Address來的訊息
listener = new TcpListener(System.Net.IPAddress.Any, );
// 開啟監聽
listener.Start(); while (IsRun)
{
UpdateStatus(string.Format("{0}-等待客戶端連接", DateTime.Now.ToString("HH:mm:ss")));
TcpClient client = listener.AcceptTcpClient(); if (!IsRun)
{
listener.Stop();
client.Close();
}
else
{
ProcessClient(client);
}
}
} /// <summary>
/// 停止服務器監聽
/// </summary>
public static void StopServer()
{
IsRun = false;
UpdateStatus(string.Format("{0}-停止客戶端連接", DateTime.Now.ToString("HH:mm:ss")));
} /// <summary>
/// 接收客戶端訊息處理並回覆
/// </summary>
/// <param name="pClient"></param>
private static void ProcessClient(TcpClient pClient)
{
SslStream sslStream = new SslStream(pClient.GetStream(), true); try
{
sslStream.AuthenticateAsServer(ServerCertificate, false, SslProtocols.Tls, true);
sslStream.ReadTimeout = ;
sslStream.WriteTimeout = ;
UpdateStatus(string.Format("{0}-等待客戶端訊息", DateTime.Now.ToString("HH:mm:ss"))); string messageData = ReadMessage(sslStream); UpdateStatus(string.Format("{0}-接收訊息內容: {1}", DateTime.Now.ToString("HH:mm:ss"), messageData));
byte[] message = Encoding.UTF8.GetBytes(string.Format("服務器已接收此: {0} 訊息<EOF>", messageData)); UpdateStatus(string.Format("{0}-回覆客戶端訊息", DateTime.Now.ToString("HH:mm:ss")));
sslStream.Write(message);
}
catch (Exception)
{
sslStream.Close();
pClient.Close();
return;
}
finally
{
sslStream.Close();
pClient.Close();
}
} /// <summary>
/// 讀取訊息內容
/// </summary>
/// <param name="pSslStream"></param>
/// <returns></returns>
private static string ReadMessage(SslStream pSslStream)
{
byte[] buffer = new byte[];
StringBuilder messageData = new StringBuilder();
int bytes = -;
do
{
bytes = pSslStream.Read(buffer, , buffer.Length); Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, , bytes)];
decoder.GetChars(buffer, , bytes, chars, );
messageData.Append(chars); if (messageData.ToString().IndexOf("<EOF>") != -)
{
break;
}
} while (bytes != ); return messageData.ToString();
} /// <summary>
/// 更新主視窗ListBoxUI
/// </summary>
/// <param name="pMessage"></param>
private static void UpdateStatus(string pMessage)
{
Form1.MainListBox.Invoke(new Action(() => Form1.MainListBox.Items.Add(pMessage)));
}
}
此类别中 RunServer 方法为启动监听需呼叫的方法,首先将透过 X509Certificate 建构函式 (String, String) 建立一个 X509 凭证存入密钥容器,传入参数为 (FileName, Password),之后建立 TcpListener 对象用来监听来至于 TCP 客户端的链接且在此需要指定本机 IP 及 Port ,而在 While 循环内则建立 TcpClient 对象取得客户端来连接时的 NetworkStream 的数据流,在此 TcpListener 使用了 AcceptTcpClient (接受暂止联机要求) 方法,意思是此监听将处于暂时静止状态,当客户端有链接时才会响应,所以最好将 SslSocket 类别使用执行序执行以避免主线程阻塞。
当客户端已连接后会执行 ProcessClient 方法,此时将建立 SslStream 对象来接收客户端传送来的数据流并且进行服务器的凭证验证,而后进行数据的读取动作,其中<EOF>卷标为标注讯息的结尾判断使用,最后将处理完的数据回入数据流中传送至客户端处理。
接着在窗体程序中产生一个执行序去执行 RunServer 方法启动监听同时还需要指定服务器使用的凭证。
public partial class Form1 : Form
{
public static ListBox MainListBox; public Form1()
{
InitializeComponent(); MainListBox = this.lbxMsg;
} private void btnStart_Click(object sender, EventArgs e)
{
Thread socket = new Thread(RunSocket);
socket.IsBackground = true;
socket.Start();
} private void btnStop_Click(object sender, EventArgs e)
{
SslSocket.StopServer();
} private void RunSocket()
{
SslSocket.Certificate = Application.StartupPath + @"\SslSocket.pfx";
SslSocket.RunServer();
}
}
Step 2
接下来建立一个客户端用来连接服务器沟通,建立一个 Web 网站于方案中,将刚刚产生的 SslSocket.cer 凭证放置在网站底下,简单拉一个测试画面。
建立一个 SendToServer 类别,主要工作于将客户端讯息传送至服务器端,详细代码如下。
public class SendToServer
{
public string HostAddress { get; set; }
public int HostPort { get; set; } /// <summary>
/// 建構子,傳入服務器IP及Port
/// </summary>
/// <param name="pHostAddress"></param>
/// <param name="pHostPort"></param>
public SendToServer(string pHostAddress, int pHostPort)
{
HostAddress = pHostAddress;
HostPort = pHostPort;
} /// <summary>
/// 執行將訊息發送至服務器方法
/// </summary>
/// <param name="pMessage"></param>
/// <returns></returns>
public string SendMsgToServer(string pMessage)
{
TcpClient client = new TcpClient(HostAddress, HostPort);
SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
X509CertificateCollection certs = new X509CertificateCollection();
X509Certificate cert = X509Certificate.CreateFromCertFile(HttpContext.Current.Server.MapPath(@"~/cer/SslSocket.cer"));
certs.Add(cert);
try
{
sslStream.AuthenticateAsClient("SslSocket", certs, System.Security.Authentication.SslProtocols.Tls, true);
}
catch (Exception ex)
{
client.Close();
return ex.Message;
}
byte[] messsage = Encoding.UTF8.GetBytes(string.Format("{0}<EOF>", pMessage));
sslStream.Write(messsage);
sslStream.Flush();
string serverMessage = ReadMessage(sslStream);
client.Close();
return serverMessage;
} /// <summary>
/// 讀取訊息內容
/// </summary>
/// <param name="pSslStream"></param>
/// <returns></returns>
private string ReadMessage(SslStream pSslStream)
{
byte[] buffer = new byte[];
StringBuilder messageData = new StringBuilder();
int bytes = -;
do
{
bytes = pSslStream.Read(buffer, , buffer.Length); Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, , bytes)];
decoder.GetChars(buffer, , bytes, chars, );
messageData.Append(chars); if (messageData.ToString().IndexOf("<EOF>") != -)
{
break;
}
} while (bytes != ); return messageData.ToString();
} /// <summary>
/// 驗證服務器SSL憑證
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
return false;
}
}
在 SendMsgToServer 方法中,开始先建立 TcpClient 对象连接至指定的服务器 IP 及 Port ,之后建立 SslStream 对象并透过 AuthenticateAsClient 方法验证与服务器的凭证,验证成功后将讯息转换成 byte[] 写入数据流中传送至服务器处理,服务器处理完成后将回写数据至客户端进行解析后显示。
最后在测试页面程序代码中,建立一个 SendToServer 对象并且指定其 IP 及 Port 后呼叫 SendMsgToServer 方法即可。
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{ }
protected void btnSend_Click(object sender, EventArgs e)
{
SendToServer send = new SendToServer("127.0.0.1", );
lblResult.Text = send.SendMsgToServer(txtMessage.Text.Trim());
}
}
以上为参考MSDN范例而产生的一个简单的使用说明。
参考数据
C#使用 SSL Socket 建立 Client 与 Server 连接的更多相关文章
- ESP8266 station模式下建立client、server TCP连接
程序实现内容: 1.在station模式下,ESP8266作为client.server进行TCP连接2.实现数据的发送.接收(同时回传)实现思路:TCP网络通信分层为:应用层.网络层.数据链路层.物 ...
- 上机题目(0基础)- Java网络操作-Socket实现client和server端通信二(Java)
上一节实现了client像server端发送请求.本节将实现server端向client回传信息.实现原理非常easy,在原来的基础上.在server端实现输出流,在client实现输入流就可以,详细 ...
- 上机题目(0基础)- Java网络操作-Socket实现client和server端通信(Java)
非常多刚開始学习的人对于java网络通信不太熟悉.对相关概念也不太明确,这里我们主要实现一下socket通信,socket通信在java中应用十分广泛.比如QQ和MSN等都是基于socket通信的,什 ...
- Socket实现client和server端通信(Java)(转)
转自: https://blog.csdn.net/yayun0516/article/details/50819147 https://www.jianshu.com/p/2d4f223f1462 ...
- socket 建立网络连接,client && server
client代码: package socket; import java.io.IOException; import java.net.Socket; /** * 客户端_聊天室 * * @aut ...
- 套接口socket编程(Client/Server编程实例)
基本概念 套接口也就是网络中的ID.网络通信,归根到底还是进程间通信(不同计算机上的进程间的通信).在网络中,每一个节点(计算机或路由器)都有一个网络地址,也就是IP地址. IP地址:在网络中唯一标识 ...
- TCP/UDP Socket调试工具提供了TCP Server,TCP Client,UDP Server,UDP Client,UDP Group 五种Socket调试方案。
一.TCP通信测试: 1) 创建TCP Server: 选中左方的TCP Server, 然后点击”创建”按钮,软件弹出监听端口输入框 输入监听端口后,即创建了一个在指定端口上进行监听的TCP S ...
- java中关于SSL/TSL的介绍和如何实现SSL Socket双向认证
一. SSL概述 SSL协议采用数字证书及数字签名进行双端实体认证,用非对称加密算法进行密钥协商,用对称加密算法将数据加密后进行传输以保证数据的保密性,并且通过计算数字摘要来验证数据在传 ...
- SSL+socket详解
转自:http://hengstart.iteye.com/blog/842529 一. SSL概述 SSL协议采用数字证书及数字签名进行双端实体认证,用非对称加密算法进行密钥协商,用对 ...
随机推荐
- OpenCV中基于Haar特征和级联分类器的人脸检测
使用机器学习的方法进行人脸检测的第一步需要训练人脸分类器,这是一个耗时耗力的过程,需要收集大量的正负样本,并且样本质量的好坏对结果影响巨大,如果样本没有处理好,再优秀的机器学习分类算法都是零. 今年3 ...
- WPF的消息机制(一)- 让应用程序动起来
原文:WPF的消息机制(一)- 让应用程序动起来 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/powertoolsteam/article/det ...
- Boltzmann 玻尔兹曼机(BM)
Hopfield + 模拟退火 ⇒ Boltimann machine(随机神经网络),由 Hinton 和他的长期合作者 Sejnowski(Hopfield 的博士生) 共同提出. 1. 基本公式 ...
- android viewpager fragment切换时界面卡顿解决办法
目前开发的程序在切换View时界面卡顿现象比较严重,影响用户体验,当前项目共就四个View,每个View也只是按钮,所以可以同时加载,不让其它view销毁. 只需在Adapter中重载destroyI ...
- CORSFilter
import java.io.IOException; import javax.servlet.Filter;import javax.servlet.FilterChain;import java ...
- Painting and Drawing[MSDN/Windows GDI]
https://msdn.microsoft.com/en-us/library/dd162759(v=vs.85).aspx Painting and Drawing This overview d ...
- ItemsPanelTemplate
用以定义集合控件的容器外观,如ListBox,Combox 等等使用一个自定义的ListBox用以说明,其默认外观是上下排列,这里修改成横向排列 <Window.Resources> &l ...
- sql 循环 随机数创建数据
--循环 WHILE @i<40 BEGIN …… end --随机数 SET @money=rand()*100000 例子: DECLARE @i INT DECLARE @money MO ...
- dotnet pack 打包文件版本号引起 "Could not load file or assembly" 问题
如果不是遇到,真的不会想到,代码世界的问题真是千奇百怪,这次遇到的是 dotnet pack 打包文件版本号引起的问题. 之前进行 nuget 打包都是在 Visual Studio build 时进 ...
- .net与.net core学习目录
.net C#调用python 模拟请求(模拟header/gzip解压/泛型) C#控制台关闭之前做一些操作 C# 元组.匿名对象.ref&out DataTable转换为Entity(反射 ...