├── .gitattributes
├── .gitignore
├── 01.Coldairarrow.Util.Sockets
├── 01.Coldairarrow.Util.Sockets.csproj
├── Properties
│ └── AssemblyInfo.cs
├── SocketClient.cs
├── SocketConnection.cs
└── SocketServer.cs
├── 02.Sockets_Server
├── 02.Sockets_Server.csproj
├── App.config
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
├── 03.Sockets_Client
├── 03.Sockets_Client.csproj
├── App.config
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
├── README.md
├── SocketsDemo.sln
└── 测试截图.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/01.Coldairarrow.Util.Sockets/01.Coldairarrow.Util.Sockets.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {5DF7AFDF-6B88-42AD-B855-4EB9BF0E2495}
8 | Library
9 | Properties
10 | Coldairarrow.Util.Sockets
11 | Coldairarrow.Util.Sockets
12 | v4.0
13 | 512
14 |
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 | bin\Debug\Coldairarrow.Util.Sockets.xml
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/01.Coldairarrow.Util.Sockets/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("01.Coldairarrow.Util.Sockets")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("01.Coldairarrow.Util.Sockets")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("5df7afdf-6b88-42ad-b855-4eb9bf0e2495")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/01.Coldairarrow.Util.Sockets/SocketClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Text;
5 |
6 | namespace Coldairarrow.Util.Sockets
7 | {
8 | ///
9 | /// Socket客户端
10 | ///
11 | public class SocketClient
12 | {
13 | #region 构造函数
14 |
15 | ///
16 | /// 构造函数,连接服务器IP地址默认为本机127.0.0.1
17 | ///
18 | /// 监听的端口
19 | public SocketClient(int port)
20 | {
21 | _ip = "127.0.0.1";
22 | _port = port;
23 | }
24 |
25 | ///
26 | /// 构造函数
27 | ///
28 | /// 监听的IP地址
29 | /// 监听的端口
30 | public SocketClient(string ip, int port)
31 | {
32 | _ip = ip;
33 | _port = port;
34 | }
35 |
36 | #endregion
37 |
38 | #region 内部成员
39 |
40 | private Socket _socket = null;
41 | private string _ip = "";
42 | private int _port = 0;
43 | private bool _isRec=true;
44 | private bool IsSocketConnected()
45 | {
46 | bool part1 = _socket.Poll(1000, SelectMode.SelectRead);
47 | bool part2 = (_socket.Available == 0);
48 | if (part1 && part2)
49 | return false;
50 | else
51 | return true;
52 | }
53 |
54 | ///
55 | /// 开始接受客户端消息
56 | ///
57 | public void StartRecMsg()
58 | {
59 | try
60 | {
61 | byte[] container = new byte[1024 * 1024 * 2];
62 | _socket.BeginReceive(container, 0, container.Length, SocketFlags.None, asyncResult =>
63 | {
64 | try
65 | {
66 | int length = _socket.EndReceive(asyncResult);
67 |
68 | //马上进行下一轮接受,增加吞吐量
69 | if (length > 0 && _isRec && IsSocketConnected())
70 | StartRecMsg();
71 |
72 | if (length > 0)
73 | {
74 | byte[] recBytes = new byte[length];
75 | Array.Copy(container, 0, recBytes, 0, length);
76 |
77 | //处理消息
78 | HandleRecMsg?.BeginInvoke(recBytes, this,null,null);
79 | }
80 | else
81 | Close();
82 | }
83 | catch (Exception ex)
84 | {
85 | HandleException?.BeginInvoke(ex,null,null);
86 | Close();
87 | }
88 | }, null);
89 | }
90 | catch (Exception ex)
91 | {
92 | HandleException?.BeginInvoke(ex,null,null);
93 | Close();
94 | }
95 | }
96 |
97 | #endregion
98 |
99 | #region 外部接口
100 |
101 | ///
102 | /// 开始服务,连接服务端
103 | ///
104 | public void StartClient()
105 | {
106 | try
107 | {
108 | //实例化 套接字 (ip4寻址协议,流式传输,TCP协议)
109 | _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
110 | //创建 ip对象
111 | IPAddress address = IPAddress.Parse(_ip);
112 | //创建网络节点对象 包含 ip和port
113 | IPEndPoint endpoint = new IPEndPoint(address, _port);
114 | //将 监听套接字 绑定到 对应的IP和端口
115 | _socket.BeginConnect(endpoint, asyncResult =>
116 | {
117 | try
118 | {
119 | _socket.EndConnect(asyncResult);
120 | //开始接受服务器消息
121 | StartRecMsg();
122 |
123 | HandleClientStarted?.BeginInvoke(this,null,null);
124 | }
125 | catch (Exception ex)
126 | {
127 | HandleException?.BeginInvoke(ex,null,null);
128 | }
129 | }, null);
130 | }
131 | catch (Exception ex)
132 | {
133 | HandleException?.BeginInvoke(ex,null,null);
134 | }
135 | }
136 |
137 | ///
138 | /// 发送数据
139 | ///
140 | /// 数据字节
141 | public void Send(byte[] bytes)
142 | {
143 | try
144 | {
145 | _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult =>
146 | {
147 | try
148 | {
149 | int length = _socket.EndSend(asyncResult);
150 | HandleSendMsg?.BeginInvoke(bytes, this,null,null);
151 | }
152 | catch (Exception ex)
153 | {
154 | HandleException?.BeginInvoke(ex,null,null);
155 | }
156 | }, null);
157 | }
158 | catch (Exception ex)
159 | {
160 | HandleException?.BeginInvoke(ex,null,null);
161 | }
162 | }
163 |
164 | ///
165 | /// 发送字符串(默认使用UTF-8编码)
166 | ///
167 | /// 字符串
168 | public void Send(string msgStr)
169 | {
170 | Send(Encoding.UTF8.GetBytes(msgStr));
171 | }
172 |
173 | ///
174 | /// 发送字符串(使用自定义编码)
175 | ///
176 | /// 字符串消息
177 | /// 使用的编码
178 | public void Send(string msgStr, Encoding encoding)
179 | {
180 | Send(encoding.GetBytes(msgStr));
181 | }
182 |
183 | ///
184 | /// 传入自定义属性
185 | ///
186 | public object Property { get; set; }
187 |
188 | ///
189 | /// 关闭与服务器的连接
190 | ///
191 | public void Close()
192 | {
193 | try
194 | {
195 | _isRec = false;
196 | _socket.Disconnect(false);
197 | HandleClientClose?.BeginInvoke(this,null,null);
198 | }
199 | catch (Exception ex)
200 | {
201 | HandleException?.BeginInvoke(ex,null,null);
202 | }
203 | finally
204 | {
205 | _socket.Dispose();
206 | GC.Collect();
207 | }
208 | }
209 |
210 | #endregion
211 |
212 | #region 事件处理
213 |
214 | ///
215 | /// 客户端连接建立后回调
216 | ///
217 | public Action HandleClientStarted { get; set; }
218 |
219 | ///
220 | /// 处理接受消息的委托
221 | ///
222 | public Action HandleRecMsg { get; set; }
223 |
224 | ///
225 | /// 客户端连接发送消息后回调
226 | ///
227 | public Action HandleSendMsg { get; set; }
228 |
229 | ///
230 | /// 客户端连接关闭后回调
231 | ///
232 | public Action HandleClientClose { get; set; }
233 |
234 | ///
235 | /// 异常处理程序
236 | ///
237 | public Action HandleException { get; set; }
238 |
239 | #endregion
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/01.Coldairarrow.Util.Sockets/SocketConnection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Sockets;
3 | using System.Text;
4 |
5 | namespace Coldairarrow.Util.Sockets
6 | {
7 | ///
8 | /// Socket连接,双向通信
9 | ///
10 | public class SocketConnection
11 | {
12 | #region 构造函数
13 |
14 | ///
15 | /// 构造函数
16 | ///
17 | /// 维护的Socket对象
18 | /// 维护此连接的服务对象
19 | public SocketConnection(Socket socket,SocketServer server)
20 | {
21 | _socket = socket;
22 | _server = server;
23 | }
24 |
25 | #endregion
26 |
27 | #region 私有成员
28 |
29 | private readonly Socket _socket;
30 | private bool _isRec=true;
31 | private SocketServer _server = null;
32 | private bool IsSocketConnected()
33 | {
34 | bool part1 = _socket.Poll(1000, SelectMode.SelectRead);
35 | bool part2 = (_socket.Available == 0);
36 | if (part1 && part2)
37 | return false;
38 | else
39 | return true;
40 | }
41 |
42 | #endregion
43 |
44 | #region 外部接口
45 |
46 | ///
47 | /// 开始接受客户端消息
48 | ///
49 | public void StartRecMsg()
50 | {
51 | try
52 | {
53 | byte[] container = new byte[1024 * 1024 * 4];
54 | _socket.BeginReceive(container, 0, container.Length, SocketFlags.None, asyncResult =>
55 | {
56 | try
57 | {
58 | int length = _socket.EndReceive(asyncResult);
59 |
60 | //马上进行下一轮接受,增加吞吐量
61 | if (length > 0 && _isRec && IsSocketConnected())
62 | StartRecMsg();
63 |
64 | if (length > 0)
65 | {
66 | byte[] recBytes = new byte[length];
67 | Array.Copy(container, 0, recBytes, 0, length);
68 | try
69 | {
70 | //处理消息
71 | HandleRecMsg?.BeginInvoke(recBytes, this, _server, null, null);
72 | }
73 | catch (Exception ex)
74 | {
75 | HandleException?.Invoke(ex);
76 | }
77 | }
78 | else
79 | Close();
80 | }
81 | catch (Exception ex)
82 | {
83 | HandleException?.BeginInvoke(ex,null,null);
84 | Close();
85 | }
86 | }, null);
87 | }
88 | catch (Exception ex)
89 | {
90 | HandleException?.BeginInvoke(ex,null,null);
91 | Close();
92 | }
93 | }
94 |
95 | ///
96 | /// 发送数据
97 | ///
98 | /// 数据字节
99 | public void Send(byte[] bytes)
100 | {
101 | try
102 | {
103 | _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult =>
104 | {
105 | try
106 | {
107 | int length = _socket.EndSend(asyncResult);
108 | HandleSendMsg?.BeginInvoke(bytes, this, _server,null,null);
109 | }
110 | catch (Exception ex)
111 | {
112 | HandleException?.BeginInvoke(ex,null,null);
113 | }
114 | }, null);
115 | }
116 | catch (Exception ex)
117 | {
118 | HandleException?.BeginInvoke(ex,null,null);
119 | }
120 | }
121 |
122 | ///
123 | /// 发送字符串(默认使用UTF-8编码)
124 | ///
125 | /// 字符串
126 | public void Send(string msgStr)
127 | {
128 | Send(Encoding.UTF8.GetBytes(msgStr));
129 | }
130 |
131 | ///
132 | /// 发送字符串(使用自定义编码)
133 | ///
134 | /// 字符串消息
135 | /// 使用的编码
136 | public void Send(string msgStr,Encoding encoding)
137 | {
138 | Send(encoding.GetBytes(msgStr));
139 | }
140 |
141 | ///
142 | /// 传入自定义属性
143 | ///
144 | public object Property { get; set; }
145 |
146 | ///
147 | /// 关闭当前连接
148 | ///
149 | public void Close()
150 | {
151 | try
152 | {
153 | _isRec = false;
154 | _socket.Disconnect(false);
155 | _server.RemoveConnection(this);
156 | HandleClientClose?.BeginInvoke(this, _server,null,null);
157 | }
158 | catch (Exception ex)
159 | {
160 | HandleException?.BeginInvoke(ex,null,null);
161 | }
162 | finally
163 | {
164 | _socket.Dispose();
165 | GC.Collect();
166 | }
167 | }
168 |
169 | #endregion
170 |
171 | #region 事件处理
172 |
173 | ///
174 | /// 客户端连接接受新的消息后调用
175 | ///
176 | public Action HandleRecMsg { get; set; }
177 |
178 | ///
179 | /// 客户端连接发送消息后回调
180 | ///
181 | public Action HandleSendMsg { get; set; }
182 |
183 | ///
184 | /// 客户端连接关闭后回调
185 | ///
186 | public Action HandleClientClose { get; set; }
187 |
188 | ///
189 | /// 异常处理程序
190 | ///
191 | public Action HandleException { get; set; }
192 |
193 | #endregion
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/01.Coldairarrow.Util.Sockets/SocketServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Net.Sockets;
6 | using System.Threading;
7 |
8 | namespace Coldairarrow.Util.Sockets
9 | {
10 | ///
11 | /// Socket服务端
12 | ///
13 | public class SocketServer
14 | {
15 | #region 构造函数
16 |
17 | ///
18 | /// 构造函数
19 | ///
20 | /// 监听的IP地址
21 | /// 监听的端口
22 | public SocketServer(string ip, int port)
23 | {
24 | _ip = ip;
25 | _port = port;
26 | }
27 |
28 | ///
29 | /// 构造函数,监听IP地址默认为本机0.0.0.0
30 | ///
31 | /// 监听的端口
32 | public SocketServer(int port)
33 | {
34 | _ip = "0.0.0.0";
35 | _port = port;
36 | }
37 |
38 | #endregion
39 |
40 | #region 内部成员
41 |
42 | private Socket _socket { get; set; } = null;
43 | private string _ip { get; set; } = "";
44 | private int _port { get; set; } = 0;
45 | private bool _isListen { get; set; } = true;
46 | private void StartListen()
47 | {
48 | try
49 | {
50 | _socket.BeginAccept(asyncResult =>
51 | {
52 | try
53 | {
54 | Socket newSocket = _socket.EndAccept(asyncResult);
55 |
56 | //马上进行下一轮监听,增加吞吐量
57 | if (_isListen)
58 | StartListen();
59 |
60 | SocketConnection newConnection = new SocketConnection(newSocket, this)
61 | {
62 | HandleRecMsg = HandleRecMsg == null ? null : new Action(HandleRecMsg),
63 | HandleClientClose = HandleClientClose == null ? null : new Action(HandleClientClose),
64 | HandleSendMsg = HandleSendMsg == null ? null : new Action(HandleSendMsg),
65 | HandleException = HandleException == null ? null : new Action(HandleException)
66 | };
67 |
68 | newConnection.StartRecMsg();
69 | AddConnection(newConnection);
70 | HandleNewClientConnected?.BeginInvoke(this, newConnection, null, null);
71 | }
72 | catch (Exception ex)
73 | {
74 | HandleException?.BeginInvoke(ex, null, null);
75 | }
76 | }, null);
77 | }
78 | catch (Exception ex)
79 | {
80 | HandleException?.BeginInvoke(ex, null, null);
81 | }
82 | }
83 | private LinkedList _clientList { get; } = new LinkedList();
84 |
85 | #endregion
86 |
87 | #region 外部接口
88 |
89 | ///
90 | /// 开始服务,监听客户端
91 | ///
92 | public void StartServer()
93 | {
94 | try
95 | {
96 | //实例化套接字(ip4寻址协议,流式传输,TCP协议)
97 | _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
98 | //创建ip对象
99 | IPAddress address = IPAddress.Parse(_ip);
100 | //创建网络节点对象包含ip和port
101 | IPEndPoint endpoint = new IPEndPoint(address, _port);
102 | //将 监听套接字绑定到 对应的IP和端口
103 | _socket.Bind(endpoint);
104 | //设置监听队列长度为Int32最大值(同时能够处理连接请求数量)
105 | _socket.Listen(int.MaxValue);
106 | //开始监听客户端
107 | StartListen();
108 | HandleServerStarted?.BeginInvoke(this, null, null);
109 | }
110 | catch (Exception ex)
111 | {
112 | HandleException?.BeginInvoke(ex, null, null);
113 | }
114 | }
115 |
116 | ///
117 | /// 维护客户端列表的读写锁
118 | ///
119 | public ReaderWriterLockSlim RWLock_ClientList { get; } = new ReaderWriterLockSlim();
120 |
121 | ///
122 | /// 关闭指定客户端连接
123 | ///
124 | /// 指定的客户端连接
125 | public void CloseConnection(SocketConnection theConnection)
126 | {
127 | theConnection.Close();
128 | }
129 |
130 | ///
131 | /// 添加客户端连接
132 | ///
133 | /// 需要添加的客户端连接
134 | public void AddConnection(SocketConnection theConnection)
135 | {
136 | RWLock_ClientList.EnterWriteLock();
137 | try
138 | {
139 | _clientList.AddLast(theConnection);
140 | }
141 | finally
142 | {
143 | RWLock_ClientList.ExitWriteLock();
144 | }
145 | }
146 |
147 | ///
148 | /// 删除指定的客户端连接
149 | ///
150 | /// 指定的客户端连接
151 | public void RemoveConnection(SocketConnection theConnection)
152 | {
153 | RWLock_ClientList.EnterWriteLock();
154 | try
155 | {
156 | _clientList.Remove(theConnection);
157 | }
158 | finally
159 | {
160 | RWLock_ClientList.ExitWriteLock();
161 | }
162 | }
163 |
164 | ///
165 | /// 通过条件获取客户端连接列表
166 | ///
167 | /// 筛选条件
168 | ///
169 | public IEnumerable GetConnectionList(Func predicate)
170 | {
171 | RWLock_ClientList.EnterReadLock();
172 | try
173 | {
174 | return _clientList.Where(predicate);
175 | }
176 | finally
177 | {
178 | RWLock_ClientList.ExitReadLock();
179 | }
180 | }
181 |
182 | ///
183 | /// 获取所有客户端连接列表
184 | ///
185 | ///
186 | public IEnumerable GetConnectionList()
187 | {
188 | return _clientList;
189 | }
190 |
191 | ///
192 | /// 寻找特定条件的客户端连接
193 | ///
194 | /// 筛选条件
195 | ///
196 | public SocketConnection GetTheConnection(Func predicate)
197 | {
198 | RWLock_ClientList.EnterReadLock();
199 | try
200 | {
201 | return _clientList.Where(predicate).FirstOrDefault();
202 | }
203 | finally
204 | {
205 | RWLock_ClientList.ExitReadLock();
206 | }
207 | }
208 |
209 | ///
210 | /// 获取客户端连接数
211 | ///
212 | ///
213 | public int GetConnectionCount()
214 | {
215 | RWLock_ClientList.EnterReadLock();
216 | try
217 | {
218 | return _clientList.Count;
219 | }
220 | finally
221 | {
222 | RWLock_ClientList.ExitReadLock();
223 | }
224 | }
225 |
226 | #endregion
227 |
228 | #region 公共事件
229 |
230 | ///
231 | /// 异常处理程序
232 | ///
233 | public Action HandleException { get; set; }
234 |
235 | #endregion
236 |
237 | #region 服务端事件
238 |
239 | ///
240 | /// 服务启动后执行
241 | ///
242 | public Action HandleServerStarted { get; set; }
243 |
244 | ///
245 | /// 当新客户端连接后执行
246 | ///
247 | public Action HandleNewClientConnected { get; set; }
248 |
249 | ///
250 | /// 服务端关闭客户端后执行
251 | ///
252 | public Action HandleCloseClient { get; set; }
253 |
254 | #endregion
255 |
256 | #region 客户端连接事件
257 |
258 | ///
259 | /// 客户端连接接受新的消息后调用
260 | ///
261 | public Action HandleRecMsg { get; set; }
262 |
263 | ///
264 | /// 客户端连接发送消息后回调
265 | ///
266 | public Action HandleSendMsg { get; set; }
267 |
268 | ///
269 | /// 客户端连接关闭后回调
270 | ///
271 | public Action HandleClientClose { get; set; }
272 |
273 | #endregion
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/02.Sockets_Server/02.Sockets_Server.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {9A6B2C8C-913E-49E3-9245-99FEB3962108}
8 | Exe
9 | _02.Sockets_Server
10 | 02.Sockets_Server
11 | v4.5
12 | 512
13 |
14 |
15 | AnyCPU
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | AnyCPU
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {5df7afdf-6b88-42ad-b855-4eb9bf0e2495}
53 | 01.Coldairarrow.Util.Sockets
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/02.Sockets_Server/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/02.Sockets_Server/Program.cs:
--------------------------------------------------------------------------------
1 | using Coldairarrow.Util.Sockets;
2 | using System;
3 | using System.Text;
4 |
5 | namespace Console_Server
6 | {
7 | class Program
8 | {
9 | static void Main(string[] args)
10 | {
11 | //创建服务器对象,默认监听本机0.0.0.0,端口12345
12 | SocketServer server = new SocketServer(12345);
13 |
14 | //处理从客户端收到的消息
15 | server.HandleRecMsg = new Action((bytes, client, theServer) =>
16 | {
17 | string msg = Encoding.UTF8.GetString(bytes);
18 | Console.WriteLine($"收到消息:{msg}");
19 | });
20 |
21 | //处理服务器启动后事件
22 | server.HandleServerStarted = new Action(theServer =>
23 | {
24 | Console.WriteLine("服务已启动************");
25 | });
26 |
27 | //处理新的客户端连接后的事件
28 | server.HandleNewClientConnected = new Action((theServer, theCon) =>
29 | {
30 | Console.WriteLine($@"一个新的客户端接入,当前连接数:{theServer.GetConnectionCount()}");
31 | });
32 |
33 | //处理客户端连接关闭后的事件
34 | server.HandleClientClose = new Action((theCon, theServer) =>
35 | {
36 | Console.WriteLine($@"一个客户端关闭,当前连接数为:{theServer.GetConnectionCount()}");
37 | });
38 |
39 | //处理异常
40 | server.HandleException = new Action(ex =>
41 | {
42 | Console.WriteLine(ex.Message);
43 | });
44 |
45 | //服务器启动
46 | server.StartServer();
47 |
48 | while (true)
49 | {
50 | Console.WriteLine("输入:quit,关闭服务器");
51 | string op = Console.ReadLine();
52 | if (op == "quit")
53 | break;
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/02.Sockets_Server/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("02.Sockets_Server")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("02.Sockets_Server")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("9a6b2c8c-913e-49e3-9245-99feb3962108")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
33 | // 方法是按如下所示使用“*”: :
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/03.Sockets_Client/03.Sockets_Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {5844268A-DE58-4192-9989-D7A22D2ACCFA}
8 | Exe
9 | _03.Sockets_Client
10 | 03.Sockets_Client
11 | v4.5
12 | 512
13 |
14 |
15 | AnyCPU
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | AnyCPU
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {5df7afdf-6b88-42ad-b855-4eb9bf0e2495}
53 | 01.Coldairarrow.Util.Sockets
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/03.Sockets_Client/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/03.Sockets_Client/Program.cs:
--------------------------------------------------------------------------------
1 | using Coldairarrow.Util.Sockets;
2 | using System;
3 | using System.Text;
4 |
5 | namespace Console_Client
6 | {
7 | class Program
8 | {
9 | static void Main(string[] args)
10 | {
11 | //创建客户端对象,默认连接本机127.0.0.1,端口为12345
12 | SocketClient client = new SocketClient(12345);
13 |
14 | //绑定当收到服务器发送的消息后的处理事件
15 | client.HandleRecMsg = new Action((bytes, theClient) =>
16 | {
17 | string msg = Encoding.UTF8.GetString(bytes);
18 | Console.WriteLine($"收到消息:{msg}");
19 | });
20 |
21 | //绑定向服务器发送消息后的处理事件
22 | client.HandleSendMsg = new Action((bytes, theClient) =>
23 | {
24 | string msg = Encoding.UTF8.GetString(bytes);
25 | Console.WriteLine($"向服务器发送消息:{msg}");
26 | });
27 |
28 | //开始运行客户端
29 | client.StartClient();
30 |
31 | while (true)
32 | {
33 | Console.WriteLine("输入:quit关闭客户端,输入其它消息发送到服务器");
34 | string str = Console.ReadLine();
35 | if (str == "quit")
36 | {
37 | client.Close();
38 | break;
39 | }
40 | else
41 | {
42 | client.Send(str);
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/03.Sockets_Client/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("03.Sockets_Client")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("03.Sockets_Client")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("5844268a-de58-4192-9989-d7a22d2accfa")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
33 | // 方法是按如下所示使用“*”: :
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sockets
2 | A very helpful C# Sockets framework
3 |
4 | How to use it is here:http://www.cnblogs.com/coldairarrow/p/7501645.html
5 |
6 | 如何使用?
7 | 框架核心类:
8 | SocketServer//Socket服务端
9 | SocketConnection//Socket连接对象,双向通信
10 | SocketClient//Socket客户端
11 | 1、建立连接
12 | 服务端:
13 | SocketServer server = new SocketServer(12345);//默认监听地址0.0.0.0 端口12345,构造函数重载可以修改
14 | server.StartServer();
15 | 客户端:
16 | SocketClient client = new SocketClient(12345);//默认连接地址127.0.0.1 端口12345,构造函数重载可以修改
17 | client.StartClient();
18 | 2、消息收发
19 | 服务端:
20 | 服务端主要就是维护一个客户端连接队列,每当新的客户端连接到服务端时,都会将新的连接对象添加到连接队列中。
21 | 因此,服务端要向客户端发送消息,必须先找到需要发送消息的连接对象。
22 | 那么才能如何找到需要发送消息的连接呢?
23 | 思路:当我们要寻找某些东西的时候,肯定需要某些东西的特征,比如说我们要确认一个人的身份,我们只需要知道这个人的身份证(黑的不算),那么我们就可以轻易的知道这个人的身份信息了。同样道理,我已经预先给SocketConnection开放了一个自定义属性Property,其类型为object,也就是说,你可以传字符串,也可以传自定义对象,这个Property就可以作为当前连接的身份标志了,当连接拥有身份标志之后,就可以通过Lambda表达式查询出来(不会的自己去补充基础),服务端调用GetConnectionList方法,传入筛选条件,即可找到符合条件的IEnumerable,也可以调用GetTheConnection方法,传入筛选条件,找到符合条件的一个SocketConnection,使用示例如下:
24 | var theConnection= server.GetTheConnection(x =>
25 | {
26 | var Id = (string)x.Property;
27 | return Id == "Admin";
28 | });
29 |
30 | 看代码吃力的,请补充基础
31 |
32 | 发送消息:
33 | theConnection.Send("Hello World!");//默认UTF-8编码格式发送字符串,有重载方法,不详解了
34 | 客户端:
35 | 发送消息:
36 | client.Send("OK!");//默认UTF-8编码格式发送字符串,有重载方法,不详解了
37 |
38 | 事件处理:
39 | 客户端连接到服务端之后,双方肯定要进行通信,也就是收发数据,这里我只讲最常用的事件
40 | 1、新的客户端连接到服务端时触发(可以选择这个时候给对应的SocketConnection传入身份标识Property)
41 | server.HandleNewClientConnected = new Action((theServer,theCon) =>
42 | {
43 | theCon.Property = "Admin";//身份标志,也可以传别的对象,自己定义,用的时候强制转下(不懂,百度:多态)
44 | Console.WriteLine($@"当前连接数:{theServer.GetConnectionCount()}");
45 | });
46 | 2、服务端接收到客户端发送的消息时触发
47 | //bytes为收到的数据(字节数组),client为对应的SocketConnection,theServer为维护连接的服务对象
48 | server.HandleRecMsg = new Action((bytes,client,theServer)=>
49 | {
50 | string msg = Encoding.UTF8.GetString(bytes);
51 | client.Send($"服务端已收到收到消息:{msg}");
52 | Console.WriteLine($"收到消息:{msg}");
53 | });
54 | 3、客户端端接收到客户端发送的消息时触发
55 | client.HandleRecMsg = new Action((bytes, theClient) =>
56 | {
57 | string msg = Encoding.UTF8.GetString(bytes);
58 | Console.WriteLine($"收到消息:{msg}");
59 | });
60 |
61 | 最后:其它还有很多操作,请看三个类的外部接口
62 |
--------------------------------------------------------------------------------
/SocketsDemo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26730.10
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01.Coldairarrow.Util.Sockets", "01.Coldairarrow.Util.Sockets\01.Coldairarrow.Util.Sockets.csproj", "{5DF7AFDF-6B88-42AD-B855-4EB9BF0E2495}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02.Sockets_Server", "02.Sockets_Server\02.Sockets_Server.csproj", "{9A6B2C8C-913E-49E3-9245-99FEB3962108}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "03.Sockets_Client", "03.Sockets_Client\03.Sockets_Client.csproj", "{5844268A-DE58-4192-9989-D7A22D2ACCFA}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {5DF7AFDF-6B88-42AD-B855-4EB9BF0E2495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {5DF7AFDF-6B88-42AD-B855-4EB9BF0E2495}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {5DF7AFDF-6B88-42AD-B855-4EB9BF0E2495}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {5DF7AFDF-6B88-42AD-B855-4EB9BF0E2495}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {9A6B2C8C-913E-49E3-9245-99FEB3962108}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {9A6B2C8C-913E-49E3-9245-99FEB3962108}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {9A6B2C8C-913E-49E3-9245-99FEB3962108}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {9A6B2C8C-913E-49E3-9245-99FEB3962108}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {5844268A-DE58-4192-9989-D7A22D2ACCFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {5844268A-DE58-4192-9989-D7A22D2ACCFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {5844268A-DE58-4192-9989-D7A22D2ACCFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {5844268A-DE58-4192-9989-D7A22D2ACCFA}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {7E5390DD-1DCB-4059-AB32-F3B9B84A377F}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/测试截图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Coldairarrow/Sockets/29c7e8ca9440147a90193d59bb9f89d19a1d152c/测试截图.png
--------------------------------------------------------------------------------