共计 4972 个字符,预计需要花费 13 分钟才能阅读完成。
台达 PLC 解密台达 PLC 解密次数限度【业余】【信用【18230062509】在通信利用中很多时候须要和已有规范的利用协定进行通信,针对这状况就要针对相应协定的实现;标准协议上思考的状况比拟多,所以协定的复杂度也绝对高些,比照之前的 Protobuf 通信的简略协定来说则会简单。接下来用组件去实现一个简略的 HTTP 协定服务,让浏览器能够去拜访它。
HTTP 协定
对于 HTTP 协定的介绍置信也不必过多形容,毕竟这个协定曾经利用了 N 年了,网上针对这一协定的介绍也十分多。这协定的版本有 1.0,1.1 和 2.0,接下来实现的是 HTTP1.1。其实更合乎多场景利用是 2.0,不过 2.0 的协定复杂度就比拟高了,所以就不在这里实现介绍了。
HTTP 1.1 协定只容许同一时间解决一个申请,就是当服务端接管到申请后直到响应实现才会解决下一下申请。为了满足这须要针对通信协定制订了 Request 和 Response 对象。
Request 对象
该对象次要用于收集 HTTP 的申请信息,定义如下:
class HttpRequest | |
{ | |
// 以后 HTTP 版本信息 | |
public string HttpVersion {get; set;} | |
// 申请的办法 | |
public string Method {get; set;} | |
// 根底的 url | |
public string BaseUrl {get; set;} | |
// 客户端 IP | |
public string ClientIP {get; set;} | |
// 申请门路 | |
public string Path {get; set;} | |
//Url 参数 | |
public string QueryString {get; set;} | |
// 残缺 URL | |
public string Url {get; set;} | |
// 头部信息 | |
public Dictionary<string, string> Headers {get; private set;} = new Dictionary<string, string>(); | |
//HTTP 内容 | |
public byte[] Body { get; set;} | |
//HTTP 内容长度 | |
public int ContentLength {get; set;} | |
// 申请对象状态 | |
public RequestStatus Status {get; set;} = RequestStatus.None; | |
} | |
复制代码 |
以上是一个 HTTP 申请的简略形容对象,服务会依据网络数据依据 HTTP 协定转换成相应的对象音讯。
HttpResponse 对象
该对象用于设置申请响应内容,定义如下:
class HttpResponse : IWriteHandler | |
{public HttpResponse() | |
{Headers["Content-Type"] = "text/html"; | |
} | |
public string HttpVersion {get; set;} = "HTTP/1.1"; | |
public int Status {get; set;} | |
public string StatusMessage {get; set;} = "OK"; | |
public string Body {get; set;} | |
public Dictionary<string, string> Headers = new Dictionary<string, string>(); | |
public void Write(Stream stream) | |
{var pipeStream = stream.ToPipeStream(); | |
// 写入响应状态 | |
pipeStream.WriteLine($"{HttpVersion} {Status} {StatusMessage}"); | |
// 写入头部信息 | |
foreach (var item in Headers) | |
pipeStream.WriteLine($"{item.Key}: {item.Value}"); | |
byte[] bodyData = null; | |
if (!string.IsNullOrEmpty(Body)) | |
{bodyData = Encoding.UTF8.GetBytes(Body); | |
} | |
if (bodyData != null) | |
{pipeStream.WriteLine($"Content-Length: {bodyData.Length}"); | |
} | |
pipeStream.WriteLine(""); | |
// 写入响应音讯体 | |
if (bodyData != null) | |
{pipeStream.Write(bodyData, 0, bodyData.Length); | |
} | |
Completed?.Invoke(this); | |
} | |
public Action<IWriteHandler> Completed {get; set;} | |
} | |
复制代码 |
对象实现了 IWriteHandler 接口,用于通知组件提供自定义流输入实现。
协定实现
在这个示例中协定剖析并没有实现 IPacket, 而是间接接管 SessionReceive 办法来对流进行 HTTP 协定剖析,具体实现代码如下:
public override void SessionReceive(IServer server, SessionReceiveEventArgs e) | |
{var request = GetRequest(e.Session); | |
var pipeStream = e.Stream.ToPipeStream(); | |
if (LoadRequest(request, pipeStream) == RequestStatus.Completed) | |
{OnCompleted(request, e.Session); | |
} | |
} | |
private RequestStatus LoadRequest(HttpRequest request, PipeStream stream) | |
{ | |
// 剖析 HTTP 申请信息 | |
LoadRequestLine(request, stream); | |
// 剖析头信息 | |
LoadRequestHeader(request, stream); | |
// 加载 Body | |
LoadRequestBody(request, stream); | |
return request.Status; | |
} | |
private void LoadRequestLine(HttpRequest request, PipeStream stream) | |
{if (request.Status == RequestStatus.None) | |
{if (stream.TryReadLine(out string line)) | |
{var subItem = line.SubLeftWith(' ', out string value); | |
request.Method = value; | |
subItem = subItem.SubLeftWith(' ', out value); | |
request.Url = value; | |
request.HttpVersion = subItem; | |
subItem = request.Url.SubRightWith('?', out value); | |
request.QueryString = value; | |
request.BaseUrl = subItem; | |
request.Path = subItem.SubRightWith('/', out value); | |
if (request.Path != "/") | |
request.Path += "/"; | |
request.Status = RequestStatus.LoadingHeader; | |
} | |
} | |
} | |
private void LoadRequestHeader(HttpRequest request, PipeStream stream) | |
{if (request.Status == RequestStatus.LoadingHeader) | |
{while (stream.TryReadLine(out string line)) | |
{if (string.IsNullOrEmpty(line)) | |
{if (request.ContentLength == 0) | |
{request.Status = RequestStatus.Completed;} | |
else | |
{request.Status = RequestStatus.LoadingBody;} | |
return; | |
} | |
var name = line.SubRightWith(':', out string value); | |
if (String.Compare(name, "Content-Length", true) == 0) | |
{request.ContentLength = int.Parse(value); | |
} | |
request.Headers[name] = value.Trim();} | |
} | |
} | |
private void LoadRequestBody(HttpRequest request, PipeStream stream) | |
{if (request.Status == RequestStatus.LoadingBody) | |
{if (stream.Length >= request.ContentLength) | |
{var data = new byte[request.ContentLength]; ; | |
stream.Read(data, 0, data.Length); | |
request.Body = data; | |
request.Status = RequestStatus.Completed; | |
} | |
} | |
} | |
复制代码 |
在剖析过程中最罕用的办法是 TryReadLine, 次要起因 HTTP 的头信息数据都是以换行的形式来形容,直到读取一个空行表明头部已结。如果存在 Content-Length 头信息形容阐明存在音讯体(HTTP 有两种形容音讯体的状况,这里就不多作介绍了)。
服务解决
协定解决好后就能够集成在服务中,绝对于协定剖析来说集成就更简略了。
public static void Main(string[] args) | |
{mServer = SocketFactory.CreateTcpServer<Program>(); | |
mServer.Options.DefaultListen.Port = 80; | |
mServer.Options.AddListenSSL("ssl.pfx", "123456"); | |
mServer.Open(); | |
System.Threading.Thread.Sleep(-1); | |
} | |
private void OnCompleted(HttpRequest request, ISession session) | |
{HttpResponse response = new HttpResponse(); | |
StringBuilder sb = new StringBuilder(); | |
sb.AppendLine("<html>"); | |
sb.AppendLine("<body>"); | |
sb.AppendLine($"<p>Method:{request.Method}</p>"); | |
sb.AppendLine($"<p>Url:{request.Url}</p>"); | |
sb.AppendLine($"<p>Path:{request.Path}</p>"); | |
sb.AppendLine($"<p>QueryString:{request.QueryString}</p>"); | |
sb.AppendLine($"<p>ClientIP:{request.ClientIP}</p>"); | |
sb.AppendLine($"<p>Content-Length:{request.ContentLength}</p>"); | |
foreach (var item in request.Headers) | |
{sb.AppendLine($"<p>{item.Key}:{item.Value}</p>"); | |
} | |
sb.AppendLine("</body>"); | |
sb.AppendLine("</html>"); | |
response.Body = sb.ToString(); | |
ClearRequest(session); | |
session.Send(response); | |
} | |
复制代码 |
在服务中把默认监听的端口改成 80,而后增加一个 SSL 监听用于反对 HTTPS 拜访;示例中通过 OnCompleted 办法响应申请内容,次要返回的内容是把以后申请的申请详细信息输入。
拜访后果
启动服务后能够通过浏览器拜访相干地址:
- HTTP
- HTTPS
因为证书是本人创立的,所以会被浏览器标记为不平安。