支持帧消息协议并改进 TCP 服务器
添加消息帧(头/尾)支持和稳健的接收逻辑,以及客户端管理和并发性改进。SocketServerOptions:将默认 Backlog 增加至 1000,并添加 MessageHeader/MessageFooter 选项。TcpSocketServer:添加客户端跟踪集合、同步对象和 CancellationTokenSource;引入 GetClientIds/GetClientIdByDevice、SendToDeviceAsync、SendMessageAsync、BroadcastAsync 及其他辅助方法。消息/接收路径:将 ReadLineAsync 替换为 ReceiveFullMessageAsync,该函数处理头/尾帧和编码,并在触发事件前检测 JSON 负载。更新 WriteToClientAsync 及其他发送路径以使用帧消息。更新 appsettings.json 注释并将 SocketServer.Backlog 改为 1000。更新 .gitignore 以忽略 .vs 和 .db 文件。此外,还包括 Visual Studio 工作空间布局和 Copilot 索引数据库更改(IDE 元数据),并添加一个 xlsx 文件。
| | |
| | | /Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/copilot-chat |
| | | /Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/CopilotIndices |
| | | /Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/CopilotIndices |
| | | /Code/WCS/WIDESEAWCS_Server/.vs |
| | | /Code/WCS/WIDESEAWCS_Server/.vs |
| | | *.db |
| | | Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/CopilotIndices/18.0.988.22099/CodeChunks.db |
| | | Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/CopilotIndices/18.0.988.22099/SemanticSymbols.db |
| | |
| | | "WorkspaceRootPath": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\", |
| | | "Documents": [ |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\robotjob\\robotjob.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\robotjob\\robotjob.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | }, |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\socketserver\\tcpsocketserver.messaging.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\socketserver\\tcpsocketserver.messaging.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | }, |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\socketserver\\tcpsocketserver.server.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\socketserver\\tcpsocketserver.server.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\socketserver\\socketserveroptions.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\socketserver\\socketserveroptions.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | }, |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\socketserver\\tcpsocketserver.clients.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\socketserver\\tcpsocketserver.clients.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | }, |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\robotjob\\robotjob.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\robotjob\\robotjob.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | }, |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{487FA45B-EA1A-4ACA-BB5B-0F6708F462C0}|WIDESEAWCS_Server\\WIDESEAWCS_Server.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_server\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", |
| | | "RelativeMoniker": "D:0:0:{487FA45B-EA1A-4ACA-BB5B-0F6708F462C0}|WIDESEAWCS_Server\\WIDESEAWCS_Server.csproj|solutionrelative:wideseawcs_server\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" |
| | | }, |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\socketserver\\tcpsocketserver.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\socketserver\\tcpsocketserver.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | }, |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\socketserver\\tcpsocketserver.clients.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\socketserver\\tcpsocketserver.clients.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\socketserver\\tcpsocketserver.server.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | | "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\socketserver\\tcpsocketserver.server.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" |
| | | }, |
| | | { |
| | | "AbsoluteMoniker": "D:0:0:{83F18A31-5983-4587-A0B2-414BF70E50B5}|WIDESEAWCS_TaskInfoService\\WIDESEAWCS_TaskInfoService.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_taskinfoservice\\taskservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", |
| | |
| | | "DocumentGroups": [ |
| | | { |
| | | "DockedWidth": 200, |
| | | "SelectedChildIndex": 11, |
| | | "SelectedChildIndex": 14, |
| | | "Children": [ |
| | | { |
| | | "$type": "Bookmark", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 8, |
| | | "DocumentIndex": 1, |
| | | "Title": "SocketServerOptions.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\SocketServerOptions.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\SocketServer\\SocketServerOptions.cs", |
| | | "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\SocketServerOptions.cs", |
| | | "RelativeToolTip": "WIDESEAWCS_Tasks\\SocketServer\\SocketServerOptions.cs", |
| | | "ViewState": "AgIAACEAAAAAAAAAAAAQwCAAAAA7AAAAAAAAAA==", |
| | | "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", |
| | | "WhenOpened": "2026-02-03T03:20:50.458Z", |
| | | "EditorCaption": "" |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 4, |
| | | "Title": "appsettings.json", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Server\\appsettings.json", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Server\\appsettings.json", |
| | | "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Server\\appsettings.json", |
| | | "RelativeToolTip": "WIDESEAWCS_Server\\appsettings.json", |
| | | "ViewState": "AgIAABUAAAAAAAAAAAAAADYAAAAbAAAAAAAAAA==", |
| | | "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|", |
| | | "WhenOpened": "2026-02-03T03:54:20.54Z", |
| | | "EditorCaption": "" |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 10, |
| | | "Title": "TaskTypeEnum.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Common\\TaskEnum\\TaskTypeEnum.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Common\\TaskEnum\\TaskTypeEnum.cs", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 7, |
| | | "DocumentIndex": 9, |
| | | "Title": "TaskEnumHelper.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Common\\TaskEnum\\TaskEnumHelper.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Common\\TaskEnum\\TaskEnumHelper.cs", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 5, |
| | | "DocumentIndex": 7, |
| | | "Title": "TaskService.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_TaskInfoService\\TaskService.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_TaskInfoService\\TaskService.cs", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 11, |
| | | "DocumentIndex": 13, |
| | | "Title": "FormationCommonStackerCraneJob.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\FormationStackerCraneJob\\FormationCommonStackerCraneJob.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\FormationStackerCraneJob\\FormationCommonStackerCraneJob.cs", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 10, |
| | | "DocumentIndex": 12, |
| | | "Title": "ITaskService.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_ITaskInfoService\\ITaskService.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_ITaskInfoService\\ITaskService.cs", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 6, |
| | | "DocumentIndex": 8, |
| | | "Title": "TaskStatusEnum.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Common\\TaskEnum\\TaskStatusEnum.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Common\\TaskEnum\\TaskStatusEnum.cs", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 9, |
| | | "DocumentIndex": 11, |
| | | "Title": "CommonStackerCraneJob.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\StackerCraneJob\\CommonStackerCraneJob.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\StackerCraneJob\\CommonStackerCraneJob.cs", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 12, |
| | | "DocumentIndex": 14, |
| | | "Title": "QuartzNetExtension.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_QuartzJob\\QuartzNet\\QuartzNetExtension.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_QuartzJob\\QuartzNet\\QuartzNetExtension.cs", |
| | |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 0, |
| | | "DocumentIndex": 3, |
| | | "Title": "RobotJob.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\RobotJob\\RobotJob.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\RobotJob\\RobotJob.cs", |
| | | "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\RobotJob\\RobotJob.cs", |
| | | "RelativeToolTip": "WIDESEAWCS_Tasks\\RobotJob\\RobotJob.cs", |
| | | "ViewState": "AgIAACMAAAAAAAAAAAAAwD4AAABRAAAAAAAAAA==", |
| | | "ViewState": "AgIAAFYAAAAAAAAAAAAAAGwAAAAMAAAAAAAAAA==", |
| | | "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", |
| | | "WhenOpened": "2026-02-02T03:52:06.502Z", |
| | | "EditorCaption": "" |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 1, |
| | | "DocumentIndex": 0, |
| | | "Title": "TcpSocketServer.Messaging.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Messaging.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Messaging.cs", |
| | | "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Messaging.cs", |
| | | "RelativeToolTip": "WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Messaging.cs", |
| | | "ViewState": "AgIAACMAAAAAAAAAAAAkwD4AAAAQAAAAAAAAAA==", |
| | | "ViewState": "AgIAALYAAAAAAAAAAAAIwLsAAAAWAAAAAAAAAA==", |
| | | "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", |
| | | "WhenOpened": "2026-02-02T03:38:35.325Z", |
| | | "EditorCaption": "" |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 3, |
| | | "DocumentIndex": 5, |
| | | "Title": "TcpSocketServer.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.cs", |
| | | "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.cs", |
| | | "RelativeToolTip": "WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.cs", |
| | | "ViewState": "AgIAACQAAAAAAAAAAAAowDYAAAA/AAAAAAAAAA==", |
| | | "ViewState": "AgIAAB8AAAAAAAAAAAAmwDsAAABaAAAAAAAAAA==", |
| | | "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", |
| | | "WhenOpened": "2026-02-02T03:37:56.495Z", |
| | | "EditorCaption": "" |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 4, |
| | | "DocumentIndex": 2, |
| | | "Title": "TcpSocketServer.Clients.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Clients.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Clients.cs", |
| | | "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Clients.cs", |
| | | "RelativeToolTip": "WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Clients.cs", |
| | | "ViewState": "AgIAAAAAAAAAAAAAAAAAAE0AAAAhAAAAAAAAAA==", |
| | | "ViewState": "AgIAAEQAAAAAAAAAAAAkwGAAAAAAAAAAAAAAAA==", |
| | | "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", |
| | | "WhenOpened": "2026-02-02T03:37:53.636Z", |
| | | "EditorCaption": "" |
| | | }, |
| | | { |
| | | "$type": "Document", |
| | | "DocumentIndex": 2, |
| | | "DocumentIndex": 6, |
| | | "Title": "TcpSocketServer.Server.cs", |
| | | "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Server.cs", |
| | | "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Server.cs", |
| | | "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Server.cs", |
| | | "RelativeToolTip": "WIDESEAWCS_Tasks\\SocketServer\\TcpSocketServer.Server.cs", |
| | | "ViewState": "AgIAAFMAAAAAAAAAAAAQwGwAAAAAAAAAAAAAAA==", |
| | | "ViewState": "AgIAAAAAAAAAAAAAAAAAAGwAAAAAAAAAAAAAAA==", |
| | | "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", |
| | | "WhenOpened": "2026-02-02T03:37:49.526Z", |
| | | "EditorCaption": "" |
| | |
| | | "WebSocketEnable": true, //æ¯å¦å¼å¯WebSocketæå¡ |
| | | "WebSocketPort": 9296, //WebSocketæå¡ç«¯å£ |
| | | "SocketServer": { |
| | | "Enabled": true, |
| | | "Port": 2000, |
| | | "IpAddress": "0.0.0.0", |
| | | "Backlog": 100, |
| | | "EncodingName": "utf-8", |
| | | "AutoDetectEncoding": true, |
| | | "IdleTimeoutSeconds": 300, |
| | | "EnableHeartbeat": true, |
| | | "LogFilePath": "socketserver.log" |
| | | "Enabled": true, //æ¯å¦å¯ç¨Socketæå¡å¨ |
| | | "Port": 2000, //çå¬ç«¯å£ |
| | | "IpAddress": "0.0.0.0", //çå¬å°åï¼ |
| | | "Backlog": 1000, //æå¤§è¿æ¥æ° |
| | | "EncodingName": "utf-8", //ç¼ç æ¹å¼ |
| | | "AutoDetectEncoding": true, //æ¯å¦èªå¨æ£æµç¼ç |
| | | "IdleTimeoutSeconds": 300, //空é²è¶
æ¶æ¶é´ï¼åä½ç§ï¼0表示ä¸è¶
æ¶ |
| | | "EnableHeartbeat": true, //æ¯å¦å¯ç¨å¿è·³æ£æµ |
| | | "LogFilePath": "socketserver.log" //æ¥å¿æä»¶è·¯å¾ |
| | | } |
| | | } |
| | |
| | | /// <summary> |
| | | /// è¿æ¥éåé¿åº¦ |
| | | /// </summary> |
| | | public int Backlog { get; set; } = 100; |
| | | public int Backlog { get; set; } = 1000; |
| | | |
| | | /// <summary> |
| | | /// ææ¬ç¼ç åç§°ï¼ä¾å¦: utf-8, gbkï¼ |
| | |
| | | /// æ¥å¿æä»¶è·¯å¾ï¼ç¸å¯¹äºç¨åºè¿è¡ç®å½ï¼ |
| | | /// </summary> |
| | | public string LogFilePath { get; set; } = "socketserver.log"; |
| | | |
| | | /// <summary> |
| | | /// æ¶æ¯å¤´æ è®° |
| | | /// </summary> |
| | | public string MessageHeader { get; set; } = "<START>"; |
| | | |
| | | /// <summary> |
| | | /// æ¶æ¯å°¾æ è®° |
| | | /// </summary> |
| | | public string MessageFooter { get; set; } = "<END>"; |
| | | } |
| | | } |
| | |
| | | { |
| | | public partial class TcpSocketServer |
| | | { |
| | | /// <summary> |
| | | /// æ£ç´¢å½å卿å¡ä¸æ³¨åçææå®¢æ·ç«¯æ è¯ç¬¦çåªè¯»å表ã |
| | | /// </summary> |
| | | /// <remarks>è¿åçå表表示è°ç¨æ¶å»å®¢æ·ç«¯IDçå¿«ç
§ãåç»å¯¹å®¢æ·ç«¯éåçæ´æ¹ä¸ä¼å½±åè¿åçåè¡¨ãæ¤æ¹æ³æ¯çº¿ç¨å®å
¨çã</remarks> |
| | | /// <returns>å
å«å®¢æ·ç«¯IDç<see cref="IReadOnlyList{String}"/>ãå¦ææ²¡æå®¢æ·ç«¯æ³¨åï¼å表å°ä¸ºç©ºã</returns> |
| | | public IReadOnlyList<string> GetClientIds() |
| | | { |
| | | lock (_syncRoot) |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ£ç´¢ä¸æå®è®¾å¤æ è¯ç¬¦å
³èç客æ·ç«¯æ è¯ç¬¦ã |
| | | /// </summary> |
| | | /// <remarks>æ¤æ¹æ³æ¯çº¿ç¨å®å
¨çãå¦ææªæ¾å°è®¾å¤æ è¯ç¬¦ï¼æ¹æ³å°è¿ånullè䏿¯æåºå¼å¸¸ã</remarks> |
| | | /// <param name="deviceId">è¦æ£ç´¢å®¢æ·ç«¯æ è¯ç¬¦ç设å¤çå¯ä¸æ è¯ç¬¦ãä¸è½ä¸ºnullã</param> |
| | | /// <returns>䏿å®è®¾å¤æ è¯ç¬¦å
³èç客æ·ç«¯æ è¯ç¬¦ï¼å¦æä¸åå¨å
³èåè¿ånullã</returns> |
| | | public string? GetClientIdByDevice(string deviceId) |
| | | { |
| | | lock (_syncRoot) |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 弿¥åæå®è®¾å¤åéæ¶æ¯ã |
| | | /// </summary> |
| | | /// <remarks>妿æå®è®¾å¤æªæ³¨åææ æ³æ¾å°ï¼åè¿å <see langword="false"/>ã</remarks> |
| | | /// <param name="deviceId">ç®æ 设å¤çå¯ä¸æ è¯ç¬¦ãä¸è½ä¸ºnullæç©ºã</param> |
| | | /// <param name="message">è¦åéç»è®¾å¤çæ¶æ¯ãä¸è½ä¸ºnullã</param> |
| | | /// <returns>è¡¨ç¤ºå¼æ¥æä½çä»»å¡ãå¦ææ¶æ¯æååéï¼ä»»å¡ç»æä¸º <see langword="true"/>ï¼ |
| | | /// å¦å为 <see langword="false"/>ã</returns> |
| | | public Task<bool> SendToDeviceAsync(string deviceId, string message) |
| | | { |
| | | var clientId = GetClientIdByDevice(deviceId); |
| | |
| | | return SendToClientAsync(clientId, message); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// éè¿TCPè¿æ¥å¼æ¥åæå®å®¢æ·ç«¯åéå¸¦å¸§çææ¬æ¶æ¯ã |
| | | /// </summary> |
| | | /// <remarks>妿客æ·ç«¯æªè¿æ¥æä¸åå¨ï¼æ¤æ¹æ³å°è¿å <see langword="false"/> ä¸ä¸åéæ¶æ¯ã |
| | | /// æ¶æ¯å°ä¼å
使ç¨å®¢æ·ç«¯é¦éçææ¬ç¼ç è¿è¡ç¼ç ï¼å¦å使ç¨é»è®¤ç¼ç ã |
| | | /// æ¤æ¹æ³å¯¹äºåä¸å客æ·ç«¯çå¹¶åè°ç¨æ¯çº¿ç¨å®å
¨çã</remarks> |
| | | /// <param name="clientId">è¦åéæ¶æ¯å°ç客æ·ç«¯çå¯ä¸æ è¯ç¬¦ãå¿
须对åºå·²è¿æ¥ç客æ·ç«¯ã</param> |
| | | /// <param name="message">è¦åéç»å®¢æ·ç«¯çææ¬æ¶æ¯ãä¸è½ä¸ºnullã</param> |
| | | /// <returns>è¡¨ç¤ºå¼æ¥æä½çä»»å¡ãå¦ææ¶æ¯æååéï¼ä»»å¡ç»æä¸º <see langword="true"/>ï¼ |
| | | /// å¦åï¼å¦æå®¢æ·ç«¯æªè¿æ¥æä¸åå¨ï¼ç»æä¸º <see langword="false"/>ã</returns> |
| | | public async Task<bool> SendToClientAsync(string clientId, string message) |
| | | { |
| | | TcpClient? client; |
| | |
| | | try |
| | | { |
| | | var ns = client.GetStream(); |
| | | var data = enc.GetBytes((message ?? string.Empty) + "\n"); |
| | | var framedMessage = BuildFramedMessage(message); |
| | | var data = enc.GetBytes(framedMessage); |
| | | await ns.WriteAsync(data, 0, data.Length); |
| | | } |
| | | finally |
| | |
| | | return true; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 弿¥åææå·²è¿æ¥ç客æ·ç«¯åéæå®çæ¶æ¯ã |
| | | /// </summary> |
| | | /// <remarks>妿åæä¸ªå®¢æ·ç«¯åéæ¶æ¯æ¶åçé误ï¼å¼å¸¸å°è¢«æå¶å¹¶ç»§ç»åå
¶ä»å®¢æ·ç«¯å¹¿æã |
| | | /// 彿æåéæä½å®æåï¼æ¤æ¹æ³ç»æã</remarks> |
| | | /// <param name="message">è¦å¹¿æç»ææå®¢æ·ç«¯çæ¶æ¯ãä¸è½ä¸ºnullã</param> |
| | | /// <returns>è¡¨ç¤ºå¼æ¥å¹¿ææä½çä»»å¡ã</returns> |
| | | public async Task BroadcastAsync(string message) |
| | | { |
| | | List<TcpClient> clients; |
| | |
| | | |
| | | await Task.WhenAll(clients.Select(c => Task.Run(async () => |
| | | { |
| | | try { await SendAsync(c, message); } catch { } |
| | | try { await SendMessageAsync(c, message); } catch { } |
| | | }))); |
| | | } |
| | | |
| | | public static async Task SendAsync(TcpClient client, string message) |
| | | /// <summary> |
| | | /// éè¿ç½ç»æµå¼æ¥åæå®çTCP客æ·ç«¯åéå¸¦å¸§çææ¬æ¶æ¯ã |
| | | /// </summary> |
| | | /// <remarks>妿客æ·ç«¯ä¸ºnullææªè¿æ¥ï¼æ¤æ¹æ³å°ç«å³è¿åèä¸åéæ¶æ¯ã |
| | | /// æ¶æ¯å°ä½¿ç¨é
ç½®çææ¬ç¼ç è¿è¡ç¼ç å¹¶æ·»å 帧头åéè¿ç½ç»æµåéã</remarks> |
| | | /// <param name="client">è¦åéæ¶æ¯å°çTCP客æ·ç«¯ãå¿
é¡»å¤äºè¿æ¥ç¶æï¼å¦åæ¹æ³ä¸æ§è¡ä»»ä½æä½ã</param> |
| | | /// <param name="message">è¦åéç»å®¢æ·ç«¯çææ¬æ¶æ¯ãæ¶æ¯å¨ä¼ è¾åå°è¢«ç¼ç å¹¶æ·»å 帧头ã</param> |
| | | /// <returns>è¡¨ç¤ºå¼æ¥åéæä½çä»»å¡ã</returns> |
| | | private async Task SendMessageAsync(TcpClient client, string message) |
| | | { |
| | | if (client == null || !client.Connected) |
| | | { |
| | |
| | | } |
| | | |
| | | NetworkStream stream = client.GetStream(); |
| | | var data = Encoding.UTF8.GetBytes((message ?? string.Empty) + "\n"); |
| | | var framedMessage = BuildFramedMessage(message); |
| | | var data = _textEncoding.GetBytes(framedMessage); |
| | | await stream.WriteAsync(data, 0, data.Length); |
| | | } |
| | | } |
| | |
| | | { |
| | | public partial class TcpSocketServer |
| | | { |
| | | /// <summary> |
| | | /// éæ¾æå¡å¨ä½¿ç¨çææèµæºå¹¶åæ¢çå¬ä¼ å
¥è¿æ¥ã |
| | | /// </summary> |
| | | /// <remarks>å½ä¸åéè¦æå¡å¨æ¶è°ç¨æ¤æ¹æ³ï¼ä»¥ç¡®ä¿ææç¸å
³èµæºï¼å¦ç½ç»çå¬å¨å忥åè¯ï¼è¢«æ£ç¡®éæ¾ã |
| | | /// è°ç¨ <see cref="Dispose"/> åï¼æå¡å¨æ æ³éæ°å¯å¨æå次使ç¨ã</remarks> |
| | | public void Dispose() |
| | | { |
| | | _cts?.Cancel(); |
| | |
| | | using HslCommunication.Core.IMessage; |
| | | using System; |
| | | using System.Net.Sockets; |
| | | using System.Text; |
| | | using System.Text.Json; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | using WIDESEAWCS_QuartzJob; |
| | | using System.IO; |
| | | |
| | | namespace WIDESEAWCS_Tasks.SocketServer |
| | | { |
| | | public partial class TcpSocketServer |
| | | { |
| | | /// <summary> |
| | | /// 弿¥å¤çä¸å·²è¿æ¥çTCP客æ·ç«¯çéä¿¡ï¼å¤çæºå¨äººèµ·éæºä¼è¯ä¸çä¼ å
¥æ¶æ¯å客æ·ç«¯ç¶ææ´æ°ã |
| | | /// </summary> |
| | | /// <remarks>æ¤æ¹æ³ç®¡ç客æ·ç«¯è¿æ¥ççå½å¨æï¼å
æ¬è¯»åæ¶æ¯ãæ´æ°å®¢æ·ç«¯ç¶æåè°ç¨ç¸å
³äºä»¶ã |
| | | /// å½å¤çç»ææ¶ï¼å®¢æ·ç«¯åç¸å
³çç½ç»èµæºå°è¢«éæ¾ã妿å¯ç¨å¿è·³æç©ºé²è¶
æ¶éé¡¹ï¼ |
| | | /// å°åºç¨é¢å¤çåæ¶é»è¾ãäºä»¶è°ç¨æé´çå¼å¸¸å°è¢«æè·å¹¶æå¶ï¼ä»¥ç¡®ä¿ä¼è¯å¤çç鲿£æ§ã</remarks> |
| | | /// <param name="client">表示è¦å¤ççè¿ç¨è¿æ¥çTCP客æ·ç«¯ãæ¹æ³å®æåå°éæ¾æ¤å¯¹è±¡ã</param> |
| | | /// <param name="clientId">å·²è¿æ¥å®¢æ·ç«¯çå¯ä¸æ è¯ç¬¦ãç¨äºå¨æ´ä¸ªä¼è¯ä¸è·è¸ªåæ´æ°å®¢æ·ç«¯ç¶æã</param> |
| | | /// <param name="cancellationToken">å¯ç¨äºåæ¶å®¢æ·ç«¯å¤çæä½ç忶令çãå¦æè¯·æ±åæ¶ï¼æ¹æ³å°ç«å³ç»æ¢å¤çã</param> |
| | | /// <param name="robotCrane">表示ä¸å®¢æ·ç«¯å
³èçæºå¨äººèµ·éæºçå½åç¶æå¯¹è±¡ãç¨äºä¸ºæ¶æ¯å¤çåäºä»¶è°ç¨æä¾ä¸ä¸æã</param> |
| | | /// <returns>表示å¤ç客æ·ç«¯è¿æ¥ç弿¥æä½çä»»å¡ãå½å®¢æ·ç«¯æå¼è¿æ¥æè¯·æ±åæ¶æ¶ä»»å¡å®æã</returns> |
| | | public async Task HandleClientAsync(TcpClient client, string clientId, CancellationToken cancellationToken, RobotSocketState robotCrane) |
| | | { |
| | | using (client) |
| | |
| | | try |
| | | { |
| | | var ct = localCts?.Token ?? cancellationToken; |
| | | message = await reader.ReadLineAsync().WaitAsync(ct); |
| | | message = await ReceiveFullMessageAsync(networkStream, _textEncoding, ct); |
| | | //message = await reader.ReadLineAsync().WaitAsync(ct); |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | |
| | | |
| | | if (MessageReceived != null) |
| | | { |
| | | try { _ = MessageReceived.Invoke(message, false, client, robotCrane); } catch { } |
| | | try |
| | | { |
| | | // 夿æ¯å¦ä¸º JSON æ ¼å¼ |
| | | bool isJsonFormat = TryParseJsonSilent(message); |
| | | _ = MessageReceived.Invoke(message, isJsonFormat, client, robotCrane); |
| | | } |
| | | catch { } |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// å°è¯å¤çæ¥èªå®¢æ·ç«¯çè®¾å¤æ³¨å请æ±ãè¿åä¸ä¸ªå¼æç¤ºè¯¥æ¶æ¯æ¯å¦è¢«ä½ä¸ºæ³¨å请æ±å¤çã |
| | | /// </summary> |
| | | /// <remarks>å¦ææ¶æ¯æ¯ææç注å请æ±ä¸å
å«é空çè®¾å¤æ è¯ç¬¦ï¼ |
| | | /// åå°è®¾å¤ç»å®å°å®¢æ·ç«¯å¹¶åé确认信æ¯ãæ¤æ¹æ³ä¸ä¼å æ ææ¶æ¯èæåºå¼å¸¸ï¼ |
| | | /// å®ä»
è¿å falseã</remarks> |
| | | /// <param name="messageLower">客æ·ç«¯æ¶æ¯çå°åçæ¬ï¼ç¨äºå¤ææ¶æ¯æ¯å¦ä¸ºæ³¨å请æ±ã</param> |
| | | /// <param name="message">å
嫿³¨åå½ä»¤åè®¾å¤æ è¯ç¬¦çåå§å®¢æ·ç«¯æ¶æ¯ã</param> |
| | | /// <param name="clientId">åéæ³¨å请æ±ç客æ·ç«¯çå¯ä¸æ è¯ç¬¦ã</param> |
| | | /// <param name="client">ä¸å®¢æ·ç«¯éä¿¡çTCP客æ·ç«¯è¿æ¥ã</param> |
| | | /// <param name="cancellationToken">å¯ç¨äºåæ¶æ³¨åæä½ç忶令çã</param> |
| | | /// <returns>å¦ææ¶æ¯è¢«è¯å«å¹¶ä½ä¸ºæ³¨å请æ±å¤çï¼åè¿å trueï¼å¦åè¿å falseã</returns> |
| | | private bool TryHandleRegister(string messageLower, string message, string clientId, NetworkStream networkStream, CancellationToken cancellationToken) |
| | | { |
| | | if (!messageLower.StartsWith("register,")) |
| | |
| | | return true; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ´æ°å®¢æ·ç«¯ç¶æ |
| | | /// </summary> |
| | | /// <param name="clientId"></param> |
| | | /// <param name="message"></param> |
| | | private void UpdateClientStatus(string clientId, string message) |
| | | { |
| | | lock (_syncRoot) |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥æ¶æ¯å°å®¢æ·ç«¯ |
| | | /// </summary> |
| | | /// <param name="clientId"></param> |
| | | /// <param name="networkStream"></param> |
| | | /// <param name="message"></param> |
| | | /// <param name="cancellationToken"></param> |
| | | /// <returns></returns> |
| | | private async Task WriteToClientAsync(string clientId, NetworkStream networkStream, string message, CancellationToken cancellationToken) |
| | | { |
| | | SemaphoreSlim? sem = null; |
| | |
| | | if (sem != null) await sem.WaitAsync(cancellationToken); |
| | | try |
| | | { |
| | | var data = enc.GetBytes(message + "\n"); |
| | | var framedMessage = BuildFramedMessage(message); |
| | | var data = enc.GetBytes(framedMessage); |
| | | await networkStream.WriteAsync(data, 0, data.Length, cancellationToken); |
| | | } |
| | | finally |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ·»å æ¶æ¯å¸§å¤´å°¾ |
| | | /// </summary> |
| | | /// <param name="message"></param> |
| | | /// <returns></returns> |
| | | private string BuildFramedMessage(string message) |
| | | { |
| | | var header = _options.MessageHeader ?? string.Empty; |
| | | var footer = _options.MessageFooter ?? string.Empty; |
| | | return header + (message ?? string.Empty) + footer; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// JSONæ ¼å¼å°è¯è§£æï¼éé»å¤±è´¥ï¼ |
| | | /// </summary> |
| | | /// <param name="message"></param> |
| | | /// <returns></returns> |
| | | private static bool TryParseJsonSilent(string message) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(message)) return false; |
| | |
| | | try { JsonDocument.Parse(message); return true; } catch { return false; } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// utf-8 å¯è½æ§æ£æµ |
| | | /// </summary> |
| | | /// <param name="data"></param> |
| | | /// <returns></returns> |
| | | private static bool IsLikelyUtf8(byte[] data) |
| | | { |
| | | int i = 0; |
| | |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读å宿´æ¶æ¯ |
| | | /// </summary> |
| | | /// <param name="networkStream">åèæµ</param> |
| | | /// <param name="encoding">ç¼ç æ ¼å¼</param> |
| | | /// <param name="cancellationToken"></param> |
| | | /// <returns></returns> |
| | | private async Task<string?> ReceiveFullMessageAsync(NetworkStream networkStream, Encoding encoding, CancellationToken cancellationToken) |
| | | { |
| | | var header = _options.MessageHeader ?? string.Empty; |
| | | var footer = _options.MessageFooter ?? string.Empty; |
| | | |
| | | var buffer = new byte[1024]; |
| | | var builder = new StringBuilder(); |
| | | |
| | | while (true) |
| | | { |
| | | int bytesRead = await networkStream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken); |
| | | if (bytesRead <= 0) |
| | | { |
| | | if (builder.Length == 0) return null; |
| | | return string.IsNullOrEmpty(header) && string.IsNullOrEmpty(footer) ? builder.ToString() : null; |
| | | } |
| | | |
| | | builder.Append(encoding.GetString(buffer, 0, bytesRead)); |
| | | |
| | | if (string.IsNullOrEmpty(header) && string.IsNullOrEmpty(footer)) |
| | | { |
| | | if (!networkStream.DataAvailable) |
| | | { |
| | | break; |
| | | } |
| | | continue; |
| | | } |
| | | |
| | | var data = builder.ToString(); |
| | | var headerIndex = string.IsNullOrEmpty(header) ? 0 : data.IndexOf(header, StringComparison.Ordinal); |
| | | if (headerIndex < 0) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | var startIndex = headerIndex + header.Length; |
| | | var footerIndex = string.IsNullOrEmpty(footer) ? data.Length : data.IndexOf(footer, startIndex, StringComparison.Ordinal); |
| | | if (footerIndex >= 0) |
| | | { |
| | | return data.Substring(startIndex, footerIndex - startIndex); |
| | | } |
| | | } |
| | | |
| | | return builder.ToString(); |
| | | } |
| | | } |
| | | } |
| | |
| | | { |
| | | public partial class TcpSocketServer |
| | | { |
| | | /// <summary> |
| | | /// 弿¥å¯å¨TCPæå¡å¨ï¼ä½¿å
¶å¼å§æ¥åä¼ å
¥ç客æ·ç«¯è¿æ¥ã |
| | | /// </summary> |
| | | /// <remarks>妿æå¡å¨å·²å¨è¿è¡æéè¿é
ç½®ç¦ç¨ï¼æ¤æ¹æ³å°ç«å³è¿åèä¸å¯å¨æå¡å¨ã |
| | | /// åç»ç客æ·ç«¯çæ§åæ¥åæä½å¨åå°ä»»å¡ä¸è¿è¡ãæ¤æ¹æ³ä¸ä¼é»å¡è°ç¨çº¿ç¨ã</remarks> |
| | | /// <param name="cancellationToken">å¯ç¨äºè¯·æ±åæ¶æå¡å¨å¯å¨ååç»åå°æä½ç忶令çã</param> |
| | | /// <returns>è¡¨ç¤ºå¼æ¥å¯å¨æä½çä»»å¡ã彿å¡å¨å¼å§çå¬è¿æ¥æ¶ä»»å¡å®æã</returns> |
| | | public Task StartAsync(CancellationToken cancellationToken) |
| | | { |
| | | if (IsRunning || !_options.Enabled) |
| | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | //// <summary> |
| | | /// 弿¥åæ¢æå¡å¨å¹¶çå¾
æææ´»å¨å®¢æ·ç«¯è¿æ¥å®æã |
| | | /// </summary> |
| | | /// <remarks>妿æå¡å¨æªè¿è¡ï¼æ¤æ¹æ³å°ç«å³è¿åè䏿§è¡ä»»ä½æä½ã |
| | | /// æ¤æ¹æ³ç¡®ä¿ææå®¢æ·ç«¯ä»»å¡å®æåæå°æå¡å¨æ è®°ä¸ºå·²åæ¢ã</remarks> |
| | | /// <param name="cancellationToken">å¯ç¨äºå¨å®æå忶忢æä½ç忶令çã</param> |
| | | /// <returns>è¡¨ç¤ºå¼æ¥åæ¢æä½çä»»å¡ã</returns> |
| | | public async Task StopAsync(CancellationToken cancellationToken) |
| | | { |
| | | if (!IsRunning) |
| | |
| | | IsRunning = false; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æç»æ¥åä¼ å
¥çTCP客æ·ç«¯è¿æ¥ï¼ç´å°è¯·æ±åæ¶ã |
| | | /// </summary> |
| | | /// <remarks>æ¤æ¹æ³æ¨å¨åå°è¿è¡ä»¥å¤çæ°ç客æ·ç«¯è¿æ¥ã |
| | | /// 妿çå¬å¨è¢«éæ¾æéè¿æä¾ç令ç请æ±åæ¶ï¼å¾ªç¯å°éåºã</remarks> |
| | | /// <param name="cancellationToken">å¯ç¨äºè¯·æ±åæ¶æ¥å循ç¯ç令çãå½è¯·æ±åæ¶æ¶ï¼å¾ªç¯å°ç«å³ç»æ¢ã</param> |
| | | /// <returns>è¡¨ç¤ºå¼æ¥æ¥åå¾ªç¯æä½çä»»å¡ã</returns> |
| | | private async Task AcceptLoopAsync(CancellationToken cancellationToken) |
| | | { |
| | | while (!cancellationToken.IsCancellationRequested) |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// ä»å
é¨éåä¸ç§»é¤æå®æ è¯ç¬¦ç客æ·ç«¯ï¼å¹¶éæ¾ç¸å
³èµæºã |
| | | /// </summary> |
| | | /// <remarks>æ¤æ¹æ³å
³é客æ·ç«¯è¿æ¥ï¼éæ¾ä»»ä½å
³èçéï¼å¹¶ç§»é¤å¯¹å®¢æ·ç«¯çææå¼ç¨ï¼ |
| | | /// å
æ¬è®¾å¤ç»å®åç¼ç ä¿¡æ¯ãéè¿å¯¹å
é¨åæ¥å¯¹è±¡å éç¡®ä¿çº¿ç¨å®å
¨ã</remarks> |
| | | /// <param name="clientId">è¦ç§»é¤ç客æ·ç«¯çå¯ä¸æ è¯ç¬¦ãä¸è½ä¸ºnullæç©ºã</param> |
| | | private void RemoveClient(string clientId) |
| | | { |
| | | lock (_syncRoot) |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 弿¥çæ§å·²è¿æ¥ç客æ·ç«¯ï¼å¹¶æå¼è¶
è¿é
ç½®è¶
æ¶æ¶é´é²ç½®ç客æ·ç«¯è¿æ¥ã |
| | | /// </summary> |
| | | /// <remarks>æ¤æ¹æ³æç»æ£æ¥é²ç½®å®¢æ·ç«¯ï¼å¦æå
¶ä¸æ´»å¨æ¶é´è¶
è¿æå®ç空é²è¶
æ¶ï¼åæå¼è¿æ¥ã |
| | | /// çæ§å¾ªç¯å°æç»è¿è¡ï¼ç´å°éè¿æä¾ç令ç请æ±åæ¶ã</remarks> |
| | | /// <param name="cancellationToken">å¯ç¨äºè¯·æ±ç»æ¢çæ§å¾ªç¯ç忶令çã</param> |
| | | /// <returns>è¡¨ç¤ºå¼æ¥çæ§æä½çä»»å¡ã</returns> |
| | | private async Task MonitorClientsAsync(CancellationToken cancellationToken) |
| | | { |
| | | while (!cancellationToken.IsCancellationRequested) |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åºäºè¿ç¨ç»ç«¯ç¹è·åæå®TCP客æ·ç«¯çå¯ä¸æ è¯ç¬¦å符串ã |
| | | /// </summary> |
| | | /// <remarks>è¿åçæ è¯ç¬¦éç¨äºå¨æ¥å¿è®°å½æè·è¸ªåºæ¯ä¸åºå客æ·ç«¯ã |
| | | /// 妿客æ·ç«¯çè¿ç¨ç»ç«¯ç¹ä¸å¯ç¨ï¼å°çæGUID以确ä¿å¯ä¸æ§ã</remarks> |
| | | /// <param name="client">è¦è·åæ è¯ç¬¦çTCP客æ·ç«¯ãä¸è½ä¸ºnullã</param> |
| | | /// <returns>表示客æ·ç«¯è¿ç¨ç»ç«¯ç¹çå符串ï¼å¦æå¯ç¨ï¼ï¼å¦å为çæçæ°GUIDå符串ã</returns> |
| | | public static string GetClientId(TcpClient client) |
| | | { |
| | | return client.Client.RemoteEndPoint?.ToString() ?? Guid.NewGuid().ToString(); |
| | |
| | | public partial class TcpSocketServer : IDisposable |
| | | { |
| | | private readonly SocketServerOptions _options; |
| | | |
| | | /// <summary> |
| | | /// æä¾ä¸ä¸ªå¯ç¨äºåæ¥å¯¹å
å«å®ä¾ç访é®ç对象ã |
| | | /// </summary> |
| | | /// <remarks>å¨å¯¹å®ä¾å®ç°çº¿ç¨å®å
¨æä½æ¶ï¼å¯å°æ¤å¯¹è±¡ç¨ä½éå®ç®æ ãæ¤æ¨¡å¼é常ç¨äºé¿å
æ»éå¹¶ç¡®ä¿ä¸è´ç忥ã</remarks> |
| | | public readonly object _syncRoot = new(); |
| | | |
| | | private TcpListener? _listener; |
| | | |
| | | /// <summary> |
| | | /// 表示ç¨äºååºè¿è¡ä¸æä½çåæ¶è¯·æ±çåæ¶ä»¤çæºã |
| | | /// </summary> |
| | | /// <remarks>妿å½åæ²¡ææ´»å¨çåæ¶æºå¶ï¼æ¤å段å¯è½ä¸ºnullãä½¿ç¨æ¤ä»¤çæºåæ¶æ¯æåæ¶ç任塿æä½ã</remarks> |
| | | public CancellationTokenSource? _cts; |
| | | |
| | | /// <summary> |
| | | /// æä¾è¡¨ç¤ºæ´»å¨å®¢æ·ç«¯æä½çä»»å¡å表ã |
| | | /// </summary> |
| | | /// <remarks>æ¤å段ç¨äºå
é¨è·è¸ªå¼æ¥å®¢æ·ç«¯æ´»å¨ã宿¯åªè¯»çï¼ä¸åºå¨å
å«ç±»å¤é¨ç´æ¥ä¿®æ¹ã</remarks> |
| | | public readonly List<Task> _clientTasks = new(); |
| | | |
| | | /// <summary> |
| | | /// æä¾ä»å®¢æ·ç«¯æ è¯ç¬¦å°å
¶å
³èçTCP客æ·ç«¯è¿æ¥çæ å°ã |
| | | /// </summary> |
| | | /// <remarks>æ¤åå
¸å
许éè¿å¯ä¸å符串æ è¯ç¬¦è®¿é®æ´»å¨çTCP客æ·ç«¯ãå¨å¤çº¿ç¨åºæ¯ä¸ï¼å¯¹éåçä¿®æ¹åºå°å¿è¿è¡ä»¥é¿å
å¹¶åé®é¢ã</remarks> |
| | | public readonly Dictionary<string, TcpClient> _clients = new(); |
| | | |
| | | /// <summary> |
| | | /// æä¾ä»è®¾å¤æ è¯ç¬¦å°å
¶å¯¹åºç»å®å¼çæ å°ã |
| | | /// </summary> |
| | | /// <remarks>æ¤å段æ¯åªè¯»çï¼ç¨äºå
å«ç±»å
é¨ä½¿ç¨ãåºéè¿æå®çæ¹æ³æå±æ§å¯¹åå
¸è¿è¡ä¿®æ¹ä»¥ç¡®ä¿ä¸è´æ§ã</remarks> |
| | | public readonly Dictionary<string, string> _deviceBindings = new(); |
| | | |
| | | /// <summary> |
| | | /// æä¾ä»å®¢æ·ç«¯æ è¯ç¬¦å°å
¶å
³èéçæ å°ï¼ç¨äºåæ¥å¯¹å®¢æ·ç«¯ç¹å®èµæºç访é®ã |
| | | /// </summary> |
| | | /// <remarks>åå
¸ä¸çæ¯ä¸ªæ¡ç®å°ä¸ä¸ªå¯ä¸ç客æ·ç«¯IDä¸ä¸ä¸ª<see cref="SemaphoreSlim"/>å®ä¾å
³èï¼å®ç°æ¯ä¸ªå®¢æ·ç«¯ç线ç¨å®å
¨æä½ãæ¤éåç¨äºå
é¨åè°å¹¶å访é®ï¼ä¸åºç´æ¥ä¿®æ¹ã</remarks> |
| | | public readonly Dictionary<string, SemaphoreSlim> _clientLocks = new(); |
| | | |
| | | /// <summary> |
| | | /// æä¾ä»å®¢æ·ç«¯æ è¯ç¬¦å°å
¶å
³èææ¬ç¼ç çæ å°ã |
| | | /// </summary> |
| | | /// <remarks>æ¤åå
¸ç¨äºå
é¨è·è¸ªå·²è¿æ¥å®¢æ·ç«¯çç¼ç å好ãé®è¡¨ç¤ºå®¢æ·ç«¯æ è¯ç¬¦ï¼å¼æå®ç¨äºææ¬æä½ç对åº<see cref="System.Text.Encoding"/>ã</remarks> |
| | | public readonly Dictionary<string, Encoding> _clientEncodings = new(); |
| | | |
| | | /// <summary> |
| | | /// å卿¯ä¸ªå®¢æ·ç«¯æåæ´»å¨çæ¶é´æ³ï¼ä»¥å®¢æ·ç«¯æ è¯ç¬¦ä¸ºé®ã |
| | | /// </summary> |
| | | /// <remarks>æ¤å段ç¨äºå
é¨è·è¸ªå®¢æ·ç«¯æ´»å¨ãåå
¸å°å®¢æ·ç«¯æ è¯ç¬¦æ å°å°å¯¹åºçæåæ´»å¨æ¶é´ï¼UTCï¼ãç´æ¥ä¿®æ¹æ¤éåå¯è½å½±å客æ·ç«¯ä¼è¯ç®¡çé»è¾ã</remarks> |
| | | public readonly Dictionary<string, DateTime> _clientLastActive = new(); |
| | | |
| | | /// <summary> |
| | | /// æå®å
å«ç±»åä¸åç¬¦æ°æ®ä½¿ç¨çææ¬ç¼ç ã |
| | | /// </summary> |
| | | /// <remarks>ä½¿ç¨æ¤å段确å®å¤çåç¬¦æ°æ®æ¶å¦ä½ç¼ç æè§£ç ææ¬ãç¼ç å½±ååèå¦ä½è¢«è§£é为å符ï¼åä¹äº¦ç¶ã常è§çç¼ç å
æ¬UTF8ãASCIIåUnicodeã</remarks> |
| | | public readonly Encoding _textEncoding; |
| | | |
| | | /// <summary> |
| | | /// 表示èªå¨æ£æµå°çGB2312ç¼ç ï¼å¦æå¯ç¨ï¼ã |
| | | /// </summary> |
| | | /// <remarks>é常ä»è¾å
¥æ°æ®ç¡®å®ç¼ç æ¶è®¾ç½®æ¤å段ãå¦ææ£æµå¤±è´¥ææªæ§è¡æ£æµï¼å¼å¯è½ä¸ºnullã</remarks> |
| | | public readonly Encoding? _autoDetectedGb2312; |
| | | |
| | | private readonly string _logFile; |
| | | |
| | | private Task? _monitorTask; |
| | | |
| | | /// <summary> |
| | | /// ä½¿ç¨æå®çæå¡å¨é项åå§å TcpSocketServer ç±»çæ°å®ä¾ã |
| | | /// </summary> |
| | | /// <remarks>妿å¯ç¨äº AutoDetectEncoding éé¡¹ï¼æå¡å¨å°é»è®¤ä½¿ç¨ UTF-8 ç¼ç ï¼ |
| | | /// å¹¶å°è¯æ¯æ GBK ç¼ç è¿è¡èªå¨æ£æµã妿ç¼ç æ£æµå¤±è´¥ææä¾äºæ æçç¼ç åç§°ï¼ |
| | | /// å°åéä½¿ç¨ UTF-8 ç¼ç ãæ¥å¿æä»¶è·¯å¾ç± LogFilePath é项å³å®ï¼ |
| | | /// å¦ææªæå®ï¼åé»è®¤ä¸ºåºç¨ç¨åºåºç®å½ä¸ç 'socketserver.log' æä»¶ã</remarks> |
| | | /// <param name="options">奿¥åæå¡å¨çé
ç½®é项ãä¸è½ä¸º nullãæä¾ç¼ç è®¾ç½®ãæ¥å¿æä»¶è·¯å¾åèªå¨æ£æµè¡ä¸ºçé
ç½®ã</param> |
| | | public TcpSocketServer(IOptions<SocketServerOptions> options) |
| | | { |
| | | _options = options.Value; |
| | |
| | | public bool IsRunning { get; private set; } |
| | | |
| | | public event Func<string, bool, TcpClient, RobotSocketState, Task<string?>>? MessageReceived; |
| | | |
| | | public event Func<string, Task<string?>>? RobotReceived; |
| | | |
| | | private void Log(string message) |