多处理器运行MFC CSocketFile序列化错误

PRB: CArchive::Read Might Hang When Using CSocketFile

 

SYMPTOMS

<script type="text/javascript">loadTOCNode(1, 'symptoms');</script>
When reading data that uses CArchive::Read and the file is a CSocketFile, certain buffer sizes and data sizes are more likely to cause a hang in CSocket::Receive at the end of transmission when the transmission isn't closed.

CAUSE

<script type="text/javascript">loadTOCNode(1, 'cause');</script>
CArchive sometimes tries to maintain data in its buffer even if all of the data requested has been read. This causes an extra CSocketFile::Read call when no data is available on the socket file. The Read call hangs until more data appears or until the socket connection is closed.

RESOLUTION

<script type="text/javascript">loadTOCNode(1, 'resolution');</script>
The underlying socket of the socket file should be closed at the end of transmitting the data.

MORE INFORMATION

<script type="text/javascript">loadTOCNode(1, 'moreinformation');</script>
CArchive provides a buffered mechanism to read data from a file, which can be based on CSocketFile as well as CFile. The algorithm for CArchive::Read is implemented so that an extra call to a CFile derived class (for example, CSocketFile) Read function is made even if all of the needed data is present. For disk-based files, this presents no problem because EOF is reached and the call to Read returns immediately. For CsocketFile-based files, either the socket must be closed or more data needs to be sent for the Read call to return.

The hang is more likely to occur if the transmitted data size is a multiple of the buffer size (4k) because the last byte needed is more likely to be the last byte in the current buffer. This results in an empty buffer and causes another Read operation even though there is no more data to be read.

Normally, if the required data to be read is less than the buffer size, the Read operation returns with less than the requested size (the buffer size), however in this case, it does not cause CArchive to replenish its buffer.

The proper way to avoid this situation is to close the socket connection. (c) Microsoft Corporation 1998, All Rights Reserved. Contributions by Adam Kim, Microsoft Corporation.
INFO: Avoid Data Peeking in Winsock

SUMMARY

<script type="text/javascript">loadTOCNode(1, 'summary');</script>
Peeking for data in Winsock means that an application requests for the amount of incoming data waiting to be received without actually receiving it. This is a holdover from Winsock 1.1 compatibility with Unix BSD Sockets. In Winsock, this is accomplished with either recv/WSARecv(..., MSG_PEEK) or ioctlsocket(FIONREAD, ...). You should avoid doing this because it is highly inefficient, and it subjects an application to an incorrect data count.

MORE INFORMATION

<script type="text/javascript">loadTOCNode(1, 'moreinformation');</script>
Socket implementation relies on fixed-size internal send and receive buffers that are allocated as necessary from contiguous, non-paged pool memory. The default size of these buffers is 8k each. Incoming network data is placed into the internal receive buffer for the socket.

Winsock applications that use one of the peek methods, with either recv/WSARecv(..., MSG_PEEK) or ioctlsocket(FIONREAD, ...), to obtain the amount of data in the receive buffer is highly inefficient because the system must lock the data and count it. As the system does this, it is likely that the real-time network will still attempt to fill the buffer with more data. Peeking also does not remove the data, which would allow the buffer to reach its storage limit. As a result, this closes down the network data-flow rate and makes the entire process of data transmission inefficient.

Polling on a stream socket until a certain number of bytes or a "message" arrives is bad code. A stream socket, such as TCP, does not preserve message boundaries because it provides a data stream. As such, the largest message size an application can ever depend upon is one-byte in length. Code that uses peeking to wait until a complete "message" arrives might never succeed on stream-based protocols in Winsock where the data straddles multiple system buffer boundaries, due to design decisions. The peek operation will report the number of bytes up until the first buffer boundary. The bytes remaining in the other boundaries might never be reported, resulting in an incorrect count of data for code algorithms that depend upon the peek values to be accurate. Subsequent peek attempts will not reveal the "hidden" data, which can still be received from the buffers.

The best stream-based protocol socket implementation is to drain data immediately upon arrival into application-allocated buffer space. This allows the socket buffers to remain open to a steady network data-flow rate as the application parses the data, resulting in much better network performance.
一种解决方法
下列代码附加至您的 StdAfx . cpp 程序:
void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)
{
    if (wParam == 0 && lParam == 0)
        return;

    // Has the socket been closed?
    CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);

    // If yes, ignore message.
    if (pSocket != NULL)
        return;

    pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, FALSE);
    if (pSocket == NULL)
    {
        // Must be in the middle of an Accept call.
        pSocket = CAsyncSocket::LookupHandle(INVALID_SOCKET, FALSE);
        ASSERT(pSocket != NULL);
        pSocket->m_hSocket = (SOCKET)wParam;
        CAsyncSocket::DetachHandle(INVALID_SOCKET, FALSE);
        CAsyncSocket::AttachHandle(pSocket->m_hSocket, pSocket, FALSE);
    }

    int nErrorCode = WSAGETSELECTERROR(lParam);
    switch (WSAGETSELECTEVENT(lParam))
    {
    case FD_READ:
        {   // Comment out the data peeking code here
            //DWORD nBytes;
            //if (!pSocket->IOCtl(FIONREAD, &nBytes))
            //  nErrorCode = WSAGetLastError();
            //if (nBytes != 0 || nErrorCode != 0)
                pSocket->OnReceive(nErrorCode);
        }
        break;
    case FD_WRITE:
        pSocket->OnSend(nErrorCode);
        break;
    case FD_OOB:
        pSocket->OnOutOfBandData(nErrorCode);
        break;
    case FD_ACCEPT:
        pSocket->OnAccept(nErrorCode);
        break;
    case FD_CONNECT:
        pSocket->OnConnect(nErrorCode);
        break;
    case FD_CLOSE:
        pSocket->OnClose(nErrorCode);
        break;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值