C# 中的 Socket






4.33/5 (63投票s)
2003 年 10 月 20 日
7分钟阅读

403042

15094
C# 中 Socket 的使用简介。
引言
C# 是一个强大的网络编程工具。在 Socket 编程中,有几个步骤——比如客户端、服务器端、阻塞或同步、非阻塞或异步等。本文的第一部分将从客户端阻塞 Socket 开始。之后,第二部分将展示如何创建服务器端和非阻塞 Socket。
什么是 Socket?
Socket 就像一个文件句柄,用于打开与另一台机器通信的路径。它类似于文件 IO,也类似于串行通信。使用 Socket 编程,我们可以实现两个应用程序之间的通信。这些应用程序通常在不同的计算机上,或者在同一台计算机上。为了使两个应用程序在同一台或不同的计算机上相互通信,一个应用程序通常充当服务器,不断监听传入的请求,而另一个应用程序充当客户端,连接到服务器应用程序。
服务器应用程序可以接受或拒绝连接。如果服务器接受连接,客户端和服务器之间就可以开始对话。一旦客户端完成了它需要做的事情,它就可以关闭与服务器的连接。连接是昂贵的,因为服务器只允许有限的连接发生。在客户端具有活动连接期间,它可以向服务器发送数据和/或接收数据。
当客户端或服务器尝试建立连接时,我们必须小心。当任何一方(客户端或服务器)发送数据时,另一方应该读取数据。但是另一方如何知道数据何时到达呢?有两种选择——要么应用程序需要定期轮询数据,要么需要某种机制,使应用程序能够获得通知,并且应用程序可以在那时读取数据。Windows 是一个事件驱动系统,通知系统似乎是一个显而易见且最佳的选择,而且它确实如此。
需要相互通信的两个应用程序首先需要建立连接。如果每台机器都想建立连接,它们必须识别自己。使用机器的 IP 地址进行识别。IP 地址不过是八个八进制数字,例如 107.108.1.179。前两个八进制数字显示网络 ID,第三个和第四个八进制数字显示主机 ID。
C# 中的 Socket。
System.Net.Sockets
命名空间包含所需的类。Socket 程序主要由两部分组成。它们是
- 服务器
- 客户端
在客户端,客户端启动以与服务器建立连接。代码如下所示
Project ("{EFDR3456}") = "SocketClientProject",
"SocketClientProject.csproj", "{EFDR3456 }" EndProject
Global
GlobalSection(SolutionConfiguration) = preSolution
ConfigName.0 = Debug
ConfigName.1 = Release
EndGlobalSection
GlobalSection(ProjectDependencies) = postSolution
EndGlobalSection
GlobalSection(ProjectConfiguration) = postSolution
{EFDR3456 }.Debug.ActiveCfg = Debug|.NET
{EFDR3456 }.Debug.Build.0 = Debug|.NET
{EFDR3456 }.Release.ActiveCfg = Release|.NET
{EFDR3456 }.Release.Build.0 = Release|.NET
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EndGlobalSection
GlobalSection(ExtensibilityAddIns) = postSolution
EndGlobalSection
EndGlobaln
如果我们在 VS.NET 中运行该程序,我们将得到一个窗口,要求输入主机 IP 和端口设置。如果连接成功,它会要求输入要发送的数据。输入数据后,数据字节将被传输。它可以选择提及端口。这是一个 4 位数字,表示客户端要监听的端口。在客户端,您可以通过按 Rx 阻止日期。
指定这些参数后,我们需要连接到服务器。因此,按 Connect 将连接到服务器,按 Close 将关闭连接。要向服务器发送一些数据,请在名为 Tx 按钮旁边的字段中输入一些数据,如果您按 Rx,应用程序将阻塞,直到有数据可读。
有了这些信息,现在让我们尝试检查其背后的代码
在 .NET 中,通过 System.Net.Sockets
命名空间中的 Socket
类可以实现 Socket 编程。Socket
类有几个方法、属性和一个构造函数。第一步是创建此类的对象。由于只有一个构造函数,我们别无选择,只能使用它。
以下是如何创建 Socket
m_socListener = new Socket
(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.IP);
第一个参数是我们使用的地址族:interNetwork
。其他选项包括 Banyan Net Bios
等。
地址族是 Sockets
命名空间中定义的一个 enum
。接下来我们需要指定 Socket 类型:我们将使用可靠的双向面向连接的 Socket(流),而不是不可靠的无连接 Socket(数据报)。因此,我们显然将 stream
指定为 Socket 类型,最后我们使用 TCP/IP,所以我们将协议类型指定为 TCP。一旦我们创建了 Socket,我们就需要与服务器建立连接,因为我们使用的是面向连接的通信。要连接到远程计算机,我们需要知道连接的 IP 地址和端口。
在 .NET 中,System.Net
命名空间下有一个名为 IPEndPoint
的类,它将网络计算机表示为 IP 地址和端口号。IPEndPoint
有两个构造函数——一个接受 IP 地址和端口号,另一个接受 long
和端口号。由于我们有计算机 IP 地址,我们将使用前者
public IPEndPoint(System.Net.IPAddress address, int port);
在此处,它接受一个 IPAddress
对象。如果您检查 IPAddress
类,您会发现它有一个名为 Parse
的静态方法,该方法在给定字符串和第二个参数为端口号的情况下返回 IP 地址。一旦我们准备好端点,我们就可以使用 Socket
类的 Connect
方法连接到端点(远程服务器计算机)。
这是代码
System.Net.IPAddress ipAdd = System.Net.IPAddress.Parse("107.108.1.179");
System.Net.IPEndPoint remoteEP = new IPEndPoint (iAdd,5002);
m_socClient.Connect (remoteEP);
这三行代码将连接到 IP 为 107.108.1.179 的计算机上运行的远程主机,并在端口 5002 监听。如果服务器正在运行并已启动(监听),连接将成功。但是,如果服务器未运行,则会抛出名为 SocketException
的异常。如果您捕获该异常并检查异常的 Message
属性,在这种情况下,您将看到以下文本
“无法建立连接,因为目标计算机主动拒绝了它。”
同样,如果您已经建立连接并且服务器不知何故崩溃,如果您尝试发送数据,您将收到以下异常:“现有连接被远程主机强制关闭”。假设连接已建立,您可以使用 Socket
类的 Send
方法将数据发送到另一侧。
Send
方法有几个重载。它们都接受一个字节数组。例如,如果您想向主机发送“我在这里”,您可以使用以下调用
try
{
String szData = "I am here";
byte[] byData = System.Text.Encoding.ASCII.GetBytes(szData);
m_socClient.Send(byData);
}
catch (SocketException se)
{
MessageBox.Show ( se.Message );
}
请注意,Send
方法是阻塞的。这意味着,在数据发送或抛出异常之前,调用将阻塞。Send
有一个非阻塞版本,我们将在本文的下一部分讨论。
与 Send
类似,Socket
类上有一个 Receive
方法。您可以使用以下调用接收数据
byte [] buffer = new byte[1024];
int iRx = m_socClient.Receive (buffer);
Receive
方法再次是阻塞的。这意味着如果没有可用数据,调用将阻塞,直到一些数据到达或抛出异常。
Receive
方法的非阻塞版本比 Send
的非阻塞版本更有用,因为如果我们选择阻塞 Receive
,我们实际上是在进行轮询。没有关于数据到达的事件。这种模型不适用于严肃的应用程序。但所有这些都是本文下一部分的主题。目前,我们将使用阻塞版本。
为了使用源代码和应用程序,您需要首先运行服务器。然后您运行客户端代码。您的服务器代码将等待客户端连接。
通常系统使用较低的端口。这意味着它使用 3000 以下的端口。因此用户可以打开高于该范围的端口。