WinSock

TCP

                    +---------------------+
                    |     higher-level    |
                    +---------------------+
                    |        TCP          |
                    +---------------------+
                    |  internet protocol  |
                    +---------------------+
                    |communication network|
                    +---------------------+
                    Figure.Protocol Layering

TCP在网络OSI的七层模型中的第四层(Transport层),第四层的数据叫Segment
IP在第三层(Network层),在第三层上的数据叫Packet
ARP在第二层(Data Link层),在第二层上的数据,我们叫Frame。
程序的数据首先会打包到TCP的Segment中,然后TCP的Segment会打包到IP的Packet中,然后再打包到以太网Ethernet的Frame中,传到对端后,各个层解析自己的协议,然后把数据交给更高层的协议处理。
http://coolshell.cn/articles/11564.html

winsock提供了套接字模式和套接字的I/O模型

套接字模式:用于当一个套接字被调用时,决定调用函数的阻塞行为,有阻塞和非阻塞两种工作模式。
套接字的I/O模型:描述了一个应用程序如何对套接字上的I/O进行管理。
套接字模式和套接字的I/O模型是两个无关的不同概念。

  • 阻塞模式
  • 非阻塞模式
  • Select模型
  • WSAAsyncSelect模型(依赖窗口)
  • WSAEventSelect模型
  • 重叠I/O模型(真正意义上的异步I/O模型)
  • 完成端口模型

WSAStartup和WSACleanup可多次调用,但必须成对使用。
WSAStartup调用失败时,不能通过调用WSAGetLastError()来得错误代码。

Windows Sockets中有3种有关IP4地址的结构:in_addr, sockaddr_in, sockaddr.

在winsock中,APP通过SOCKADDR_IN结构来指定IP和Port信息,
其中的sin_zero只充当填充项,以使SOCKADDR_IN结构和SOCKADDR结构的长度一样。
unsigned long inet_addr(const char FAR* cp)用于将一个点分IP转换成一个32位无符号长整数(按网络字节顺序)。

在computer中把IP & Port指定成多字节数时,是按主机字节(host-byte)顺序进行的,
但在网络上指定IP & Port,标准指定必须使用big-endian即网络字节(network-byte)顺序表示。

SOCKADDR_IN包含了IN_ADDR, 而SOCKADDR_IN和SOCKADDR可以相互替换。
比如,当调用bind()时,将SOCKADDR_IN强制转换成SOCKADDR作为第二个参数。
这里写图片描述

阻塞模式:用于少量数据传输的简单网络程序

阻塞模式下,在I/O操作完成前,执行操作的函数一直等待不会返回,该函数所在的线程会阻塞在这里。
可能阻塞套接字的winsock API分为4类:

  1. 输入OP:recv, recvfrom, WSARecv, WSARecvfrom.
    以阻塞套接字为参数调用这些API接收数据,如该套接字缓冲区内没有数据可读,则调用线程被阻塞。

  2. 输出OP:send, sendto, WSASend, WSASendto.
    以阻塞套接字为参数调用这些API发送数据,若套接字缓冲区没有可用空间,线程会一直睡眠,直到有空间。

  3. 接受连接:accept, WSAAccept.
    以阻塞套接字为参数调用这些API,将等待对方的连接请求,如果此时没有连接请求,线程就进入睡眠状态。

  4. 外出连接:connect, WSAConnect.
    对于TCP连接,客户端以阻塞套接字为参数,调用这些API向服务器发起连接,该函数在收到服务器应答前,不会返回;即意味着总会等待至少从客户端到服务器的一次往返时间。

非阻塞模式:

非阻塞模式下套接字函数立即返回而不管I/O是否完成。该函数所在的线程继续运行。默认情况下创建的套接字都是阻塞模式的,可以调用ioctlsocket进行设置,或者调研WSAAsyncSelect/WSAEventSelect套接字会自动设置为非阻塞模式。非阻塞模式调用,若未准备就绪,会返回WSAEWOULDBLOCK,含义如下:

  1. 输入OP:recv, recvfrom, WSARecv, WSARecvfrom.
    –接收缓冲区没有接收到数据
  2. 输出OP:send, sendto, WSASend, WSASendto.
    –发送缓冲区此时不可用
  3. 接受连接:accept, WSAAccept.
    –应用程序没有收到连接请求
  4. 外出连接:connect, WSAConnect.
    –连接未能立即完成
  5. closesocket
    –通常情况下意味着应用程序使用SO_LINGER选项并且设置了一个非0的超时值,调用了setsocketopt函数。

Select模型

WSAAsyncSelect模型(必须创建窗口)

WSAEventSelect模型

重叠I/O模型

重叠I/O是Windows的一项技术,WinAPP通过该技术请求OS为其传输数据,并且在数据传输结束后通知APP,使得APP能在进行I/O操作过程中,继续处理其他事务。Windows提供如下API支撑起该技术:

hFile = CreateFile(dwFlagsAndAttributes=FILE_FLAG_OVERLAPPED...)
ReadFile(hFile, lpOverlapped...)
WriteFile(hFile, lpOverlapped...)
WaitForSingleObject/WaitForMultipleObjects(hFile...)//等待I/O
GetOverlappedResult(hFile, lpOverlapped...)//检查确定I/O操作的结果

完成端口模型

套接字完成端口模型
1.创建完成端口
2.创建服务线程,通常服务线程数量为CPU数量的2倍,服务线程开始工作后有3种状态
2.1 等待状态:在没有I/O操作完成通知包被投递到完成端口前,服务线程在完成端口上等待
2.2 服务状态:当I/O操作完成通知包到达时,服务线程按照LIFO方式被唤醒,开始为客户端提供服务;完成服务后,服务线程回到完成端口继续等待
2.3 阻塞状态:为客户端提供服务的线程,调用了Sleep()之类的函数,线程进入阻塞状态
3.将套接字与完成端口关联在一起
4.调用输入输出函数,发起重叠I/O操作:WSASend(), WSASendTo() 发送数据;WSARecv(), WSARecvFrom()接收数据
5.在服务线程中,在完成端口上等待重叠I/O操作结果
6.取消异步操作:BOOL CancelIo(HANDLE hFile)
7.投递完成通知包: PostQueuedCompletionStatus()

1.创建完成端口

HANDLE hIOComportionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

2.创建服务线程

GetSystemInfo(&SystemInfo);
DWORD WINAPI CMyIOCPServerDlg::ServiceThread(void *pParam/*带完成端口*/)
for (int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
{
    if ((m_hThread[m_nThreadNum++] = CreateThread(NULL,
        0,
        ServiceThread,
        this,//带完成端口
        0,
        &dwThreadID)) == NULL)
    {
        AfxMessageBox(_T("create ServiceThread failed!"));
        return;
    }
}

3.关联套接字与完成端口

typedef struct _completionKey{
    SOCKET s;
    SOCKADDR_IN clientAddr;
}COMPLETIONKEY, *PCOMPLETIONKEY;
//套接字
SOCKADDR_IN addr;//服务器地址
int clientLen = sizeof(addr);
SOCKET sAccept = accept(sListen/*服务器监听套接字*/, (SOCKADDR*)&addr, &addr);
//完成键
pCompleKey->s = sAccept;
pCompleKey->clientAddr = addr;
//关联
h = CreateIoCompletionPort((HANDLE)sAccept, hIOCompletionPort, (DWORD)pCompleKey, 0);

4.调用输入输出函数,发起重叠I/O操作

//IO操作数据结构
typedef struct _io_operation_data {
    WSAOVERLAPPED   overlapped;//重叠结构
    WSABUF          dataBuf;
    CHAR            buffer[DATA_BUF_SIZE];//接收数据缓冲区
}IO_OPERATION_DATA, *PIO_OPERATION_DATA;
//IO操作数据
PIO_OPERATION_DATA pIoData;
pIoData->dataBuf.buf = pIoData->buffer;
pIoData->dataBuf.len = DATA_BUF_SIZE;
ZeroMemory(&pIoData->overlapped, sizeof(pIoData->overlapped));
//接收数据
if (WSARecv(sAccept, &(pIoData->dataBuf), 1, &recvBytes, &flags, &(pIoData->overlapped), NULL) == SOCKET_ERROR)
{
    if (WSAGetLastError() != ERROR_IO_PENDING)//是否成功发起异步接收数据操作
    {
        //WSARecv 失败
    }
}

5.等待重叠I/O操作结果

BOOL bRet = GetQueuedCompletionStatus(hIOComPort, &dwNumberOfBytedTrans, (LPDWORD)pCompletionKey, &pOverlapped, THREAD_SLEEP_TIME);
if (bRet)
{
    //成功,开始处理数据
}
else
{
    int nErrCode = WSAGetLastError();
    if (NULL != pOverlapped)//失败的IO操作
    {
        //检查错误代码
    }
    else if (WAIT_TIMEOUT == nErrCode)
    {
        continue;//超时
    }
    else
    {
        //检查错误代码
    }
}

6.取消异步操作: 当关闭套接字应用程序时,如果系统中还有未完成的异步操作,那么应用程序可以调用CancelIo来取消等待执行的异步操作

7.投递完成通知包:当服务线程退出,应用程序可以调用PostQueuedCompletionStatus函数向服务线程发送一个特殊的完成通知包,服务线程接收到这个通知包后,线程退出

PostQueuedCompletionStatus(hIOComPort, 0, (DWORD)NULL, NULL);//发送服务线程退出通知包
//等待重叠IO操作结果
BOOL bRet = GetQueuedCompletionStatus(hIOComPort, &dwNumberOfBytedTrans, (LPDWORD)pCompletionKey, &pOverlapped, THREAD_SLEEP_TIME);
if (NULL == pOverlapped && NULL == pCompletionKey)
{
    //服务线程退出
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值