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协议采用数字证书及数字签名进行双端实体认证,用非对称加密算法进行密钥协商,用对 ...
随机推荐
- ios 拿到第一响应者的当前视图
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; UIView *firstResponder = [keyWi ...
- hdoj 5087 Revenge of LIS II 【第二长单调递增子】
称号:hdoj 5087 Revenge of LIS II 题意:非常easy,给你一个序列,让你求第二长单调递增子序列. 分析:事实上非常easy.不知道比赛的时候为什么那么多了判掉了. 我们用O ...
- WPF 将图片进行灰度处理
原文:WPF 将图片进行灰度处理 处理前: 处理后: 这个功能使用使用了 FormatConvertedBitmap(为BitmapSource提供像素格式转换功能) 代码如下: ...
- VelocityTracker简要
翻译自:http://developer.android.com/reference/android/view/VelocityTracker.html 參照自: http://blog.jrj.co ...
- mac_开发机初始化环境
#安装 homebrew 类似 yum ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/ma ...
- Dijkstra含权图最短路径;审判,不要错过枚举退款保证不会重复;国际象棋八皇后问题
求两节点的最短通路.对于无权图,能够通过图的广度优先遍历求解.含权图一般通过Dijkstra算法求解. import java.util.ArrayList; import java.util.Has ...
- glibc 内存申请和释放及堆连续检查
C语言有两种内存申请方式: 1.静态申请:当你声明全局或静态变量的时候,会用到静态申请内存.静态申请的内存有固定的空间大小.空间只在程序开始的时候申请一次,并且不再释放(除非程序结束). 2.自动申请 ...
- 特殊存储过程——触发器Trigger
触发器类型 insert 触发器delete 触发器update 触发器 Inserted和Deleted两个临时表的作用 Inserted:对于插入记录操作来说,插入表里存放的是要插入的数据:对于更 ...
- sql server DateTime与DateTime2的区别
DateTime字段类型对应的时间格式是 yyyy-MM-dd HH:mm:ss.fff ,3个f,精确到1毫秒(ms),示例 2014-12-03 17:06:15.433 .DateTime2字段 ...
- OA 框架
@{ Layout = null;}<!DOCTYPE html><html><head> <meta name="viewport&q ...