Socket在NS3中的实现
Socket的源代码位于src/network
目录下,主要由文件socket.h
和socket.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
通过调用回调函数来通知一些信息。