C/S客户端程序 winform接收外部http (GET|POST)请求 工具类逻辑开发
前言
我们知道web项目(即B/S端程序的S端)是很容易提供API接口,供外部进行访问的,这是Web本身的特性所然。Web项目在发布后,会挂载到比如IIS管理器,上面会要求配置IP和端口号,外部访问时根据约定的IP,端口,以及约定的路由路径、请求方式、传参等就很容易外部对内API接口访问。
客户端程序(即C/S端程序的C端)对外提供API接口则不那么容易,毕竟本身客户端没有明确需要表面自身身份的IP和端口,发布好的C/S端程序拷到哪台机器都可以使用!
问题
现在有特殊场景需求,我们开发的winform (C/S架构)程序需要和上层的总控系统对接,而总控系统可能是B/S架构,它需要和我的winform程序进行接口对接,需要我暴露自身的接口,由它来调用。
这就面临一个问题:winform如何能接收外部http (GET|POST)请求?
解决过程
1.我们现在原有的项目中,新建一个HttpServerHelper类,内部封装一个Http服务器逻辑,将winform程序当作一个Http服务器,实时监听服务器的端口有没有被访问,一旦被访问则执行相关逻辑

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks; namespace DetectMonitor.Helper
{
public class HttpServerHelper
{
private readonly string selfUrl;//客户端自身(也就是该程序本身)作为服务器,需要确认以自身哪个端口来对外监听外部的访问
private readonly HttpListener myListener;
private readonly Dictionary<string, Func<HttpListenerContext, Task<string>>> myRoutes; //路由映射 //构造函数 //selfListenAddrs:实际给一个 http://ip:port/就行,不用搞太多端口区分
public HttpServerHelper(string[] selfListenAddrs)
{
if (!HttpListener.IsSupported)
{
throw new NotSupportedException("当前平台不支持HttpListener");
} myListener = new HttpListener();//初始化HttpListener实例 //初始化路由映射字典
myRoutes = new Dictionary<string, Func<HttpListenerContext, Task<string>>>();// //为服务器(就是自身)添加地址和端口 (实际) foreach (string addr in selfListenAddrs)
{
myListener.Prefixes.Add(addr); //内容格式http://ip:port/api/
} selfUrl = selfListenAddrs[0];//记录第一个监听的地址 } //判断监听实例是否在监听
public bool IsOpen
{
get { return myListener.IsListening; }
} //启动服务器
public void Start()
{
myListener.Start();
myListener.BeginGetContext(ProcessRequestCallback,myListener);//处理客户端请求
} //停止服务器
public void Stop()
{
myListener.Stop();
myListener.Close(); } //添加路由和处理程序的映射关系
public void AddRoute(string route, Func<HttpListenerContext,Task<string>> handler)
{
myRoutes.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 url = context.Request.Url.AbsolutePath;
string responseString = "No Data!";//默认响应字符串
Func<HttpListenerContext, Task<string>> handler; //如果请求路径存在与路由映射中,执行相应的处理程序
if (myRoutes.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; // 设置响应头中的 Content-Type 为 application/json 并指定字符编码为 UTF-8(修复浏览器访问GET请求时,反馈值中文乱码问题)
context.Response.ContentType = "application/json; charset=utf-8"; //将响应写入输出流并关闭输出流
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.OutputStream.Close(); }
else
{
//如果请求不存在与路由映射中,返回404错误
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.Close();
} }catch (Exception ex)
{
Log4NetHelper.Error("HttpServerHelper.ProcessRequestCallback:" + ex.Message);
} } }
}
2. 编写HttpServer的监听并调用对应接口的逻辑,在项目的加载函数或初始化函数调用执行,这样实时监听外部的端口访问,一旦监听到,就分析访问的路由,调用对应路由映射的函数
//20250102 本机C/S客户端程序提供开放接口需求,供外部调用
public static void IniHttpServer()
{
//注意:参数内的IP就是本机以后对外身份的ip(正式使用需要修改), 参数内的端口就是本机对外接受“接口”访问的端口
HttpServerHelper httpServer = new HttpServerHelper(new string[]
{
"http://127.0.0.1:7777/"
}); //绑定映射,处理函数 (路径就是将来给到外部来访问的路径,函数就是路径(接口)被访问后执行自己自定义相关逻辑函数)
//(1)如果外部访问模式3,路径为:http://127.0.0.1:7777/pattern_three,那么外部访问该路径后,会调用DetectObjectDeal函数处理函数
httpServer.AddRoute("/api/detectobject", DetectObjectDeal); //(2)同理,如果外部访问模式2:根据外部系统传入的指定测试区域,执行区域内若干摄像头按指定间隔时间自动循环切换
httpServer.AddRoute("/api/testarea", TestAreaDeal); //(3)同理,如果外部访问模式1:根据外部系统传入的指定摄像头,执行指定摄像头的切换
httpServer.AddRoute("/api/appointcamera", AppointCameraDeal); httpServer.Start(); } //模式3:根据外部系统传入的指定监测对象,执行yolo目标监测
public static async Task<string> DetectObjectDeal(HttpListenerContext context)
{
string httpMethod = context.Request.HttpMethod; //获取外部访问接口使用的是什么方法(理应事先应约定:GET POST)
Log4NetHelper.Info("接口模拟测试-----请求方法判别:" + httpMethod); string responseString = "这是我初始给的内容";//接口被调用返回内容 //判别为GET请求
if (httpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase))
{
// 处理 GET 请求
//string queryString = context.Request.QueryString.ToString();
NameValueCollection queryString = context.Request.QueryString;
if (queryString.HasKeys())
{
// 处理路径上直接传参的情况
//后面要按照约定的请求传参格式,进行对应的解析。。。
Log4NetHelper.Info("接口模拟测试-----GET请求带参数, 参数全部Keys内容:" + string.Join(", ", queryString.AllKeys)); //假设模拟的GET请求参数 http://127.0.0.1:7777/detectobject?myname="chaochao"& myage=18 进行解析
//var queryParams = System.Web.HttpUtility.ParseQueryString();
//string paramValue1 = queryParams["myname"];
// string paramValue2 = queryParams["myage"];
//Log4NetHelper.Info("接口模拟测试-----GET请求带参数,参数解析:myname:" + paramValue1 + ";myage: " + paramValue2); // 构建日志信息,显示所有键值对
string logInfo = "接口模拟测试-----GET请求带参数, 具体键值内容:"; foreach (string key in queryString.AllKeys)
{
logInfo += key + ": " + queryString[key] + "; ";
} Log4NetHelper.Info(logInfo); // 解析查询字符串并执行相应的逻辑
string paramValue1 = queryString["myname"];
int paramValue2 = Convert.ToInt32(queryString["myage"]); Log4NetHelper.Info("接口模拟测试-----GET请求带参数, 参数解析:myname: " + paramValue1 + "; myage: " + paramValue2); // 处理带参数的 GET 请求
responseString = "{\"code\":\"200\",\"message\":\"GET请求带参数处理成功\", \"myname\":\"" + paramValue1 + "\", \"myage\":\"" + paramValue2 + "\"}"; }
else
{
// 处理路径上不传参的情况
Log4NetHelper.Info("接口模拟测试-----GET请求不带参数");
responseString = "{\"code\":\"200\",\"message\":\"GET请求不带参数处理成功\"}";
//可能直接处理相关业务逻辑。。。
}
}
//判别为POST请求
else if (httpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
{
// 处理 POST 请求
if (context.Request.HasEntityBody)
{
using (Stream body = context.Request.InputStream)
{
using (StreamReader reader = new StreamReader(body, context.Request.ContentEncoding))
{
string postData = await reader.ReadToEndAsync(); // 读取 POST 数据 //处理数据
//。。。。。
//返回数据(约定返回给外部请求方)
responseString = "{\"code\":\"200\",\"message\":\"处理成功\"}";//后面根据实际约定进行定义 Log4NetHelper.Info("接口模拟测试-----POST请求数据:" + postData); }
}
}
} return responseString;
}
3.运行winform程序,通过postman测试POST请求

4.运行winform程序,通过浏览器或postman测试GET请求

C/S客户端程序 winform接收外部http (GET|POST)请求 工具类逻辑开发的更多相关文章
- 分享一个客户端程序(winform)自动升级程序,思路+说明+源码
做winform的程序,不管用没用过自动更新,至少都想过自动更新是怎么实现的. 我这里共享一个自动更新的一套版本,给还没下手开始写的人一些帮助,也希望有大神来到,给指点优化意见. 本初我是通过sock ...
- [黑马程序员] 集合框架2——Map系 & 集合工具类(Collections、Arrays)
---------------------- ASP.Net+Android+IO开发..Net培训.期待与您交流! ---------------------- 0. 集合框架按其所实现的接口, 大 ...
- 基于 SailingEase WinForm Framework 开发客户端程序(3:实现菜单/工具栏按钮的解耦及状态控制)
本系列文章将详细阐述客户端应用程序的设计理念,实现方法. 本系列文章以 SailingEase WinForm Framework 为基础进行设计并实现,但其中的设计理念及方法,亦适用于任何类型的客 ...
- winform客户端程序第一次调用webservice方法很慢的解决方法
.net2.0的winform客户端最常用的与服务端通信方式是通过webservice,最近在用dottrace对客户端做性能测试的时候发现,客户端程序启动以后,第一次调用某一个webservice的 ...
- 在winform嵌入外部应用程序
应朋友要求,需要将一个第三方应用程序嵌入到本程序WinForm窗口,以前在VB6时代做过类似的功能,其原理就是利用Windows API中FindWindow函数找到第三方应用程序句柄,再利用SetP ...
- winform客户端程序实时读写app.config文件
新接到需求,wcf客户端程序运行时,能实时修改程序的打印机名称: 使用XmlHelper读写 winform.exe.config文件修改后始终,不能实时读取出来,查询博客园,原来已有大神解释了: 获 ...
- 使用 Socket 通信实现 FTP 客户端程序(来自IBM)
FTP 客户端如 FlashFXP,File Zilla 被广泛应用,原理上都是用底层的 Socket 来实现.FTP 客户端与服务器端进行数据交换必须建立两个套接字,一个作为命令通道,一个作为数据通 ...
- WCF开发实战系列五:创建WCF客户端程序
WCF开发实战系列五:创建WCF客户端程序 (原创:灰灰虫的家http://hi.baidu.com/grayworm) 在前面的三篇文章中我们分别介绍了WCF服务的三种载体:IIS.Self-Hos ...
- 使用Socket通信实现FTP客户端程序
FTP 客户端如 FlashFXP,File Zilla 被广泛应用,原理上都是用底层的 Socket 来实现.FTP 客户端与服务器端进行数据交换必须建立两个套接字,一个作为命令通道,一个作为数据通 ...
- php编写tcp服务器和客户端程序
这是我从别的地方看到的. 1.修改php.ini,打开extension=php_sockets.dll 2.客户端程序 SocketClient.php <?php set_time_limi ...
随机推荐
- 总决赛定档!“天翼云息壤杯”高校AI大赛巅峰之战即将打响!
近日,为梦想添翼,让AI发光--"天翼云息壤杯"高校AI大赛总决赛时间正式揭晓.总决赛将于2025年7月1日至7月17日在北京举办.届时,来自全国各地上百支成功晋级的优秀队伍和特邀 ...
- Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: 解决办法
Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: 线程"main ...
- Linux系统配置windows可访问的共享文件夹
一.简单说明 某些情况下,我们需要配置Linux系统的目录为共享文件夹,windows下可以直接访问.这里可以直接安装samba进行.(samba是一款软件,主要提供cifs协议,基于文件系统传输) ...
- DotTrace系列:1. 理解四大经典的诊断类型(上)
一:背景 1. 讲故事 在所有与 .NET相关的JetBrains产品中,我觉得 DotTrace 是最值得深入学习和研究的一款,个人觉得它的优点如下: 跨平台诊断 (Windows,Linux,Ma ...
- 从零开始实现简易版Netty(一) MyNetty Reactor模式
从零开始实现简易版Netty(一) MyNetty Reactor模式 自从18年作为一个java程序员入行以来,所接触到的大量组件如dubbo.rocketmq.redisson等都是基于netty ...
- Labubu的风过了?无用的产品才是艺术
正如标题所说,在风头过去之后,我们来理性客观地聊聊Labubu. "所有的消费行为都是在解决两件事情:一个是满足感,一个是存在感.满足感,就是人的物质需求和基本精神需求得到了满足.存在感,就 ...
- Sql 日期时间各种操作
select convert(varchar(10),getdate(),120) 输出格式:2008-02-27 00:25:13 SELECT CONVERT(char(19), getdate( ...
- vue+vite初始配置和vite获取env环境变量
配置文件 在项目根目录创建两个配置文件 // .env 文件 VITE_PUBLIC_PATH=/demo/ // .env.development 文件 VITE_PUBLIC_PATH=/ VIT ...
- 多个pdf和图片、word实现预览和下载
效果 代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...
- Luogu P8925 「GMOI R1-T2」Light 题解
P8925 「GMOI R1-T2」Light 让我们好好观察样例解释的这一张图: 左边第 \(1\) 个像到 \(O\) 点的距离 :\(L\times2=2L\) 右边第 \(1\) 个像到 \( ...