web主从站

using Microsoft.Owin.Hosting;

using Newtonsoft.Json;

using Owin;

using System;

using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;

using System.Linq;

using System.Net;

using System.Net.Http;

using System.Net.Http.Headers;

using System.Net.Sockets;

using System.Text;

using System.Threading.Tasks;

using System.Web.Http;

using System.Web.Http.Cors;

using System.Windows.Forms;

using static WindowsFormsApp1.Form1;

namespace WindowsFormsApp1

{

public partial class Form1 : Form

{

private IDisposable _webApp;

private int _localPort = 8082;

private string _localIp;

private string _baseUrl;

// 关键修正:手动配置对方 IP,不再自动生成

private string _remoteIp = "192.168.250.1"; // 运行时手动输入对方 IP

private string _apiBaseUrl => $"http://{_remoteIp}:{_localPort}"; // 指向对方的服务地址

private static readonly HttpClient _httpClient = new HttpClient();

public Form1()

{

InitializeComponent();

this.FormClosing += Form1_FormClosing;

// 1. 获取本机 IP

_localIp = GetLocalIPAddress();

_baseUrl = $"http://{_localIp}:{_localPort}";

// 2. 添加「对方IP输入框」和「保存配置按钮」(需要在窗体上拖入两个控件)

// 窗体控件建议:

// - Label:Text = "对方IP地址:"

// - TextBox:Name = txtRemoteIp

// - Button:Name = btnSaveConfig, Text = "保存配置"

txtLog.AppendText($"本机 IP:{_localIp} | 服务地址:{_baseUrl}\r\n");

txtLog.AppendText($"请输入对方 IP 后点击【保存配置】\r\n");

// 绑定保存配置按钮事件

// btnSaveConfig.Click += btnSaveConfig_Click;

_remoteIp = "192.168.250.1";// txtRemoteIp.Text.Trim();

// 重新初始化 HttpClient 指向对方地址

InitHttpClient();

txtLog.AppendText($"已配置对方 IP:{_remoteIp} | 调用地址:{_apiBaseUrl}\r\n");

}

#region 关键修正:手动保存对方 IP 配置

private void btnSaveConfig_Click(object sender, EventArgs e)

{

//if (string.IsNullOrWhiteSpace(txtRemoteIp.Text))

//{

// MessageBox.Show("请输入对方电脑的局域网 IP!");

// return;

//}

_remoteIp = "192.168.250.1";// txtRemoteIp.Text.Trim();

// 重新初始化 HttpClient 指向对方地址

InitHttpClient();

txtLog.AppendText($"已配置对方 IP:{_remoteIp} | 调用地址:{_apiBaseUrl}\r\n");

}

#endregion

#region API 服务端(无需修改,保持不变)

private void btnStartApi_Click(object sender, EventArgs e)

{

try

{

if (IsPortInUse(_localPort))

{

txtLog.AppendText($"错误:{_localPort} 端口被占用!\r\n");

return;

}

_webApp = WebApp.Start<Startup>(url: _baseUrl);

txtLog.AppendText($"API 服务启动成功!地址:{_baseUrl}\r\n");

btnStartApi.Enabled = false;

btnStopApi.Enabled = true;

}

catch (Exception ex)

{

txtLog.AppendText($"启动失败:{ex.Message}\r\n");

}

}

private void btnStopApi_Click(object sender, EventArgs e)

{

_webApp?.Dispose();

txtLog.AppendText("API 服务已停止\r\n");

btnStartApi.Enabled = true;

btnStopApi.Enabled = false;

}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)

{

_webApp?.Dispose();

}

private bool IsPortInUse(int port)

{

try

{

using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))

{

socket.Bind(new IPEndPoint(IPAddress.Parse(_localIp), port));

return false;

}

}

catch { return true; }

}

private string GetLocalIPAddress()

{

var host = Dns.GetHostEntry(Dns.GetHostName());

foreach (var ip in host.AddressList)

{

if (ip.AddressFamily == AddressFamily.InterNetwork) return ip.ToString();

}

return "127.0.0.1";

}

#endregion

#region API 客户端(修正:指向对方地址)

private void InitHttpClient()

{

_httpClient.BaseAddress = new Uri(_apiBaseUrl);

_httpClient.Timeout = TimeSpan.FromSeconds(10);

_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

}

// GET 调用:异步执行,避免 UI 卡死

private async void btnSendGet_Click(object sender, EventArgs e)

{

if (string.IsNullOrWhiteSpace(_remoteIp))

{

MessageBox.Show("请先配置对方 IP!");

return;

}

btnSendGet.Enabled = false;

try

{

var result = await CallGetHelloApi("张三");

txtLog.AppendText($"【跨机 GET 响应】:{result}\r\n");

}

catch (Exception ex)

{

txtLog.AppendText($"GET 调用失败:{ex.Message}\r\n");

}

finally { btnSendGet.Enabled = true; }

}

// POST 调用:异步执行

private async void btnSendPost_Click(object sender, EventArgs e)

{

if (string.IsNullOrWhiteSpace(_remoteIp))

{

MessageBox.Show("请先配置对方 IP!");

return;

}

btnSendPost.Enabled = false;

try

{

var result = await CallPostUserInfoApi();

if (result.Code == 200)

{

txtLog1.Text = $"【跨机 POST 成功】处理时间:{result.Data.HandleTime} | 消息:{result.Message}";

}

else

{

txtLog1.Text = $"【跨机 POST 失败】:{result.Message}";

}

}

catch (Exception ex)

{

txtLog1.Text = $"POST 调用异常:{ex.Message}";

}

finally { btnSendPost.Enabled = true; }

}

private async Task<string> CallGetHelloApi(string name)

{

var url = $"/api/hello?name={Uri.EscapeDataString(name)}";

var response = await _httpClient.GetAsync(url);

response.EnsureSuccessStatusCode();

return await response.Content.ReadAsStringAsync();

}

// 替换原来的 CallPostUserInfoApi 方法

private async Task<ApiResponse<UserInfoResponse>> CallPostUserInfoApi()

{

var defaultRes = new ApiResponse<UserInfoResponse>

{

Code = 500,

Message = "调用失败(默认值)"

};

// 先校验对方IP是否配置

if (string.IsNullOrWhiteSpace(_remoteIp))

{

defaultRes.Message = "未配置对方IP,请先保存配置";

return defaultRes;

}

try

{

// 1. 构造请求参数(和对方的 UserInfoRequest 完全一致)

var reqModel = new UserInfoRequest

{

UserId = new Random().Next(1000, 2000), // 随机ID,方便区分不同请求

UserName = $"测试用户李四_{DateTime.Now:HHmmss}",

Age = 28

};

// 2. 序列化JSON(关键:确保无循环引用、空值处理)

string jsonBody = JsonConvert.SerializeObject(reqModel, new JsonSerializerSettings

{

NullValueHandling = NullValueHandling.Ignore,

StringEscapeHandling = StringEscapeHandling.EscapeNonAscii

});

// 3. 构造请求内容(核心修正:指定UTF-8编码)

var httpContent = new StringContent(jsonBody, Encoding.UTF8, "application/json");

// 4. 拼接完整的请求地址(避免路由匹配问题)

string fullUrl = $"{_apiBaseUrl}/api/userinfo"; // 完整地址:http://对方IP:8082/api/userinfo

Application.OpenForms["Form1"]?.Invoke(new Action(() =>

{

var form = (Form1)Application.OpenForms["Form1"];

form.txtLog.AppendText($"【准备发送Post请求】地址:{fullUrl}\r\n参数:{jsonBody}\r\n");

}));

// 5. 发送Post请求(禁用缓存,确保每次都是新请求)

var response = await _httpClient.PostAsync(fullUrl, httpContent).ConfigureAwait(false);

// 6. 打印响应状态码(关键:调试是否到达对方)

Application.OpenForms["Form1"]?.Invoke(new Action(() =>

{

var form = (Form1)Application.OpenForms["Form1"];

form.txtLog.AppendText($"【响应状态码】:{response.StatusCode} ({(int)response.StatusCode})\r\n");

}));

// 7. 读取完整响应(哪怕状态码不是200,也要读出来)

string responseStr = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

Application.OpenForms["Form1"]?.Invoke(new Action(() =>

{

var form = (Form1)Application.OpenForms["Form1"];

form.txtLog.AppendText($"【原始响应内容】:{responseStr}\r\n");

}));

// 8. 处理响应

if (response.IsSuccessStatusCode)

{

// 反序列化成功响应

var result = JsonConvert.DeserializeObject<ApiResponse<UserInfoResponse>>(responseStr);

return result ?? defaultRes;

}

else

{

// 非200状态码:返回错误信息

defaultRes.Code = (int)response.StatusCode;

defaultRes.Message = $"对方返回错误:{response.StatusCode} | {responseStr}";

return defaultRes;

}

}

catch (HttpRequestException ex)

{

defaultRes.Message = $"网络调用异常:{ex.Message}";

Application.OpenForms["Form1"]?.Invoke(new Action(() =>

{

var form = (Form1)Application.OpenForms["Form1"];

form.txtLog.AppendText($"【调用异常】:{ex.Message}\r\n");

}));

return defaultRes;

}

catch (Exception ex)

{

defaultRes.Message = $"序列化/解析异常:{ex.Message}";

Application.OpenForms["Form1"]?.Invoke(new Action(() =>

{

var form = (Form1)Application.OpenForms["Form1"];

form.txtLog.AppendText($"【其他异常】:{ex.Message}\r\n");

}));

return defaultRes;

}

}

#endregion

#region 数据模型(保持不变)

public class UserInfoRequest

{

Required(ErrorMessage = "用户ID不能为空")

Range(1, int.MaxValue)

public int UserId { get; set; }

Required(ErrorMessage = "用户名不能为空")

StringLength(20)

public string UserName { get; set; } = string.Empty;

Range(0, 150)

public int Age { get; set; }

}

public class ApiResponse<T>

{

public int Code { get; set; }

public string Message { get; set; } = string.Empty;

public T Data { get; set; } = default;

}

public class UserInfoResponse

{

public int UserId { get; set; }

public string UserName { get; set; } = string.Empty;

public int Age { get; set; }

public string HandleTime { get; set; } = string.Empty;

}

#endregion

}

#region OWIN 启动 + 控制器(保持不变)

public class Startup

{

public void Configuration(IAppBuilder app)

{

var config = new HttpConfiguration();

config.EnableCors(new EnableCorsAttribute("*", "*", "*"));

config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });

app.UseWebApi(config);

}

}

public class ExternalApiController : ApiController

{

HttpGet

Route("api/hello")

public IHttpActionResult GetHello(string name = "访客")

{

return Json(new ApiResponse<string>

{

Code = 200,

Message = "成功",

Data = $"你好 {name}!我是 {GetLocalIPAddress()} 的服务",

});

}

HttpPost

Route("api/userinfo")

public IHttpActionResult PostUserInfo([FromBody] UserInfoRequest request)

{

// 关键:添加日志,确认方法被调用(会输出到对方的txtLog控件)

string logMsg = $"【PostUserInfo被调用】时间:{DateTime.Now:HH:mm:ss.fff} | ";

if (request == null)

{

logMsg += "请求参数为NULL";

}

else

{

logMsg += $"UserId={request.UserId}, UserName={request.UserName}, Age={request.Age}";

}

// 跨线程调用窗体控件(必须用Invoke,否则会报错)

Application.OpenForms["Form1"]?.Invoke(new Action(() =>

{

var form = (Form1)Application.OpenForms["Form1"];

form.txtLog.AppendText(logMsg + "\r\n");

}));

// 原有的参数校验逻辑

if (request == null)

{

return Json(new ApiResponse<object>

{

Code = 400,

Message = "参数不能为空"

});

}

var validationResults = new List<ValidationResult>();

var isValid = Validator.TryValidateObject(request, new ValidationContext(request), validationResults, true);

if (!isValid)

{

return Json(new ApiResponse<object>

{

Code = 400,

Message = "参数校验失败",

Data = validationResults.Select(v => v.ErrorMessage)

});

}

try

{

return Json(new ApiResponse<UserInfoResponse>

{

Code = 200,

Message = $"接收用户 {request.UserName} 的信息成功(来自 {GetLocalIPAddress()})",

Data = new UserInfoResponse

{

UserId = request.UserId,

UserName = request.UserName,

Age = request.Age,

HandleTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")

}

});

}

catch (Exception ex)

{

return Json(new ApiResponse<object>

{

Code = 500,

Message = "服务器内部错误",

Data = ex.Message

});

}

}

private static string GetLocalIPAddress()

{

var host = Dns.GetHostEntry(Dns.GetHostName());

foreach (var ip in host.AddressList)

if (ip.AddressFamily == AddressFamily.InterNetwork) return ip.ToString();

return "127.0.0.1";

}

}

#endregion

}

相关推荐
lihongli0002 小时前
四连杆机构驱动角与被驱动连杆角度关系
c#
℡枫叶℡2 小时前
C# - 指定友元程序集
开发语言·c#·友元程序集
黑棠会长2 小时前
微服务实战.06 |微服务对话时,你选择打电话还是发邮件?
微服务·云原生·架构·c#
黄美美分享3 小时前
【免费开源】多平台直播录制软件!支持 40 + 平台直播录制,附软件下载!
windows·自动化·电脑·开源软件
xb11323 小时前
C#串口通信
开发语言·c#
周杰伦fans3 小时前
CAD二次开发中的线程、异步操作与LockDocument
c#
绝世唐门三哥3 小时前
Mac 查看手机信息工具 + platform-tools 使用全指南
windows·macos
x-cmd3 小时前
[x-cmd] Wine 11.0 史诗级发布:Linux 运行 Windows 应用和游戏进入“完全体”时代
linux·windows·游戏
绿浪19844 小时前
C#与C++高效互操作指南
c++·c#