65.9K
CodeProject 正在变化。 阅读更多。
Home

通用的 C 语言 TCP 客户端应用程序

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (2投票s)

2010年5月9日

CPOL

4分钟阅读

viewsIcon

30149

downloadIcon

813

用于编写简单 TCP 客户端应用程序的库。

引言

本文介绍了一个用于编写简单的 TCP 客户端应用程序的库,该库使用 C 语言和面向对象的思想编写,利用标准的 Berkeley 套接字接口,并旨在实现跨平台。开发是在安装了 Ubuntu Linux 发行版的系统上进行的,之后在 Windows 2003 上使用 Visual Studio Express 2008 进行了移植和少量测试。

背景

当我为我的 CodeProject 文章“一个小型 C 语言 TCP 服务器框架”(请参阅此处)编写示例程序时,我注意到一个奇怪的现象:编写服务器示例比编写客户端示例更容易!因此,我决定深入研究这个问题,结果是原始服务器项目的精简版本,现在只包含用于编写简单客户端应用程序的功能,并且简单直接。

描述

首先,我将介绍一些术语。

通用客户端是使用本文所述库编写的简单 TCP 客户端应用程序。

这个通用客户端遵循一个由三个概念构建的特定模式:状态事件命令

状态是以下之一

  1. CONNECTED_IDLE
  2. NOT_CONNECTED

命令是以下之一

  1. CONNECT
  2. SEND
  3. RECV
  4. CLOSE

事件是以下之一

  1. CONNECTION_CREATED
  2. CONNECTION_DESTROYED
  3. CONNECT_ERROR
  4. RECV_COMPLETE
  5. RECV_TIMEOUT
  6. SEND_COMPLETE

还有另外两个实体参与其中:由库提供的 GenericClient 类的一个实例,以及由库用户编写的应用程序代码GenericClient 类是从 Client 类非正式派生而来的,Client 类提供了编写客户端应用程序的基本方法。

使用此库编写的应用程序中的一般控制流如下所示:

  1. 应用程序代码将一个命令传递给 GenericClient 实例。
  2. GenericClient 实例处理接收到的命令,并将一个事件传递给应用程序代码
  3. 然后,应用程序代码处理从 GenericClient 实例接收到的事件,然后循环再次重复。

GenericClient 实例由一个状态机驱动,如下面的伪代码所示

1. if state is NOT_CONNECTED

      // only accepts the CONNECT command
      do connect
      
      if result is OK
         set state to CONNECTED_IDLE
         return event CONNECTION_CREATED
      else
         // remains in the state NOT_CONNECTED
         return event CONNECT_ERROR
         
2. if state is CONNECTED_IDLE

      2.1 if command is SEND
          do send
          if result is OK
             // remains in the state CONNECTED_IDLE
             return event SEND_COMPLETE
          else
            set state to NOT_CONNECTED
            return event CONNECTION_DESTROYED
            
      2.2 if command is RECV
          do recv
          if result is OK
             // remains in the state CONNECTED_IDLE
             return event RECV_COMPLETE
          else if occurred TIMEOUT
             // remains in the state CONNECTED_IDLE
             return event RECV_TIMEOUT
          else // error
             set state to NOT_CONNECTED
             return event CONNECTION_DESTROYED
             
      2.3 if command is CLOSE
          do close
          set state to NOT_CONNECTED
          return event CONNECTION_DESTROYED

应用程序代码不必关心 GenericClient 实例如何工作;它只关心如何处理从 GenericClient 实例接收到的事件

应用程序代码的伪代码是开放的,但通常如下所示

1. if event is CONNECTION_CREATED
      prepare message to send to the server
      pass the command SEND to the generic client instance
      
2. if event is SEND_COMPLETE
      update whatever controls needed by the application
      pass the command RECV to the generic client instance
      
3. if event is RECV_COMPLETE
      process reply received from the server
      prepare another message to send to the server
      pass the command SEND to the generic client instance
      
4. if event is RECV_TIMEOUT (*** optional ***)
      do whatever the application needs in case of timeout
      if business rules says try again
         prepare another message to send to the server
         pass the command SEND to the generic client instance
      else
         pass the command CLOSE to the generic client instance
         
5. if event is CONNECTION_DESTROYED
      // can be the result of SEND, RECV or CLOSE commands
      // (most errors will automatically destroy the connection)
      do whatever cleanup the application needs
      pass the command CONNECT to the generic client instance
      
6. if event is CONNECT_ERROR
      do whatever the application needs in this case
      if business rules says try again
         pass the command CONNECT to the generic client instance
      else
         end application

利用上述模式,编写简单的 TCP 客户端应用程序非常容易,因为看不到 TCP/IP 网络代码。实际上,只需要编写应用程序代码

这里的目标不是编写生产级别的应用程序,而是用于测试场景、原型开发新功能、查找服务器应用程序中的错误等的简单实用程序。在这些情况下,开发速度至关重要,因为创建的程序通常是可丢弃的、一次性的实用程序,其范围和功能有限,不值得花费太多时间进行开发。

实现上述伪代码的 C 代码如下所示

switch (genCli_waitEvent())
{
   case CLI_EVT_CONNECTION_CREATED:
      prepareFirstMessage();
      genCli_send();
      break;
      
   case CLI_EVT_RECV_COMPLETE:
      processServerReply();
      prepareAnotherMessage();
      genCli_send();
      break;
      
   case CLI_EVT_SEND_COMPLETE:
      genCli_recv();
      break;
      
   case CLI_EVT_RECV_TIMEOUT:
      prepareAnotherMessage();
      genCli_send();
      break;
      
   case CLI_EVT_CONNECT_ERROR:
      printConnectionError();
      genCli_connect();
      break;
      
   case CLI_EVT_CONNECTION_DESTROYED:
      printOperationError();
      genCli_connect();
      break;
      
   default:
      printf("*invalid event %d\n", genCli_event());
      abort();
      break;
} // switch

上面的 genCli_xxx 方法由 GenericClient 类提供。应用程序程序员不必担心通常与 TCP/IP 编程相关的所有样板代码。有关更多详细信息,请参阅 gen_client_1 示例项目。

该库还提供了其他功能,由 Client 类呈现,但实际上是由其他类实现的(这里的 Client 类充当外观)。

这些附加功能中最重要的是与 Message 类相关。它提供了在客户端和服务器之间交换消息时使用的缓冲区的封装,还提供了在网络上传输消息的定界框架。请参阅 Message 类、Client 类以及此项目附带的 gen_client_1 示例的文档。顺便说一下,Message 类为应用程序提供的更重要的方法(由 Client 类封装)是:client_messageBufferclient_messageSizeclient_setMessageSize

还有一个 Log 类,由库内部使用,但也可以由应用程序使用。其主要方法是(同样,由 Client 类封装)

  • client_logInfo
  • client_logWarn
  • client_logDebug
  • client_logTrace
  • client_logError
  • client_logFatal

库内部还有一些其他类,在编写客户端应用程序时可能有用,也可能无用:MutexThreadTimeTimeout

该库的编写旨在与使用此 CodeProject 文章中描述的库编写的服务器进行互操作。具体来说,此项目附带的 gen_client_1 示例可与上述文章附带的 server_2 server_3 示例一起使用。

要使该库能够与不同的服务器一起工作,很可能需要更改 MessageImpl.h 文件中声明的 Message 结构。具体来说,请参阅客户端和服务器之间交换的消息在网络上的格式。

结论

TCP 客户端应用程序通常遵循一个通用模式:打开与服务器的连接,发送请求,然后接收并处理响应。在通信出错的情况下,最安全的做法几乎总是关闭连接并重新开始。在该库中,我试图整合这种通用行为,以便将繁琐且重复的工作只做一次,而应用程序程序员只需关注与应用程序相关的业务规则。当然,这是利用了软件工程中一个非常著名的原则:DRY(Don't Repeat Yourself,不要重复你自己)原则。

历史

这是本文的第一个版本,涵盖了库的 1.00 版本。

© . All rights reserved.