Socket的源代码位于src/network目录下,主要由文件socket.hsocket.cc构成。以NS3命名空间中Socket类的形式进行实现。

与原始的BSD的Socket API不同,NS3中的实现是异步的,其中不再包含会导致阻塞的调用。发送和接收操作都是通过NS3中提供的回调机制实现的。另外,NS3中使用了Packet类来作为一个字节的缓冲区,其允许将其在API之间进行传送而不是一个数据的指针。

同样,不是所有的POSIX的Socket API是支持的,但尽管如此,NS3中的实现已经尽可能地接近于原始的BSD版本的实现方式,对于熟悉BSD API的人来说,理解NS3中对于Socket的实现并不困难。

Socket类直接继承于Object类。

更多的细节可以在官网给出的ns-3 tutorial中找到,此不赘述。

枚举类型

SocketErrno/错误号

根据简写猜了一些错误号代表的意思,已经注释到了代码的后面。

  /**
   * \enum SocketErrno
   * \brief Enumeration of the possible errors returned by a socket.
   */
  enum SocketErrno {
    ERROR_NOTERROR, // not error
    ERROR_ISCONN, // is connected
    ERROR_NOTCONN, // not connected
    ERROR_MSGSIZE, // message size
    ERROR_AGAIN, // again
    ERROR_SHUTDOWN, // shutdown
    ERROR_OPNOTSUPP, // operation not support
    ERROR_AFNOSUPPORT, // address famliy not support
    ERROR_INVAL, // invalid 
    ERROR_BADF, // bad file descriptor
    ERROR_NOROUTETOHOST, // no route to host
    ERROR_NODEV, // no device
    ERROR_ADDRNOTAVAIL, // address not available
    ERROR_ADDRINUSE, // address in use
    SOCKET_ERRNO_LAST // error number last
  };

SocketType/Socket类型

  /**
   * \enum SocketType
   * \brief Enumeration of the possible socket types.
   */
  enum SocketType {
    NS3_SOCK_STREAM,
    NS3_SOCK_SEQPACKET,
    NS3_SOCK_DGRAM,
    NS3_SOCK_RAW
  };

其中:

  • NS3_SOCK_STREAM代表流式Socket,也就是TCP。
  • NS3_SOCK_SEQPACKET:The SOCK_SEQPACKET socket type is similar to the SOCK_STREAM type, and is also connection-oriented. The only difference between these types is that record boundaries are maintained using the SOCK_SEQPACKET type. A record can be sent using one or more output operations and received using one or more input operations, but a single operation never transfers parts of more than one record. Record boundaries are visible to the receiver via the MSG_EOR flag in the received message flags returned by the recvmsg() function. It is protocol-specific whether a maximum record size is imposed.
  • NS3_SOCK_DGRAM代表数据报式Socket,也就是UDP。
  • NS3_SOCK_RAW也即是原始套接字是一种不同于SOCK_STREAM、SOCK_DGRAM的套接字,它实现于系统核心。然而,原始套接字能做什么呢?首先来说,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文。总体来说,SOCK_RAW可以处理普通的网络报文之外,还可以处理一些特殊协议报文以及操作IP层及其以上的数据。

SocketPriority/Socket优先级

The networking code within Linux handles many protocols besides IP, so in order to handle the priority of a packet in a generic way it is translated to a generic "Linux Priority". The Linux priority of a packet is a number from 0 to 15, high numbers meaning a higher priority packet. Not all of the 16 priorities are named in the kernel.

  /**
   * \enum SocketPriority
   * \brief Enumeration of the possible socket priorities.
   *
   * Names and corresponding values are derived from
   * the Linux TC_PRIO_* macros
   */
  enum SocketPriority {
    NS3_PRIO_BESTEFFORT = 0,
    NS3_PRIO_FILLER = 1,
    NS3_PRIO_BULK = 2,
    NS3_PRIO_INTERACTIVE_BULK = 4,
    NS3_PRIO_INTERACTIVE = 6,
    NS3_PRIO_CONTROL = 7
  };

关键变量

Protected

m_boundnetdevice

Ptr<NetDevice> m_boundnetdevice; //!< the device this socket is bound to (might be null).

这个变量表示这个socket所绑定的设备,也就是网卡(NetDevice),有可能是null值。

m_recvPktInfo

bool m_recvPktInfo; //!< if the socket should add packet info tags to the packet forwarded to L4.

注意这个变量是一个布尔值,也就是一个开关,用来决定是否应当将接收到的包信息转交到上层L4,也就是传输层。

Private

Callbacks

在私有区定义了9个回调函数,如下:

Callback<void, Ptr<Socket> > m_connectionSucceeded;  //!< connection succeeded callback
Callback<void, Ptr<Socket> > m_connectionFailed;     //!< connection failed callback
Callback<void, Ptr<Socket> > m_normalClose;          //!< connection closed callback
Callback<void, Ptr<Socket> > m_errorClose;           //!< connection closed due to errors callback
Callback<bool, Ptr<Socket>, const Address&> m_connectionRequest;    //!< connection request callback
Callback<void, Ptr<Socket>, const Address&> m_newConnectionCreated; //!< connection created callback
Callback<void, Ptr<Socket>, uint32_t> m_dataSent;             //!< data sent callback
Callback<void, Ptr<Socket>, uint32_t > m_sendCb;               //!< packet sent callback
Callback<void, Ptr<Socket> > m_receivedData;         //!< data received callback

这些变量分别代表当某一事件发生后系统将要执行的回调函数。

  • 首先,Socket类的用户通过设置将回调函数赋值到这些回调函数变量中;
  • 当事件发生后,Socket类自动调用存在这些变量中的回调函数。

这9个回调函数的作用分别为:

  • 首先是关于连接的两个回调函数:
    • m_connectionSucceeded表示当连接成功时的回调函数,通过函数SetConnectCallback设置,通过函数NotifyConnectionSucceeded调用;
    • m_connectionFailed表示当连接失败时的回调函数,同样通过函数SetConnectCallback设置,通过函数NotifyConnectionFailed调用。
  • 其次是关于关闭的两个回调函数:
    • m_normalClose表示当连接关闭后的回调函数,通过函数SetCloseCallbacks设置,通过函数NotifyNormalClose调用;
    • m_errorClose表示当连接关闭失败后的回调函数,同样通过函数SetCloseCallbacks设置,通过函数NotifyErrorClose调用。
  • 当接收到连接请求时并建立新的连接时:
    • m_connectionRequest表示当接收到连接请求时的回调,通过函数SetAcceptCallback设置,通过函数NotifyConnectionRequest调用。
    • m_newConnectionCreated表示当服务器接受了客户端请求,并且一个新的连接被生成时的回调,这个函数同样通过SetAcceptCallback设置,并通过函数NotifyNewConnectionCreated调用。
  • 数据被发送之后:
    • m_dataSent表示当数据被发送之后的回调,通过函数SetDataSentCallback设置,通过函数NotifyDataSent在数据被发送时调用,用来通知应用层数据已经被发送了。
    • m_sendCb同样表示数据被发送之后的回调,但是这个回调更关注当前发送端缓冲区的状态,在发送方缓冲区缓冲等级下降事件发生时被调用。通过函数SetSendCallback设置,通过函数NotifySend调用。
  • 数据需要被接收时:
    • m_receivedData表示当数据需要被应用层读取时的回调函数,通过函数SetRecvCallback设置,通过函数NotifyDataRecv来通知应用层。

m_priority

uint8_t m_priority; //!< the socket priority

这个表示socket的优先级,具体可见Understanding IP Precedence, ToS, and DSCP « 云中君

IPv4 options

这个部分列出了一些对于IPv4的相关状态变量。其中包括三个开关(bool),两个数据,具体解释可看以下代码的注释。

  bool m_manualIpTtl; //!< socket has IPv4 TTL set
  bool m_ipRecvTos;   //!< socket forwards IPv4 TOS tag to L4
  bool m_ipRecvTtl;   //!< socket forwards IPv4 TTL tag to L4

  uint8_t m_ipTos; //!< the socket IPv4 TOS
  uint8_t m_ipTtl; //!< the socket IPv4 TTL

IPv6 options

这个部分列出了一些对于IPv6的相关状态变量,其中包含四个开关(bool),两个数据,具体解释可看以下代码的注释。

  bool m_manualIpv6Tclass;    //!< socket has IPv6 Tclass set
  bool m_manualIpv6HopLimit;  //!< socket has IPv6 Hop Limit set
  bool m_ipv6RecvTclass;      //!< socket forwards IPv6 Tclass tag to L4
  bool m_ipv6RecvHopLimit;    //!< socket forwards IPv6 Hop Limit tag to L4

  uint8_t m_ipv6Tclass;     //!< the socket IPv6 Tclass
  uint8_t m_ipv6HopLimit;   //!< the socket IPv6 Hop Limit

关键函数

Socket原语

在socket中,一共有八个原语:

  • SOCKET服务原语:用于建立发方通信端点,返回一个整数用作为socket标识
  • ` BIND`服务原语:用于给新建立的通信端点赋予一个地址
  • CONNECT服务原语:在面向连接的TCP服务中用于在本地端点和远地端点间建立一条连接,在无连接的UDP服务中是把对方地址存储下来。
  • LISTEN服务原语:服务器为请求连接的客户分配请求连接队列空间,并指定队列长度(一般为5)。
  • ACCEPT服务原语:由服务器执行,等待连接请求的到来,请求到达后,服务器创建一个新连接端点,并将该端点的标识符返给请求端,接着产生一个进程为该连接服务,然后再去等待新的连接。
  • SEND服务原语:进行发送数据。
  • RECV服务原语:进行接收数据。
  • CLOSE服务原语:用于释放连接,双方都使用CLOSE原语后,连接即释放。

它们分别对应NS3中的函数:

CreateSocket(对应SOCKET)

  /**
   * This method wraps the creation of sockets that is performed
   * on a given node by a SocketFactory specified by TypeId.
   * 
   * \return A smart pointer to a newly created socket.
   * 
   * \param node The node on which to create the socket
   * \param tid The TypeId of a SocketFactory class to use
   */
  static Ptr<Socket> CreateSocket (Ptr<Node> node, TypeId tid);

这个函数可以看作是通过第二个参数tid提供的一个SocketFactory,这个SocketFactory类是一个基类,对于不同的传输层协议应当实现这个类,并且在调用该函数前应当首先将这个SocketFactory聚集到这台主机node上, 来批量为这个节点,也就是主机node来生产Socket。

这个函数是静态函数,因为当没有socket实例的时候应当能够生成socket。

Bind(对应Bind)

Bind函数也包括了一些其重载函数,其将一个IP地址分配给当前的socket。可以是IPv4也可以是IPv6。

Connect(对应CONNECT)

  /**
   * \brief Initiate a connection to a remote host
   * \param address Address of remote.
   * \returns 0 on success, -1 on error (in which case errno is set).
   */
  virtual int Connect (const Address &address) = 0;

给定一个远端主机地址,我们主动地发送连接的请求。

这个函数是一个纯虚函数,这意味着这个函数需要在继承Socket的具体类中,比如UdpSocket类中来实现。从函数语义的角度来考虑,这种设计是合理的,因为我们无法在基类中实现一个默认的实现方式。

Listen(对应LISTEN)

  /**
   * \brief Listen for incoming connections.
   * \returns 0 on success, -1 on error (in which case errno is set).
   */
  virtual int Listen (void) = 0;

将当前设置为侦听阶段,接收远端发来的请求。

这个函数是一个纯虚函数,这意味着这个函数需要在继承Socket的具体类中,比如UdpSocket类中来实现。从函数语义的角度来考虑,这种设计是合理的,因为我们无法在基类中实现一个默认的实现方式。

SetAcceptCallback(对应Accept)

这个函数允许设置两个Callback。

  /**
   * \brief Accept connection requests from remote hosts
   * \param connectionRequest Callback for connection request from peer. 
   *        This user callback is passed a pointer to this socket, the 
   *        ip address and the port number of the connection originator. 
   *        This callback must return true to accept the incoming connection,
   *        false otherwise. If the connection is accepted, the 
   *        "newConnectionCreated" callback will be invoked later to 
   *        give access to the user to the socket created to match 
   *        this new connection. If the user does not explicitly 
   *        specify this callback, all incoming  connections will be refused.
   * \param newConnectionCreated Callback for new connection: when a new
   *        is accepted, it is created and the corresponding socket is passed
   *        back to the user through this callback. This user callback is 
   *        passed a pointer to the new socket, and the ip address and 
   *        port number of the connection originator.
   */
  void SetAcceptCallback (Callback<bool, Ptr<Socket>, 
                                   const Address &> connectionRequest,
                          Callback<void, Ptr<Socket>, 
                                   const Address&> newConnectionCreated);

当连接请求到来时,系统会执行这些Callback,第一个Callback也就是connectionRequest是首先被处理的。其通过返回True来接收连接请求,当连接被接受时,系统会调用第二个Callback,也就是newConnectionCreated来通知用户新的连接已经建立,并初始化一个新的socket来处理这个连接。当connectionRequest未被指定时,任何到来的请求都将被拒绝。

Send(对应SEND)

这个函数用来发送数据,ns-3: ns3::Socket Class Reference中的注释很详细,这里不再展开。

virtual int Send (Ptr<Packet> p, uint32_t flags) = 0;

这个函数是一个纯虚函数,这意味着这个函数需要在继承Socket的具体类中,比如UdpSocket类中来实现。从函数语义的角度来考虑,这种设计是合理的,因为我们无法在基类中实现一个默认的实现方式。

Recv(对应RECV)

这个函数用来接收数据,ns-3: ns3::Socket Class Reference中的注释很详细,这里不再展开。

virtual Ptr<Packet> Recv (uint32_t maxSize, uint32_t flags) = 0;

这个函数是一个纯虚函数,这意味着这个函数需要在继承Socket的具体类中,比如UdpSocket类中来实现。从函数语义的角度来考虑,这种设计是合理的,因为我们无法在基类中实现一个默认的实现方式。

Close(对应CLOSE)

关闭当前socket。

  /**
   * \brief Close a socket.
   * \returns zero on success, -1 on failure.
   *
   * After the Close call, the socket is no longer valid, and cannot
   * safely be used for subsequent operations.
   */
  virtual int Close (void) = 0;

这个函数是一个纯虚函数,这意味着这个函数需要在继承Socket的具体类中,比如UdpSocket类中来实现。从函数语义的角度来考虑,这种设计是合理的,因为我们无法在基类中实现一个默认的实现方式。

Public

Setxxx & Bindxxx

绑定网卡设备或者设置一些状态或者标志位,以及通过SetxxxCallback为某些事件设置一个回调函数,具体可见上面关于私有变量的部分。

Getxxx

其中包含:

  • 得到当前TypeId:

    static TypeId GetTypeId (void);
    
  • 得到该socket位于哪个节点:

    virtual Ptr<Node> GetNode (void) const = 0;
    
  • 得到最近发生错误的错误号:

    virtual enum Socket::SocketErrno GetErrno (void) const = 0;
    
  • 得到Socket类型:

    virtual enum Socket::SocketType GetSocketType (void) const = 0;
    

以及:

  • 通过一次调用Send函数,最多能传输多少字节的数据:

    virtual uint32_t GetTxAvailable (void) const = 0;
    
  • 通过一次或者多次调用Recv,能够得到多少字节的数据:

    virtual uint32_t GetRxAvailable (void) const = 0;
    

以及:

  • 当前绑定的地址:

    virtual int GetSockName (Address &address) const = 0; 
    
  • 当前通信方的地址:

    virtual int GetPeerName (Address &address) const = 0;
    
  • 当前绑定的网卡设备:

    Ptr<NetDevice> GetBoundNetDevice (); 
    

以及一些之前通过Set系列设置的值的获取函数等等。

Shutdownxxx

关闭一些东西,目前有ShutdownSend ShutdownRecv两个函数,主要在TCP中使用。

Isxxx

用来判断一些东西。

Ipv6xxx

Ipv6相关操作,此处暂且不表。

Protected

Notifyxxx

通过调用回调函数来通知一些信息。

参考文献