本编文章分析了wxSocket在Linux操作系统中的实现,并总结了相关使用方法。
wxSocket 属于wxWidgets的wxNet子module。wxSocket是对系统socket API的简单封装,对外提供了wxEvent通知机制,屏蔽了操作系统相关的实现细节。用户可以通过wxSocketClient/wxSocketServer来很方便的使用。
Note: 此处使用的wxWidgets 库的版本是 v2.8.9,主要涉及到代码文件 src/common/sckstrm.cpp, src/common/socket.cpp 和 src/unix/gsocket.cpp。
1. wxSocket 实现
wxSocket相关类UML图:
在wx的在线manual里,可以看到wxNet相关的所有类。
1.1 主要操作接口
wxSocket主要的操作如下:
- basic IO
- Close
- Discard
- Peek
- Unread
- Read
- ReadMsg
- Write
- WriteMsg
- Socket state
2. Functions to retrieve current state and miscellaneous info.
- Error
- GetLocal/GetPeer
- IsData
- IsDisconnected/IsConnected
- LastCount
- LastError
- IsOk
- SaveState/RestoreState
2. Functions that perform a timed wait on a certain IO condition.
- InterruptWait
- Wait
- WaitForLost
- WaitForRead/WaitForWrite
- and also:
- wxSocketServer::WaitForAccept
- wxSocketClient::WaitOnConnect
2. Functions that allow applications to customize socket IO as needed.
- GetFlags/SetFlags
- SetTimeout
- SetLocal
- Handling socket events
- Notify/SetNotify
- GetClientData/SetClientData
- SetEventHandler
用户可以方便的通过SetNotify来订阅想要通知的事件。基于此,可以完全把Read/Write操作事件化。
1.2 wxSocketFlags
wxSocketFlags用来控制socket的运行模式,主要的flag如下:
wxSocketFlags | 含义 |
wxSOCKET_BLOCK | Block the GUI (do not yield) while reading/writing data. |
wxSOCKET_NOWAIT | Read/write as much data as possible and return immediately. |
wxSOCKET_WAITALL | Wait for all required data to be read/written unless an error occurs. |
a. wxSOCKET_BLOCK表示在调用wxSocketBase::Read/Write的时候,不会触发yield的操作。如果wxSocket本身在main thread里面进行读写操作,程序此时会不响应UI消息。而且由于yield操作本身可能会导致socket相关的操作重入(reentrant),这很可能不是用户代码想要的。所以一般推荐在wxSocket_BLOCK+thread环境下使用wxSocket,这样socket的读写操作就不会阻塞UI thread。
b. wxSOCKET_WAITALL与wxSOCKET_NOWAIT正好相反,分别用于同步和异步读写。
1.3 wxSocket实现
wxSocket 对 系统socket函数进行封装,内部的socket fd采用非阻塞模式。
src/unix/gsocket.cpp
m_fd = socket(m_peer->m_realfamily,m_stream?SOCK_STREAM:SOCK_DGRAM,0);
if(m_fd == INVALID_SOCKET)
{
m_error = GSOCKIOERR;
return GSOCK_IOERR;
}
#ifdef SO_NOSIGPIPE
setsockopt(m_fd,SOL_SOCKET,SO_NOSIGPIPE,(const char*)&arg,sizeof(arg));
#endif
#if defined(__EMX__)||defined(__VISAGECPP__)
ioctl(m_fd,FIONBIO,(char*)&arg,sizeof(arg));
#else
ioctl(m_fd,FIONBIO,&arg);
#endif
这里arg的值为1,通过ioctl来设置socket fd为非阻塞。
2. wxSocketBase::Read/Write
wxSocket本质上是non-blocking socket。我们可以通过wxSocketBase::Read/Write直接读写数据。同时wxNet也提供了wxSocket stream (wxSocketInputStream, wxSocketOuputStream)来读写数据。
通过wxSocket stream来读写数据和直接通过wxSocket来读写基本一样。但是wxSocket stream 的作用在于,我们可以利用wx库提供的通用的stream操作类来处理socket相关的读写。比如: 利用wxBufferedInputStream 和 wxBufferedOutputStream来进行带缓冲区的读写。
2.1 未设置wxSOCKET_WAITALL flag时的wxSocketBase::Read
问题: 假如直接调用wxSocketBase::Read或者wxSocketInputStream::Read读取一大块数据(e.g. 10MB),可能会发生什么情况?
答案: wxSocketInputStream::Read返回后,实际读取的字节数很随机,跟当前的系统状态有很大关系。
当然这样的行为和直接用系统函数recv(2)或read(2)从non-blocking socket 读取数据类似。
这个通过查看wxSocketBase::_Read的实现(src/common/socket.cpp),很容易弄清楚。
wxUint32 wxSocketBase::_Read(void* buffer, wxUint32 nbytes)
{
// ...
int ret;
if (m_flags & wxSOCKET_NOWAIT)
{
m_socket->SetNonBlocking(1);
ret = m_socket->Read((char *)buffer, nbytes);
m_socket->SetNonBlocking(0);
if (ret > 0)
total += ret;
}
else
{
bool more = true;
while (more)
{
if ( !(m_flags