C#通讯——关于Winform中的简单的Http服务器与客户端


前言

在实际项目中通讯的交互的过程中,遇见数据传输时同事和我说用WebApi,Get,Post等等,以前没用搞过这个块的我傻傻分不清。查询资料了解过后发现只是个简单的Http交互通讯,在此记录下Winform中的简单服务器和客户端的编写,并简要描述webapi与http通讯的差异。


一、Http是什么?

HTTP(Hypertext Transfer Protocol)是一种用于在网络上传输超文本的协议。它是一个用于传输和交换超文本、图像、视频、音频以及其他多媒体资源的应用层协议。

HTTP是Web的基础,它定义了客户端和服务器之间的通信规则。通常,当你在浏览器中输入网址、点击链接或者提交表单时,浏览器会发送HTTP请求到服务器,服务器收到请求后进行处理,然后返回HTTP响应给客户端(浏览器),最终在你的浏览器上显示网页内容。

HTTP协议的主要特点包括:

  • 无状态性(Stateless):
    HTTP协议本身是无状态的,每个请求都是相互独立的,服务器并不会保留前后请求之间的状态信息。这导致每个请求都需要包含足够的信息来被服务器正确处理,比如会话标识或者其他验证信息。
  • 基于请求-响应模型:
    客户端发送请求给服务器,服务器处理请求并返回响应。这个请求-响应模型是HTTP的核心概念,通过这种方式客户端能够请求不同资源并且服务器能够提供响应。
  • 支持多种请求方法:
    HTTP定义了一系列请求方法(也称为HTTP谓词),包括GET(用于获取资源)、POST(用于提交数据给服务器)、PUT(用于更新资源)、DELETE(用于删除资源)等。
  • URL定位资源:
    每个HTTP请求都包含一个URL(Uniform Resource Locator),用于指定被请求的资源的位置。
  • 无连接性:
    HTTP协议本身是无连接的,即每次请求完成后,连接会立即关闭。这意味着每个请求都需要重新建立连接,可能会造成一定的性能开销。

二、简单的Http服务器

using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;

namespace httpServer
{
    class HttpServer_DP
    {
        private readonly string mUrl; // 服务器监听的URL
        private readonly HttpListener mListener; // HttpListener实例
        private readonly Dictionary<string, Func<HttpListenerContext, Task<string>>> mRoutes; // 路由映射

        // 构造函数,接收服务器监听地址和端口的数组
        public HttpServer_DP(string[] prefixes)
        {
            if (!HttpListener.IsSupported)
            {
                throw new NotSupportedException("HttpListener is not supported on this platform.");
            }

            mListener = new HttpListener(); // 初始化HttpListener实例
            mRoutes = new Dictionary<string, Func<HttpListenerContext, Task<string>>>(); // 初始化路由映射字典

            // 为服务器添加监听地址和端口
            foreach (string prefix in prefixes)
            {
                mListener.Prefixes.Add(prefix);
            }

            mUrl = prefixes[0]; // 记录第一个监听地址
        }

        public bool IsOpen { get { return mListener.IsListening; } }

        // 启动服务器
        public void Start()
        {
            mListener.Start();
            Console.WriteLine($"Server started and listening on {mUrl}");
            mListener.BeginGetContext(ProcessRequestCallback, mListener); // 处理客户端请求
        }

        // 停止服务器
        public void Stop()
        {
            mListener.Stop();
            mListener.Close();
            Console.WriteLine("Server stopped.");
        }

        // 添加路由和处理程序的映射关系
        public void AddRoute(string route, Func<HttpListenerContext, Task<string>> handler)
        {
            mRoutes.Add(route, handler);
        }

        // 处理客户端请求的回调函数
        private async void ProcessRequestCallback(IAsyncResult result)
        {
            HttpListener listener = (HttpListener)result.AsyncState;

            // 开始下一个请求的监听
            listener.BeginGetContext(ProcessRequestCallback, listener);

            try
            {
                // 获取传入的请求
                HttpListenerContext context = listener.EndGetContext(result);

                // 获取请求方法和URL路径
                string httpMethod = context.Request.HttpMethod;
                string responseString = "No Data!"; // 默认响应字符串
                string url = context.Request.Url.AbsolutePath;
                Func<HttpListenerContext, Task<string>> handler;

                // 如果请求路径存在于路由映射中,执行相应的处理程序
                if (mRoutes.TryGetValue(url, out handler))
                {
                    responseString = await handler(context); // 获取处理程序返回的响应数据

                    // 将响应数据编码成字节数组
                    byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);

                    // 设置响应的内容长度和状态码
                    context.Response.ContentLength64 = buffer.Length;
                    context.Response.StatusCode = (int)HttpStatusCode.OK;

                    // 将响应写入输出流并关闭输出流
                    context.Response.OutputStream.Write(buffer, 0, buffer.Length);
                    context.Response.OutputStream.Close();
                }
                else
                {
                    // 如果请求路径不存在于路由映射中,返回404 Not Found
                    context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                    context.Response.Close();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error processing request: {ex.Message}");
            }
        }
    }
}

需要注意的是:Dictionary<string, Func<HttpListenerContext, Task>> mRoutes; // 路由映射
为了方便根据客户端传入的URL的不同路径来选择进行不同的处理流程

三、简单的Http客户端

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Http
{
    class HttpClient_DP
    {
        private readonly HttpClient _client; // HttpClient 实例

        // 构造函数,初始化 HttpClient 实例
        public HttpClient_DP()
        {
            _client = new HttpClient();
        }

        // 设置超时时间
        public TimeSpan TimeOut { set { _client.Timeout = value; } }

        // 发送 GET 请求并返回响应内容
        public async Task<string> Get(string url)
        {
            HttpResponseMessage response = await _client.GetAsync(url); // 发送 GET 请求
             
            if (response.IsSuccessStatusCode)
            {
                string content = await response.Content.ReadAsStringAsync(); // 读取响应内容
                return content;
            }
            else
            {
                return $"Error: {response.StatusCode}"; // 返回错误信息
            }
        }

        // 发送 POST 请求并返回响应内容
        public async Task<string> Post(string url, string data)
        {
            HttpContent content = new StringContent(data); // 创建包含请求数据的 HttpContent 实例

            HttpResponseMessage response = await _client.PostAsync(url, content); // 发送 POST 请求

            if (response.IsSuccessStatusCode)
            {
                string responseData = await response.Content.ReadAsStringAsync(); // 读取响应内容
                return responseData;
            }
            else
            {
                return $"Error: {response.StatusCode}"; // 返回错误信息
            }
        }
    }
}

其中只实现的Get与Post的相关代码,这也是当前我最常用的两个方法。
其中 public TimeSpan TimeOut为超时时间,不设置时将永久等待服务器响应。当响应超时时,外部可以通过(try-catch)捕获TaskCanceledException异常。

四、实际调用

初始化服务器:

        httpServer.HttpServer_DP HttpServer;
        public static void IniHttpServer()
        {
            HttpServer = new httpServer.HttpServer_DP(new string[]
            {
               "http://127.0.0.1:8080/"
            });
            //绑定映射,处理函数
            //当传入URL为"http://127.0.0.1:8080/PostAGVC_Data/"时将调用PostAGVC_Data函数接收和解析
            HttpServer.AddRoute("/PostAGVC_Data/", PostAGVC_Data);
            //当传入URL为"http://127.0.0.1:8080/PostAGV_Data/"时将调用PostAGV_Data函数接收和解析
            HttpServer.AddRoute("/PostAGV_Data/", PostAGV_Data);
            HttpServer.Start();
        }


		public static async Task<string> PostAGVC_Data(HttpListenerContext context)
        {
            string httpMethod = context.Request.HttpMethod;
            string responseString = "";
            // 处理 POST 请求
            if (context.Request.HasEntityBody)
            {
                // 从请求主体中获取数据
                using (System.IO.Stream body = context.Request.InputStream)
                {
                    using (System.IO.StreamReader reader = new System.IO.StreamReader(body, context.Request.ContentEncoding))
                    {
                        string postData = reader.ReadToEnd(); // 读取 POST 数据
                            //处理数据
                            //。。。。。。。。。。。。
							//。。。。。。。。。。。。
							//返回字符串
                        responseString = "OK";
                    }
                }
            }
            return responseString;
        }

		//与PostAGVC_Data内部类似,处理数据的代码不一样
		public static async Task<string> PostAGV_Data(HttpListenerContext context)
        {
            //省略
        }

由于Http的无连接性,客户端只需要实例化后,需要时进行连接就行,简单的初始化(如果需要的话)如下:

		Http.HttpClient_DP HttpClient ;
        public static void IniHttpClient()
        {
            if (HttpClient == null)
            {
                HttpClient = new Http.HttpClient_DP();
                HttpClient.TimeOut = TimeSpan.FromSeconds(10);
            }
        }

调用:
其中JsonConvert只是将我的类实例序列化成Json字符串,在此可以仅当字符串来看。

        string ret = await Global_Value.HttpClient.Post(url, JsonConvert.SerializeObject(new PLC_WorkState()
        {
            name = agvName,
            workState = workState
        }));

要注意一个问题,在不是本地回环地址(127.0.0.1)时,服务器启动会存在积极拒绝的情况,此时需要用管理员权限去打开应用程序或者是VS平台。

五、Winform中Http服务器和WebApi的区别?

在WinForms中编写的HTTP服务器类与WebAPI之间有几个主要区别:

1.目的和设计用途:

  • WinForms HTTP服务器类:
    通常是基于Socket或其他网络库手动实现的简单HTTP服务器。这样的服务器可能是为了特定目的而创建,比如在本地进行简单的通信或提供特定服务。
  • WebAPI: 是一个在Web开发中常用的框架,用于构建基于HTTP的RESTful
    API。它是ASP.NET框架的一部分,提供了更完整、功能更丰富的API开发环境,包括路由、中间件、控制器、模型绑定等。

2.功能和特性:

  • WinForms HTTP服务器类:
    往往具有较少的功能,可能只实现了基本的HTTP请求/响应处理,而缺少高级特性和工具。它通常用于较小规模的需求。
  • WebAPI:
    提供了许多功能强大的特性,例如路由,HTTP谓词(GET、POST、PUT等)的自动映射到相应的处理方法,参数绑定、模型验证等,使得开发者能够更轻松地构建和管理API。

3.集成和扩展性:

  • WinForms HTTP服务器类: 通常需要手动处理连接、请求、响应和其他网络层细节,灵活性较高,但也需要开发者自行处理许多功能。
  • WebAPI:
    集成在ASP.NET中,可以利用ASP.NET提供的丰富特性和中间件,容易与其他ASP.NET组件集成,并且具有更强的扩展性。

4.适用场景:

  • WinForms HTTP服务器类: 适用于需要在WinForms应用程序中实现简单的HTTP通信或提供基本服务的情况。

  • WebAPI: 更适合构建大型的Web应用程序或服务,提供多种HTTP服务,满足各种复杂的需求,例如构建RESTfulAPI供移动应用或Web前端使用。

总体来说,WinForms中的简单HTTP服务器类适用于简单的、局部的HTTP通信需求,而WebAPI更适用于构建和管理完整的Web服务或RESTful API,并提供了更多现成的工具和功能来简化开发流程。

  • 25
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在 C# WinForm 实现修改密码功能,可以按照以下步骤进行: 1. 在 WinForm 添加两个文本框和一个按钮,一个文本框是用于输入旧密码,另一个文本框是用于输入新密码,按钮是用于提交修改密码的操作。 2. 在按钮的 Click 事件,编写代码实现密码修改的逻辑。首先,可以将用户输入的旧密码和新密码保存到字符串变量,然后可以使用一些条件语句对密码进行验证,例如,旧密码是否正确,新密码是否符合要求等等。如果密码验证通过,可以调用相应的方法将新密码保存到数据库。 下面是一个简单的密码修改示例代码: ```csharp private void btnChangePassword_Click(object sender, EventArgs e) { string oldPassword = txtOldPassword.Text.Trim(); string newPassword = txtNewPassword.Text.Trim(); // 验证旧密码是否正确 if (!ValidateOldPassword(oldPassword)) { MessageBox.Show("旧密码不正确!", "提示"); return; } // 验证新密码是否符合要求 if (!ValidateNewPassword(newPassword)) { MessageBox.Show("新密码不符合要求!", "提示"); return; } // 将新密码保存到数据库 if (!SaveNewPassword(newPassword)) { MessageBox.Show("密码修改失败!", "提示"); return; } MessageBox.Show("密码修改成功!", "提示"); } private bool ValidateOldPassword(string password) { // TODO: 根据需要实现旧密码验证逻辑 return true; } private bool ValidateNewPassword(string password) { // TODO: 根据需要实现新密码验证逻辑 return true; } private bool SaveNewPassword(string password) { // TODO: 根据需要实现保存新密码到数据库的逻辑 return true; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值