LoopJump's Blog

Cwinux源码解析3

2014-01-08

网络包net

Cwinux中net包封装了网络层与传输层的TCP,UDP,Unix-Domain的协议。包括各自的网络地址对象、网络连接对象、网络连接建立对象、网络连接accept对象。net依赖cmn。

net包的代码相比cmn和app简单。

CwxIpcSap对文件名描述符的封装

在Linux环境网络编程中,建立的网络连接通过文件描述符fd来读写。在Cwinux中net包内,Cwinux对fd进行了封装。其中CwxIpcSap就是初步的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class CWX_API CwxIpcSap
{
public:
///enable或disable asynchronous。返回值,0成功;-1:失败。
int setSigio(bool enable=true) const;
///enable或disable non-blocking I/O。返回值,0成功;-1:失败。
int setNonblock(bool enable=true) const;
///enable或disable close-on-exec。返回值,0成功;-1:失败。
int setCloexec (bool enable=true) const;
///enable或disable sigurg。返回值,0成功;-1:失败。
int setSigurg (bool enable=true) const;
///是否设置了sigio, -1:失败,0:没有,1:设置
int isSigio() const;
///是否设置了nonblock, -1:失败,0:没有,1:设置
int isNonBlock() const;
///是否设置了Cloexec, -1:失败,0:没有,1:设置
int isCloexec() const;
///是否设置了Sigurg, -1:失败,0:没有,1:设置
int isSigurg() const;
/// Get the underlying handle.
CWX_HANDLE getHandle (void) const;
/// Set the underlying handle.
void setHandle (CWX_HANDLE handle);
public:
///通过fcntl设置F_SETFL状态,返回值,0成功;-1:失败。
static int setFlags (CWX_HANDLE handle, int flags);
///通过fcntl清空F_SETFL状态,返回值,0成功;-1:失败。
static int clrFlags (CWX_HANDLE handle, int flags);
///判断是否设置了flag,-1:失败,0:没有;1:设置
static int isFlags(CWX_HANDLE handle, int flags);
///enable或disable asynchronous。返回值,0成功;-1:失败。
static int setSigio(CWX_HANDLE handle, bool enable=true);
///enable或disable non-blocking I/O。返回值,0成功;-1:失败。
static int setNonblock(CWX_HANDLE handle, bool enable=true);
///enable或disable close-on-exec。返回值,0成功;-1:失败。
static int setCloexec (CWX_HANDLE handle, bool enable=true);
///enable或disable sigurg。返回值,0成功;-1:失败。
static int setSigurg (CWX_HANDLE handle, bool enable=true);
///是否设置了sigio, -1:失败,0:没有,1:设置
int isSigio(CWX_HANDLE handle) const;
///是否设置了nonblock, -1:失败,0:没有,1:设置
int isNonBlock(CWX_HANDLE handle) const;
///是否设置了Cloexec, -1:失败,0:没有,1:设置
int isCloexec(CWX_HANDLE handle) const;
///是否设置了Sigurg, -1:失败,0:没有,1:设置
int isSigurg(CWX_HANDLE handle) const;
protected:
// = Ensure that CwxIpcSap is an abstract base class.
/// Default constructor.
CwxIpcSap (void);
/// Protected destructor.
/**
* Not a virtual destructor. Protected destructor to prevent
* operator delete() from being called through a base class
* CwxIpcSap pointer/reference.
*/
~CwxIpcSap (void);
private:
/// Underlying I/O handle.
CWX_HANDLE handle_;
};

其中包含的成员变量 handle_ 就是文件描述符。该类的成员函数主要是用于属性获取和设置。

CwxSock*

CwxSockBase继承自CwxIpcSap,封装了socket handle相关的方法。主要是open和close两个函数。open用于打开一个socket,它的实现即调用系统的socket(…)函数,然后进行必要的设置(包括属性设置,将socket函数返回的文件描述符赋值给handle_ 成员变量)。close函数用于关闭socket,它的实现即调用系统的close(…)函数关闭socket,并将handle_ 设为无效。

CwxSockIo继承自CwxSockBase,该类封装了操作文件描述符fd的系统函数read/recv/send/write等。

CwxSockStream继承自CwxSockIo,该类表示一个TCP连接流。类似地,CwxSockDgram继承自CwxSockIo,该类表示一个UDP连接流。

CwxSockConnector建立主动连接

CwxSockConnector类用于发起一个主动的网络连接。其中connect函数就是用于建立连接的函数,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
@brief 建立TCP主动连接。
@param [out] stream 主动连接。
@param [in] remoteAddr 连接的远程地址。
@param [in] localAddr 连接的本地地址,若为空,则有connect自己分配。
@param [in] timeout 连接的超时时间,若NULL,表示没有超时限制。若值为0,则表示若没有连接上,立即返回。
@param [in] protocol 连接的协议,就是socket()的protocol参数,由于一个协议家族的socket 类型,其protocol也是唯一的,为0就可以了。
@param [in] reuse_addr 是否重用本地地址。
@param [in] fn socket属性设置的function。
@return -1:错误,errno记录错误的原因;0:成功。
*/
int CwxSockConnector::connect(CwxSockStream& stream, CwxAddr const& remoteAddr, CwxAddr const& localAddr, CwxTimeouter* timeout, int protocol, bool reuse_addr, CWX_NET_SOCKET_ATTR_FUNC fn, void* fnArg) {
if ((stream.getHandle() == CWX_INVALID_HANDLE) && (stream.open(remoteAddr.getType(), SOCK_STREAM, protocol, reuse_addr) == -1)) {
return -1;
}
if (localAddr != CwxAddr::sap_any) {
sockaddr *laddr = reinterpret_cast<sockaddr *>(localAddr.getAddr());
int size = localAddr.getSize();
if (::bind(stream.getHandle(), laddr, size) == -1) {
CwxErrGuard guard;
stream.close();
return -1;
}
}
// Enable non-blocking, if required.
if ((timeout != 0) && (stream.setNonblock(true) == -1)) {
CwxErrGuard guard;
stream.close();
return -1;
}
if (fn) {
if (0 != fn(stream.getHandle(), fnArg)) {
CwxErrGuard guard;
stream.close();
return -1;
}
}

int result = ::connect(stream.getHandle(), reinterpret_cast<sockaddr *>(remoteAddr.getAddr()), remoteAddr.getSize());

if (result == -1 && timeout != 0) {
// Check whether the connection is in progress.
if (errno == EINPROGRESS || errno == EWOULDBLOCK) {
// This expression checks if we were polling.
if (*timeout->getTimeout() == CwxTimeValue::ZERO) {
errno = EWOULDBLOCK;
}
// Wait synchronously using timeout.
else if (this->complete(stream, 0, timeout) == -1) {
} else {
return 0;
}
}
}

// EISCONN is treated specially since this routine may be used to
// check if we are already connected.
if (result != -1 || errno == EISCONN) {
// Start out with non-blocking disabled on the new_stream.
result = stream.setNonblock(false);
if (result == -1) {
CwxErrGuard guard;
stream.close();
}
} else if (!(errno == EWOULDBLOCK || errno == ETIMEDOUT)) {
CwxErrGuard guard;
stream.close();
}
return result;
}

简单地说,connect封装了系统的connect函数。

首先,确保stream已经打开了一个socket用于建立连接。参数localAddr用于使用户可以指定一个地址,用这个地址去建立主动连接。如果没指定,则跳过这段代码。如果指定了,要将上一步stream中的socket与指定的地址进行绑定。参数中如果指定了建立连接的超时时间,则stream设置非阻塞。如果参数指定了设置属性的函数,则调用该函数。

接下来,调用系统的connect函数,建立主动连接。

如果建立连接失败,且参数指定了超时时间,则调用complete函数。complete函数用于设置连接完成后的连接标志。其实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
@brief 设置连接完成后的连接标志。
@param [out] stream 主动连接。
@param [in] remoteAddr 连接的远程地址,若不为空则获取远端地址。
@param [in] timeout 连接的超时时间,若NULL,表示没有超时限制。若值为0,则表示若没有连接上,立即返回。
@return -1:错误,errno记录错误的原因;0:成功。
*/
int CwxSockConnector::complete(CwxSockStream &stream, CwxAddr *remoteAddr, CwxTimeouter* timeout) {
if (timeout && timeout->isTimer()) {
if (CwxSocket::handleReady(stream.getHandle(), timeout, false, true, fale, true) < 1) {
CwxErrGuard guard;
stream.close();
return -1;
}
}

int sock_err = 0;
socklen_t sock_err_len = sizeof(sock_err);
int sockopt_ret = ::getsockopt(stream.getHandle(), SOL_SOCKET, SO_ERROR, &sock_err, &sock_err_len);
if ((sockopt_ret < 0) || sock_err) {
stream.close();
errno = sock_err;
return -1;
}

if (remoteAddr != 0) {
socklen_t len = remoteAddr->getSize();
sockaddr *addr = reinterpret_cast<sockaddr *>(remoteAddr->getAddr());
if (::getpeername(stream.getHandle(), addr, &len) == -1) {
// Save/restore errno.
CwxErrGuard guard;
stream.close();
return -1;
}
}
stream.setNonblock(false);
return 0;
}

connect函数首先调用CwxSocket::handleReady来先查询一下该文件描述符是否有可读/可写/例外事件,正常的连接应该会返回一个正整数(表示该连接至少可以写入数据)。然后connect函数开始读取该连接上的设置并peername(对方的地址和端口)。

CwxSockAcceptor建立监听

CwxSockAcceptor类用于建立监听并接受连接,因此该类主要提供两个函数listen和accept。

listen函数用于建立监听。

1
2
3
4
5
6
7
8
9
10
11
 /**
@brief 建立TCP主动连接。
@param [in] addr Accept的本地地址。
@param [in] reuse 当监听地址没被释放的话,是否re-use。true:是;false:不是。
@param [in] backlog listen最大排队的数量。
@param [in] domain 协议族,就是socket()中的domain,PF_UNSPEC表示采用addr中的协议族。
@param [in] protocol 连接的协议,就是socket()的protocol参数,由于一个协议家族的socket 类型,其protocol也是唯一的,为0就可以了。
@param [in] fn socket 设置的function。
@return -1:错误,errno记录错误的原因;0:成功。
*/
int listen(CwxAddr const& addr, bool reuse= 0, int backlog = DEF_BACK_LOG, int domain = PF_INET, int protocol = 0, CWX_NET_SOCKET_ATTR_FUNC fn=NULL, void* fnArg=NULL);

其实现大致为:打开socket,调用bind函数进行地址绑定,然后调用系统的listen函数建立监听。

accept函数用于接受连接。

1
2
3
4
5
6
7
8
9
/**
@brief 接收一个被动TCP连接。
@param [in] stream 返回接收到的被动连接。
@param [in] remote_addr 若不为空,则返回接收到的对端host的地址。
@param [in] timeout accept的超时时间,若为NULL,则无限等待。
@param [in] bRestart 若在等待过程中被信号中断,是否继续等待。true:继续等待;false:不等待。
@return -1:错误,errno记录错误的原因;0:成功。
*/
int accept (CwxSockStream &stream, CwxAddr* remote_addr = 0, CwxTimeouter* timeout = NULL, bool bRestart = true) const;

其实现为调用系统的accept函数,然后设置stream参数,stream参数返回给调用者。

综上,net包主要是对Linux下网络编程的基本元素,如网络地址,连接等进行了封装。

扫描二维码,分享此文章