C++/CLI程序进程之间的通讯

发表于:2007-05-25来源:作者:点击数: 标签:程序间的C++通讯CLI
现在,把大型软件项目分解为一些相交互的小程序似乎变得越来越普遍,程序各部分之间的通讯可使用某种类型的通讯协议,这些程序可能运行在不同的机器上、不同的操作系统中、以不同的语言编写,但也有可能只在同一台机器上,实际上,这些程序可看成是同一程序
 现在,把大型软件项目分解为一些相交互的小程序似乎变得越来越普遍,程序各部分之间的通讯可使用某种类型的通讯协议,这些程序可能运行在不同的机器上、不同的操作系统中、以不同的语言编写,但也有可能只在同一台机器上,实际上,这些程序可看成是同一程序中的不同线程。而本文主要讨论C++/CLI程序间的通讯,当然,在此是讨论进程间通讯,而不是网络通讯。

  简介

  试想一个包含数据库查询功能的应用,通常有一个被称为服务端的程序,等待另一个被称为客户端程序发送请求,当接收到请求时,服务端执行相应功能,并把结果(或者错误信息)返回给客户端。在许多情况中,有着多个客户端,所有的请求都会在同一时间发送到同一服务端,这就要求服务端程序要更加高级、完善。

  在某些针对此任务的环境中,服务端程序可能只是众多程序中的一个程序,其他可能也是服务端或者客户端程序,实际上,如果我们的数据库服务端需要访问不存在于本机的文件,那么它就可能成为其他某个文件服务器的一个客户端。一个程序中可能会有一个服务线程及一个或多个客户线程,因此,我们需小心使用客户端及服务端这个术语,虽然它们表达了近似的抽象含义,但在具体实现上却大不相同。从一般的观点来看,客户端即为服务端所提供服务的"消费者",而服务端也能成为其他某些服务的客户端。

  服务端套接字

  让我们从一个具体有代表性的服务端程序开始(请看例1),此程序等待客户端发送一对整数,把它们相加之后返回结果给客户端。

  例1:

clearcase/" target="_blank" >cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
using namespace System;
using namespace System::IO;
using namespace System::Net;
using namespace System::Net::Sockets;

int main(array<String^>^ argv)
{
if (argv->Length != 1)
{
Console::WriteLine("Usage: Server port");
Environment::Exit(1);
}

int port = 0;

try
{
port = Int32::Parse(argv[0]);
}
catch (FormatException^ e)
{
Console::WriteLine("Port number {0} is ill-formed", argv[0]);
Environment::Exit(2);
}

/*1*/ if (port < IPEndPoint::MinPort || port > IPEndPoint::MaxPort)
{
Console::WriteLine("Port number must be in the range {0}-{1}",
IPEndPoint::MinPort, IPEndPoint::MaxPort);
Environment::Exit(3);
}

/*2*/ IPAddress^ ipAddress =
Dns::GetHostEntry(Dns::GetHostName())->AddressList[0];
/*3*/ IPEndPoint^ ipEndpoint = gcnew IPEndPoint(ipAddress, port);

/*4*/ Socket^ listenerSocket = gcnew Socket(AddressFamily::InterNetwork,
SocketType::Stream, ProtocolType::Tcp);

/*5*/ listenerSocket->Bind(ipEndpoint);

/*6*/ listenerSocket->Listen(1);
/*7*/ Console::WriteLine("Server listener blocking status is {0}",
listenerSocket->Blocking);

/*8*/ Socket^ serverSocket = listenerSocket->Accept();
Console::WriteLine("New connection accepted");
/*9*/ listenerSocket->Close();

/*10*/ NetworkStream^.netStream = gcnew NetworkStream(serverSocket);
/*11*/ BinaryReader^ br = gcnew BinaryReader(netStream);
/*12*/ BinaryWriter^ bw = gcnew BinaryWriter(netStream);

try
{
int value1, value2;
int result;

while (true)
{
/*13*/ value1 = br->ReadInt32();
/*14*/ value2 = br->ReadInt32();
Console::Write("Received values {0,3} and {1,3}",
value1, value2);

result = value1 + value2;
/*15*/ bw->Write(result);
Console::WriteLine(", sent result {0,3}", result);
}
}
/*16*/ catch (EndOfStreamException^ e)
{
}
/*17*/ catch (IOException^ e)
{
Console::WriteLine("IOException {0}", e);
}

/*18*/ serverSocket->Shutdown(SocketShutdown::Both);
/*19*/ serverSocket->Close();
/*20*/ netStream->Close();
Console::WriteLine("Shutting down server");
}

  此处与套接字相关的功能由命名空间System::Net和System::Net::Sockets提供,并且需要在生成期间引用System.dll程序集。另外,因为通过套接字的通讯涉及到流,所以还要用到System:IO机制。

  当程序执行时,服务端需要知道其用来监听客户端连接请求的端口号,在此,这个整数值通过命令行参数提供。一般来说,端口号在0-65535范围内,而0-1023保留给特定的用途,因此,服务端可用的端口号就为1024-65535。

  在标号1中,通过IPEndPoint类中的MinPort和MaxPort这两个公共静态字段,就可得到特定系统上可用的端口范围。

  而在标号2中,可得到我们自己的主机名,并解析到一个IpHostEntry,可从中取得本机的IP地址。接下来在标号3中,用IP地址和端口号创建了一个IPEndPoint对象,其可为某个连接提供某种服务。

  在标号4中,创建了一个Internet传输服务托管版本的套接字,一旦它被创建,就应通过Bind函数(标号5)绑定到一个特定的端点。接下来,套接字声明其已经开始服务,并监听连接请求(标号6)。传递给Listen的参数表明了请求队列中连接挂起的长度,因为我们只有一个客户端,所以在此1就足够了。

  套接字默认以阻塞模式创建,如标号7中所示,这意味着,它会一直等待连接请求。

  当从客户端接收到连接请求时,阻塞的套接字就会被唤醒,通过调用Accept(如标号8),接受请求并创建另一个套接字,并通过此套接字来与客户端通讯。我们看到,此时的服务端有两个套接字:一个用于监听客户连接请求,而另一个用于与连接的客户端通讯。当然,一个服务端在同一时间,可与多个客户端进行连接,且每个客户端都有一个套接字。

  在这个简单的例子中,我们只关心请求连接的第一个客户端,一旦连接上了,便可关闭此监听连接请求的套接字(参见标号9)。
在标号10-12中,我们用最近连接的套接字,建立了一个NetworkStream,连同两个读写函数一起,便可以从套接字接收请求,并返回结果。
服务端在此无限循环,读入一对整数,计算它们的和,并把结果返回给客户端。当服务端探测到输入流中的文件结束标志时(由客户端关闭了套接字),会抛出EndOfStreamException异常,并关闭I/O流和套接字,服务结束。

  标号18中的Socket::ShutDown调用将同时关闭套接字上的接收和发送功能,因为我们的服务端只需告之一个客户端它的关闭,所以此函数调用有点多余,但是,在服务端要过早地结束的某些情况下,这种做法还是有用的。

  为何要捕捉IOException异常的原因在标号17中,在此是为了处理客户端在关闭套接字之前的过早结束。
 客户端套接字

  现在,让我们来看一下客户端程序(参见例2)。在连接到服务端之后,客户端将发送一对随机的整数,并且在发送下一对之前等待返回的结果。此处我们所看到的是服务端与客户端的同步通讯,客户端在接收到前一对值的结果之前,是不会发送另一对新值的。

  例2:

using namespace System;
using namespace System::IO;
using namespace System::Net;
using namespace System::Net::Sockets;
using namespace System::Threading;

int main(array<String^>^ argv)
{
 if (argv->Length != 2)
 {
  Console::WriteLine("Usage: Client port message-count");
  Environment::Exit(1);
 }

 int port = 0;

 try
 {
  port = Int32::Parse(argv[0]);
 }
 catch (FormatException^ e)
 {
  Console::WriteLine("Port number {0} is ill-formed", argv[0]);
  Environment::Exit(2);
 }

 if (port < IPEndPoint::MinPort || port > IPEndPoint::MaxPort)
 {
  Console::WriteLine("Port number must be in the range {0}-{1}",IPEndPoint::MinPort, IPEndPoint::MaxPort);
  Environment::Exit(3);
 }

 int messageCount = 0;

 try
 {
  messageCount = Int32::Parse(argv[1]);
 }
 catch (FormatException^ e)
 {
  Console::WriteLine("Message count {0} is ill-formed", argv[1]);
  Environment::Exit(4);
 }

 IPAddress^ ipAddress = nullptr;
 try
 {
  /*1*/ ipAddress = Dns::GetHostEntry(Dns::GetHostName())->AddressList[0];
  /*2*/ IPEndPoint^ ipEndpoint = gcnew IPEndPoint(ipAddress, port);

  /*3*/ Socket^ clientSocket = gcnew Socket(AddressFamily::InterNetwork,
  SocketType::Stream, ProtocolType::Tcp);

  /*4*/ clientSocket->Connect(ipEndpoint);

  NetworkStream^ netStream = gcnew NetworkStream(clientSocket);
  BinaryReader^ br = gcnew BinaryReader(netStream);
  BinaryWriter^ bw = gcnew BinaryWriter(netStream);

  int value1, value2;
  int result;

  Random^ random = gcnew Random;
  (int i = 1; i <= messageCount; ++i)
  {
   /*5*/ value1 = static_cast<int>(random->NextDouble() * 100);
   /*6*/ value2 = static_cast<int>(random->NextDouble() * 100);
 
   /*7*/ bw->Write(value1);
   /*8*/ bw->Write(value2);
   Console::Write("Sent values {0,3} and {1,3}",value1, value2);
 
   /*9*/ result = br->ReadInt32();
   Console::WriteLine(", received result {0,3}", result);
   /*10*/ Thread::Sleep(3000);
  }

  /*11*/ clientSocket->Shutdown(SocketShutdown::Both);
  Console::WriteLine("Notified server we're shutting down");
  /*12*/ clientSocket->Close();
  /*13*/ netStream->Close();
  Console::WriteLine("Shutting down client");
 }
 /*14*/ catch (SocketException^ e)
 {
  Console::WriteLine("Request to connect to {0} on port {1} failed"+ "\nbecause of {2}", ipAddress, port, e);
  Environment::Exit(5);
 }
}

  如同服务端一样,客户端取得一个IP地址,把它与端口号绑定以生成一个IPEndPoint,并连接到服务端,而服务端在此之前一直处于阻塞监听模式。

  在每一个发送与接收操作之间,我们有意延迟三秒,以便观察程序的输出。

  以下是一个服务端程序使用端口2500时的输出:

Server listener blocking status is True
New connection accepted
Received values 42 and 69, sent result 111
Received values 66 and 71, sent result 137
Received values 7 and 93, sent result 100
Received values 43 and 65, sent result 108
Received values 45 and 3, sent result 48
Shutting down server

  而以下是对应的客户端程序,在发送5对值之后的输出:

Sent values 42 and 69, received result 111
Sent values 66 and 71, received result 137
Sent values 7 and 93, received result 100
Sent values 43 and 65, received result 108
Sent values 45 and 3, received result 48
Notified server we're shutting down
Shutting down client
 套接字上的串行化

  前面所演示的服务端与客户端程序以简单的方式进行数值交换,如int,然而,程序很有可能也会需要发送与接收各种不同的用户自定义的对象类型,这就涉及到串行化。

  试想某些金融程序所涉及到的许多事务类型,如存款、转账、取款,每一种都与事务有关。在此,只需简单地设置好适当的串行化与反串行化机制,服务端就能处理多个客户端请求,并可返回这些事务的任意数量与任意组合。

  以下有一些练习来加深对此的了解:

  1、 如果一个服务端连接队列已满,那对新的客户端连接请求来说,会发生什么呢?

  2、 如果当客户端还有一个打开的套接字,而服务端此时却关闭了,会发生什么呢?反之呢?

  3、 试着运行一个服务端和两个客户端。我们前面说过,服务端只能处理一个客户端,为使服务端能同时处理多个客户端,需要进行多线程设计,建议对服务端作一些适当的修改,并用两个、三个、或更多客户端来测试

  4、 以下是当有两个客户端运行时的输出:

Client 2600 4
Sent values 56 and 35, received result 91
Sent values 48 and 20, received result 68
Sent values 6 and 97, received result 103
Sent values 76 and 9, received result 85
Notified server we're shutting down
Shutting down client
Client 2600 2
Sent values 69 and 66, received result 135
Sent values 84 and 45, received result 129
Notified server we're shutting down
Shutting down client
Server 2600
Waiting for new connection request
New connection accepted
Started thread Thread-1
Waiting for new connection request
Executing thread Thread-1
Received values 56 and 35, sent result 91
New connection accepted
Started thread Thread-2
Waiting for new connection request
Executing thread Thread-2
Received values 69 and 66, sent result 135
Received values 48 and 20, sent result 68
Received values 84 and 45, sent result 129
Received values 6 and 97, sent result 103
Shutting down server thread Thread-2
Received values 76 and 9, sent result 85
Shutting down server thread Thread-1

原文转自:http://www.ltesting.net

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)