ROS-I simple_message 源码分析:SimpleSocket

分析了SimpleMessage和更上一层的数据结构,再来看看底层的通信是怎么实现的。前面已分析过SmplMsgConnection这个基类,它是对SimpleMessage底层通信的一层抽象。而SimpleSocket类是SmplMsgConnection的派生类,它定义了当采用socket做通信时的一些socket方法接口,后面编写的TCP server,TCP client,UDP server,UDP client均是基于SimpleSocket来实现的。

namespace industrial
{
namespace simple_socket
{

namespace StandardSocketPorts
{
enum StandardSocketPort
{
  MOTION = 11000, SYSTEM = 11001, STATE = 11002, IO = 11003
};
}
typedef StandardSocketPorts::StandardSocketPort StandardSocketPort;

class SimpleSocket : public industrial::smpl_msg_connection::SmplMsgConnection
{
public:

  SimpleSocket()
  {
    this->setSockHandle(this->SOCKET_FAIL);
    memset(&this->sockaddr_, 0, sizeof(this->sockaddr_));
    this->setConnected(false);
  }

  virtual ~SimpleSocket(){}

  bool isConnected()
  {
    return connected_;
  }
  
  virtual void setDisconnected()
  {
    setConnected(false);
  }
  
  bool isReadyReceive(int timeout)
  {
    bool r, e;
    rawPoll(timeout, r, e);
    return r;
  }

protected:
  //接收和发送数据的socket句柄
  int sock_handle_;
  //远程socket地址和端口号
  sockaddr_in sockaddr_;
  //socket连接状态
  bool connected_;
  //socket错误返回值
  static const int SOCKET_FAIL = -1;
  //接收数据最大字节数
  static const int MAX_BUFFER_SIZE = 1024;
  //查询socket是否ready的超时参数,1000ms
  static const int SOCKET_POLL_TO = 1000;
  //用于接收数据的内部缓冲区
  char buffer_[MAX_BUFFER_SIZE + 1];
 
  //获取socket句柄
  int  getSockHandle() const
  {
    return sock_handle_;
  }
  
  //设置socket句柄
  void setSockHandle(int sock_handle_)
  {
    this->sock_handle_ = sock_handle_;
  }

 //错误报告
  __attribute__((deprecated(
                   "Please use: logSocketError(const char* msg, const int rc, const int error_no)")))
  void logSocketError(const char* msg, int rc)
  {
    logSocketError(msg, rc, errno);
  }

  void logSocketError(const char* msg, const int rc, const int error_no)
  {
    LOG_ERROR("%s, rc: %d. Error: '%s' (errno: %d)", msg, rc, strerror(error_no), error_no);
  }
  
  // 发送和接收方法,需派生类重写 rawSendBytes 和 rawReceiveBytes
  // Virtual
  bool sendBytes(industrial::byte_array::ByteArray & buffer);
  bool receiveBytes(industrial::byte_array::ByteArray & buffer,
      industrial::shared_types::shared_int num_bytes);
  // Virtual
  virtual int rawSendBytes(char *buffer,
      industrial::shared_types::shared_int num_bytes)=0;
  virtual int rawReceiveBytes(char *buffer,
      industrial::shared_types::shared_int num_bytes)=0;
 
  //查询socket上是否有数据
  virtual bool rawPoll(int timeout, bool & ready, bool & error)=0;

  virtual void setConnected(bool connected)
  {
    this->connected_ = connected;
  }

};

} //simple_socket
} //industrial

以上代码定义了socket通信的接口,数据成员包括socket句柄(也就是文件描述符),地址和端口号,连接状态,内部数据缓冲区等。可以看出rawSendBytes、rawReceiveBytes,rawPoll并没有做实现,这3个方法是放到其派生类TcpSocket中实现了,此处只是定义了接口的功能。

sendBytes
bool SimpleSocket::sendBytes(ByteArray & buffer)
    {
      int rc = this->SOCKET_FAIL;
      bool rtn = false;

      if (this->isConnected())
      {
        // Nothing restricts the ByteArray from being larger than the what the socket
        // can handle.
        if (this->MAX_BUFFER_SIZE > (int)buffer.getBufferSize())
        {

          // copy to local array, since ByteArray no longer supports
          // direct pointer-access to data values
          std::vector<char> localBuffer;
          buffer.copyTo(localBuffer);
          rc = rawSendBytes(&localBuffer[0], localBuffer.size());
          if (this->SOCKET_FAIL != rc)
          {
            rtn = true;
          }
          else
          {
            rtn = false;
            logSocketError("Socket sendBytes failed", rc, errno);
          }

        }
        else
        {
          LOG_ERROR("Buffer size: %u, is greater than max socket size: %u", buffer.getBufferSize(), this->MAX_BUFFER_SIZE);
          rtn = false;
        }

      }
      else
      {
        rtn = false;
        LOG_WARN("Not connected, bytes not sent");
      }

      if (!rtn)
      {
        this->setConnected(false);
      }

      return rtn;

    }

sendBytes实现的是SmplMsgConnection中的虚方法,功能是发送ByteArray类型的数据。首先确保socket能发送的字节数比待发送数据的数据字节数要大,然后拷贝数据至临时变量,再调用rawSendBytes将数据发送出去,并返回发送结果至上层。

receiveBytes
bool SimpleSocket::receiveBytes(ByteArray & buffer, shared_int num_bytes)
    {
      int rc = this->SOCKET_FAIL;
      bool rtn = false;
      shared_int remainBytes = num_bytes;
      bool ready, error;

      memset(&this->buffer_, 0, sizeof(this->buffer_));

      if (this->MAX_BUFFER_SIZE > buffer.getMaxBufferSize())
      {
        LOG_WARN("Socket buffer max size: %u, is larger than byte array buffer: %u",
            this->MAX_BUFFER_SIZE, buffer.getMaxBufferSize());
      }
      if (this->isConnected())
      {
        buffer.init();
        while (remainBytes > 0)
        {
          // Polling the socket results in an "interruptable" socket read.  This
          // allows Control-C to break out of a socket read.  Without polling,
          // a sig-term is required to kill a program in a socket read function.
          if (this->rawPoll(this->SOCKET_POLL_TO, ready, error))
          {
            if(ready)
            {
              rc = rawReceiveBytes(this->buffer_, remainBytes);
              if (this->SOCKET_FAIL == rc)
              {
                this->logSocketError("Socket received failed", rc, errno);
                remainBytes = 0;
                rtn = false;
                break;
              }
              else if (0 == rc)
              {
                LOG_WARN("Recieved zero bytes: %u", rc);
                remainBytes = 0;
                rtn = false;
                break;
              }
              else
              {
                remainBytes = remainBytes - rc;
                LOG_COMM("Byte array receive, bytes read: %u, bytes reqd: %u, bytes left: %u",
                    rc, num_bytes, remainBytes);
                buffer.load(&this->buffer_, rc);
                rtn = true;
              }
            }
            else if(error)
            {
              LOG_ERROR("Socket poll returned an error");
              rtn = false;
              break;
            }
            else
            {
              LOG_ERROR("Uknown error from socket poll");
              rtn = false;
              break;
            }
          }
          else
          {
            LOG_COMM("Socket poll timeout, trying again");
          }
        }
      }
      else
      {
        LOG_WARN("Not connected, bytes not sent");
        rtn = false;
      }

      if (!rtn)
      {
        this->setConnected(false);
      }
      return rtn;
    }

receiveBytes实现的是SmplMsgConnection中的虚方法,功能是从socket上接收数据至ByteArray类型的变量。首先会检查socket能接收的最大字节数是否大于ByteArray配置的最大字节数,如果大于,则给出警告,因为可能会丢失数据。接着会调用rawPoll查询socket上的数据是否可读,最长阻塞时间为1000ms。如果可读,则调用rawReceiveBytes接收数据,接收到有效数据后调用buffer.load将buffer_内的数据加载到传进来的buffer中,并返回true给上层。

下一篇将介绍TcpSocket这个类,看一下上面的虚方法rawSendBytes,rawReceiveBytes,rawPoll是如何实现的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容