• Windows网络编程经验小结

     

     

     

    转自:CSDN网友的强贴,其IDgdy119 (夜风微凉)

    1. 如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket

     

    BOOL bReuseaddr=TRUE;

     

    setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));

     

     

     

     

    2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:

     

    BOOL bDontLinger = FALSE;

     

    setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

     

     

     

     

    3.send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:

     


    int nNetTimeout=1000; //1

     

    //发送时限

     

    setsockopt(socketSOL_S0CKET,SO_SNDTIMEO(char *)&nNetTimeout,sizeof(int));

     


    //
    接收时限

     

    setsockopt(socketSOL_S0CKET,SO_RCVTIMEO(char *)&nNetTimeout,sizeof(int));

     

     

     

     

    4.send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);
    系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收
    数据量比较大,可以设置
    socket缓冲区,而避免了send(),recv()不断的循环收发:

     


    //
    接收缓冲区

     

    int nRecvBuf=32*1024;//设置为32K

     

    setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

     


    //
    发送缓冲区

     

    int nSendBuf=32*1024;//设置为32K

     

    setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

     

     

     

     

    5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响程序的性能:

     

    int nZero=0;

     

    setsockopt(socketSOL_S0CKET,SO_SNDBUF(char *)&nZero,sizeof(nZero));

     

     

     

     

    6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区)

     

    int nZero=0;

     

    setsockopt(socketSOL_S0CKET,SO_RCVBUF(char *)&nZero,sizeof(int));

     

     

     

     

    7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:

     

    BOOL bBroadcast=TRUE;

     

    setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

     

     

     

     

    8.client连接服务器过程中,如果处于非阻塞模式下的socketconnect()的过程中可以设置
    connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的作用,在阻塞
    的函数调用中作用不大
    )

     

    BOOL bConditionalAccept=TRUE;

     

    setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

     

     

     

     

    9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们

     

    一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序
    满足具体
    应用的要求(即让没发完的数据发送出去后在关闭socket)

     

    struct linger {

     

    u_short l_onoff;

     

    u_short l_linger;

     

    };

     

    linger m_sLinger;

     

    m_sLinger.l_onoff=1;//(closesocket()调用,但是还有数据没发送完毕的时候容许逗留)

     


    //
    如果m_sLinger.l_onoff=0;则功能和2.)作用相同;

     

    m_sLinger.l_linger=5;//(容许逗留的时间为5)

     

    setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

     


    Note:
    1.
    在设置了逗留延时,用于一个非阻塞的socket是作用不大的,最好不用;

     

    2. 如果想要程序不经历SO_LINGER需要设置SO_DONTLINGER,或者设置l_onoff=0

     

     

     

     

    10.还一个用的比较少的是在SDI或者是Dialog的程序中,可以记录socket的调试信息:

     

    (前不久做过这个函数的测试,调式信息可以保存,包括socket建立时候的参数,采用的

     

    具体协议,以及出错的代码都可以记录下来)

     

    BOOL bDebug=TRUE;

     

    setsockopt(s,SOL_SOCKET,SO_DEBUG,(const char*)&bDebug,sizeof(BOOL));

     

     

     

     

    11.附加:往往通过setsockopt()设置了缓冲区大小,但还不能满足数据的传输需求,

     

    我的习惯是自己写个处理网络缓冲的类,动态分配内存;下面我将这个类写出,希望对

     

    初学者有所帮助:

     

    //仿照String 改写而成

     

    //=====================================================================

     

    // 二进制数据,主要用于收发网络缓冲区的数据

     

    // CNetIOBuffer MFC CString 的源代码作为蓝本改写而成,用法与 CString 类似,

     

    // 但是 CNetIOBuffer 中存放的是纯粹的二进制数据,'' 并不作为它的结束标志。

     

    // 其数据长度可以通过 GetLength() 获得,缓冲区地址可以通过运算符 LPBYTE 获得。

     

    //=====================================================================

     

    // Copyright (c) All-Vision Corporation. All rights reserved.

     

    // Module: NetObject

     

    // File: SimpleIOBuffer.h

     

    // Author: gdy119

     

    // Email : 8751webmaster@126.com

     

    // Date: 2004.11.26

     

    //=====================================================================

     

    // NetIOBuffer.h

     

    #ifndef _NETIOBUFFER_H

     

    #define _NETIOBUFFER_H

     

    //=====================================================================

     

    #define MAX_BUFFER_LENGTH 1024*1024

     

    //=====================================================================

     

    //主要用来处理网络缓冲的数据

     

    class CNetIOBuffer

     

    {

     

    protected:

     

    LPBYTE m_pbinData;

     

    int m_nLength;

     

    int m_nTotalLength;

     

    CRITICAL_SECTIONm_cs;

     

    void Initvalibers();

     

    public:

     

    CNetIOBuffer();

     

    CNetIOBuffer(const LPBYTE lbbyte, int nLength);

     

    CNetIOBuffer(const CNetIOBuffer&binarySrc);

     

    virtual ~CNetIOBuffer();

     

    //=====================================================================

     

    BOOL CopyData(const LPBYTE lbbyte, int nLength);

     

    BOOL ConcatData(const LPBYTE lbbyte, int nLength);

     

    void ResetIoBuffer();

     

    int GetLength() const;

     

    BOOL SetLength(int nLen);

     

    LPBYTE GetCurPos();

     

    int GetRemainLen();

     

    BOOL IsEmpty() const;

     

    operator LPBYTE() const;

     

    static GetMaxLength() { return MAX_BUFFER_LENGTH; }

     

    const CNetIOBuffer& operator=(const CNetIOBuffer& buffSrc);

     

    };

     

    #endif //

     

    // NetOBuffer.cpp: implementation of the CNetIOBuffer class.

     

    //=====================================================================

     

    #include "stdafx.h"

     

    #include "NetIOBuffer.h"

     

    //=====================================================================

     

    //=====================================================================

     

    // Construction/Destruction

     

    CNetIOBuffer::CNetIOBuffer()

     

    {

     

    Initvalibers();

     

     

    }

     

    CNetIOBuffer::CNetIOBuffer(const LPBYTE lbbyte, int nLength)

     

    {

     

    Initvalibers();

     

    CopyData(lbbyte, nLength);

     

    }

     

    CNetIOBuffer::~CNetIOBuffer()

     

    {

     

    delete []m_pbinData;

     

    m_pbinData=NULL;

     

    DeleteCriticalSection(&m_cs);

     

     

    }

     

    CNetIOBuffer::CNetIOBuffer(const CNetIOBuffer&binarySrc)

     

    {

     

     

    Initvalibers();</

  • 8.2.5完成端口模型
    创建完成端口对象 CreateCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, DWORD CompletionKey, DWORD NumberOfConcurrentThreads);
    CompletionKey:则指定要与某个特定套接字句柄关联在一起的“单句柄数据”
    1.工作者线程与完成端口
    StartWinsock();
    //step 1
    //create an IO completion port.

    CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0,0);

    //step 2;
    //Determine how many processors are on the system

    GetSystemInfo(&SystemInfo);

    //Step3
    //create worker threads based on the number of processors available on the system .For this simple case, we create one worker thread for each processor.
    for(i=0; i < SystemInfo.dwNumberOfProcessors; i++)
    {
    HANDLE ThreadHandle;
    //create a server worker thread and pass the completion port to the thread, Note:the ServerWorkerThread procedure is not defined in this listing.
    ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,0, &ThreadID);

    //close the thread handle
    CloseHandle(ThreadHandle);
    }

    //step 4
    //create a listening socket
    Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

    InternetAddr.sin_family = AF_INET;
    InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    InternetAddr.sin_port = htons(5150);
    bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));

    //prepare socket for listening
    listen(Listen, 5);

    while (TRUE)
    {
    //step 5
    //Accept connections and assign to completion port.
    Accept = WSAAccept(Listen, NULL, NULL, NULL, 0);

    //step 6
    //create per-handle data information structure to associate with the socket
    PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));

    printf("Socket number %d connected\n",Accept);
    PerHandleData->Socket = Accept;

    //step 7
    //Associate the accepted socket with the completion port.
    CreateIoCompletionPort((HANDLE)Accept,CompletionPort, (DWORD)PerHandleData,0);

    //创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该纪录。

    //step 8
    //start processing IO on the accepted socket. post oen or more WSASend() or WSARecv() calls on the socket using overlapped IO
    WSARecv(...)''
    }

    2.完成端口和重叠IO
    需要由我们的应用程序负责在以后的某个时间,通过一个OVERLAPPED结构,来接收调用的结果。
    BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred, LPDWORD lpCompletionKey, LPOVERLAPPED* lpOverlapped, DWORD dwMilliseconds);

    3.单句柄数据和单IO操作数据
    typedef struct
    {
    OVERLAPPED Overlapped;
    WSABUF DataBuf;
    CHAR Buffer[DATA_BUFSIZE];
    bool OperationType;
    } PER_IO_OPERATION_DATA;

    DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
    {
    HANDLE CompletionPort = (HANDLE).CompletionPortID;
    DWORD ByteTransferred;
    LPOVERLAPPED Overlapped;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;
    DWORD SendBytes, RecvBytes;
    DWORD Flags;

    while (TRUE)
    {
    //Wait for IO to complete on any socket associated with the completion port
    GetQueuedCompletionStatus(CompletionPort,&ByteTransferred, (LPDWORD)&PerHandleData,(LPOVERLAPPED*)&PerIoData, INFINITE);

    //socket and clean up the per-handle data and per-io opertion data associated with the socket
    if (BytesTransferred ==0&&(PerIoData->OperationType == RECV_POSTED||PerHandleData->OperationType == SEND_POSTED))
    {
    //A zero bytesTransferred indicates that the socket has been closed by the peer, so you should close the socket.
    //Note: Per-handle data was used to reference the socket associated withe IO operation.
    closesocket(PerHandleData->Socket);

    GlobalFree(PerHandleData);
    GlobalFree(PerIoData);
    continue;
    }

    //service the completed IO request, You can determine which IO request has just completed by looking at the OperationType field contained in the
    //the per-io operation data.
    if (PerIoData->OperationType==RECV_POSTED)
    {
    //Do sth with the received data in perIoData->Buffer
    }

    //post another WSASend or WSARecv operation .
    //As an example , we will post another WSARecv() IO operation

    Flags = 0;

    //Set up the per-IO operation data for the next overlapped call
    ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED));

    PerIoData->DataBuf.len = DATA_BUFSIZE;
    PerIoData->DataBuf.buf = PerIoData->Buffer;
    PerIoData->OperationType = RECV_POSTED;

    WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf),1,&RecvBytes, &Flags, &(PerIoData->Overlapped),NULL);
    }
    }

     

    如何正确地关闭IO端口-特别是同时运行一个或者多个线程,在几个不同的套接字上执行IO操作的时候,要避免的一个重要的问题是在进行重叠IO操作的同时,
    强行释放一个OVERLAPPED结构。要想避免出现这种情况,最好的办法是针对每个套接字句柄,调用closesocket,任何尚未进行的重叠IO操作都会完成。
    一旦所有套接字句柄都已经关闭,便需在完成端口上终止所有的工作者者线程的运行,PostQueuedCompletionStatus.

  • 2006-11-27

    8.2.3WSAEventSelect - [winsock]

    8.2.3WSAEventSelect
    应用程序在一个或者多个套接字上,接收以事件为基础的网络事件通知。网络事件投递至一个事件对象句柄。
    事件通知
    WSAVENT WSACreateEvent(void);
    BOOL WSAResetEvent(WSAEVENT hEvent);
    BOOL WSACloseEvent(WSAEVENT hEvent);
    int WSAEventSelect(SOCKET S, WSAEVENT hEventObject, long lNetworkEvents);

    DWORD WSAWaitForMultipleEvents(DWORD cEvents, const WSAEVENT FAR* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);

    Index = WSAWaitForMultipleEvents();
    MyEvent = EventArray[index - WSA_WAIT_EVENT_0];
    int WSAEnumNetworkEvents(SOCKET s, WSAEVET hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);

    SOCKET Socket[WSA_MAXIMUM_MAIT_EVENTS];
    WSAEVENT Event[WSA_MAXIMUM_MAIT_EVENTS];
    SOCKET Accept, Listen;
    DWORD EventTotal = 0;
    DWORD Index;

    //Set up a TCP socket for listening on port 5150;
    Listen = socket(PF_INET, SOCKET_STREAM, 0);

    InternetAddr.sin_family = AF_INET;
    InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    InternetAddr.sin_port = htons(5105);

    bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));

    NewEvent = WSACreateEvent();

    WSAEventSelect(Listen, NewEvent, FD_ACCEP|FD_CLOSE);

    listen(Listen, 5);

    Socket[EventTotal] = Listen;
    Event[EventTotal] = NewEvent;
    EventTotal++;

    while(TRUE)
    {
    //Wait for network events on all sockets
    Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);

    WSAEnumNetworkEvents(SocketArray[Index - WSAWAIT_EVENT_0],EventArray[Index_WSA_WAIT_EVENT_0], &NetworkEvents);

    //check for FD_ACCEPT messages
    if(NetWorkEvents.lNetworkEvents&FD_ACCEPT)
    {
    if(NetworkEvent.iErrorCode[FD_ACCEPT_BIT]!=0)
    {
    printf("FD_ACCEPT failed with error%d\n", NetWorkEvent.iErrorCode[FD_ACCEPT_BIT]);
    break;
    }

    //Accept a new connection and it to the socket and event lists
    Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL, NULL);

    //We can not process more than WSA_MAXIMUM_WAIT_EVENTS sockets, so close the accepted socket
    if(EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
    {
    printf("Too many connecttions");
    closesocket(Accept);
    break;
    }

    NewEvent = WSACreateEvent();

    WSAEventSelect(Accept, NewEvent, FD_READ|FD_WRITE|FD_CLOSE);

    Event[EventTotal] = NewEvent;
    Socket[EventTotal] = Accept;
    EventTotal++;

    printf("socket &d connected\n",ACCept);
    }

    //Process FD_READ notification
    if(NewWorkEvent.iErrorCode[FD_READ_BIT]!0)
    {
    if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
    {
    printf("FD_READ failed with error %dn", NetworkEvent.iErrorCode[FD_READ_BIT]);
    break;

    }

    //Read data from socket
    recv(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
    }

    //Process FD_WRITE notification
    if(NetworkEvents.lNetworkEvents&FD_WRITE)
    {
    if(NetworkEvent.iErrorCode[FD_WRITE_BIT]!=0)
    {
    printf("FD_WRITE failed with error %d\n",
    NetworkEvents.iErrorCode[FD_WRITE_BIT]);
    break;
    }

    send(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
    }

    if(NetworkEvents.lNetworkEvents&FD_CLOSE)
    {
    if(NetworkEvent.iErrorCode[FD_CLOSE_BIT]!=0)
    {
    printf("FD_CLOSE failed with error &d\n",
    NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
    break;
    }
    }

    closesocket(Socket[index - WSA_WAIT_EVET_0]);

    //Remove socket and associated event from the socket and evnet array and decrement eventtatal
    CompressArrays(Event, socket,&EventTotal);
    }
    }

  • 第8章 WinsockIO方法
    套接字模式:锁定和非锁定
    8.1套接字模式
    生产者-消费者
    8.1.2非锁定模式
    SOCKET s;
    unsigned long ul = 1;
    int nRet;

    s = socket(AF_INET, SOCK_STREAM, 0);
    nRet = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
    if(nRet == SOCKET_ERROR)
    {
    ....
    }

    8.2.1 select模型
    利用这个函数,我们判断套接字上是否存在数据,或者能否向一个套接字写入数据。
    int select(int nfds,
    fd_set FAR* readfds,
    fd_set FAR* writefds,
    fd_set FAR* exceptfds,
    const struct timeval FAR* timeout);

    FD_CLR(s,*set);从set中删除套接字s。
    FD_ISSET(s,*set);检查s是否在set中。
    FD_SET(s,*set);将s加入集合set。
    FD_ZERO(*set);将set初始化。

    SOCKET s;
    fd_set fdread;
    int ret;

    //create a socket and accept a connection

    //Manage I/O on the socket
    while(1)
    {
    FD_ZERO(&fdread);

    FD_SET(s,&fdread);

    if((ret = select(0, &fdread, NULL, NULL, NULL)== SOCKET_ERROR)
    { //ERROR }

    if(ret >0)
    {
    if(FD_ISSER(s, &fdread))
    {
    //a read event has occurred on socket s.
    }
    }

    }

  • 第7章 Winsock基础
    7.1Winsock的初始化
    int WSAStartup(WORD wVersionRequested, LPWSASATA lpWSData);
    7.2错误检查和控制
    int WSAGetLastError(void);
    7.3面向连接的协议
    7.3.1服务器API函数
    1.bind 将指定的套接字同一个已经知道的地址绑定在一起。
    2.listen(SOCKET s, int backlog);
    3.accept和WSAAccept(SOCKET s, struct socket FAR*addr, int FAR* addrlen);
    addr 返回客户机的ip地址信息
    accept返回一个新的套接字,它对应于已经接受的那个客户机连接,对于该客户机后续的所有操作,都使用这个新套接字。
    原来的套接字仍然用于接受客户机连接而处于监听模式。

    int CALLBACK ConditionFunc(
    LPWSABUF lpCallerId,
    LPWSABUF lpCallerData,
    LPQQS lpSOOS,
    LPQOS lpGOOS,
    LPWSABUF lpCalleeId,
    LPWSABUF lpCalleeData,
    GROUP FAR*g,
    DWORD dwCallbackData);

    lpCallerId: 指定建立连接的协议 包含建立连接的那个客户机的IP地址。
    lpCallerData:包含了随连接一道,由客户机发出的任何连接数据。
    lpSQOS,lpGQOS:对客户机请求的任何一个服务质量参数进行指定。
    lpCalleeId:结构中包含与客户机需要与之连接的本地地址。
    服务器将传递给条件的参数处理完之后,必须指出CF_ACCEPT, CF_REJECT , CF_DEFER.

    7.3.2客户机API函数
    1)创建套接字
    2)解析服务器名字(以基层协议为准)
    3)connect WSAConnect初始化一个链接

    TCP状态
    每个套接字的初始化都是closed,如果客户机初始化一个链接就会向服务器发送一个SYN包,同时将客户机的套接字状态设置为SYN_SENT。服务器收到SYNB包后,就会发送一个SYN-ACK包
    。作为客户机,需要一个ACK包对它作出反应。此时客户机的套接字就会变成ESTABLISHED状态。如果服务器一直不发送SYN-ACK包,客户机就会超时并返回CLOSED状态。

    若一个服务器套接字同一个本地接口和端口绑定起来,并在上面监听,那么套接字的状态是LISTEN。客户机试图与之连接时,服务器会收到一个SYN包,并用一个SYN-ACK包做出相应。服务器套接字
    变成SYN-RCVD,最后客户机一个ACK包,令服务器套接字变成ESTABLISHED状态。

    主动关闭套接字closesocket,shutdown时会向对方发送一个FIN包,而且套接字的专题变为FIN-WAIT-1。正常情况下通信对方会回应一个ACK包,套接字的状态变成FIN-WAIT-2。如果对方也关闭了连接,便会发出一个FIN包。我的机器则会相应一个ACK包,并将已方套接字的状态至为TIME-WAIT。
    7.3.3数据传输
    所有关系到收发数据的缓冲都属于简单哦案的char类型。WideCharToMultiByte
    int send(SOCKET S,
    const char FAR* buf,
    int len,
    int flags);
    带外数据:对已经建立链接的流套接字上的应用来说,如果需要发送的数据比流上的普通数据重要得多,便可将这些重要的数据标记为到外数据out-of-band OOB.位于连接另一端的应用可通过一个独立的逻辑信道来接收和处理OOB数据。
    int recv(SOCKET s, char FAR* buf, int len, int flags);

    7.3.4流协议
    大多面向连接的协议同时也是流式传输协议。不能保证对请求的数据量进行读取或者写入。也就是当send recv时不能保证数据的全部发送和接收。

    char sendbuff[2048];
    int nBytes = 2048;
    int nLeft;
    int idx;

    nLeft = nBytes;
    idx = 0;
    while(nLeft>0)
    {
    ret = send(s, &sendbuff[idx], nLeft,0);
    if(ret==SOCKET_ERROR)
    {
    //ERROR
    }
    nLeft -= ret;
    idx += ret;
    }
    7.3.5中断连接
    1.shutdown(SOCKET s, int how); SD_REVEIVE SD_SEND SD_BOTH 表示不再接收,发送和收发。
    2.closesocket(SOCKET s); 释放套接字描述符相关联的资源,包括丢弃所有等候处理的数据。

    7.4无连接协议
    7.4.1接收端
    int recvfrom(SOCKET S, char FAR* buf, int len, int flags, struct socketaddr FAR* from, int FAR* fromlen);
    7.4.2发送端
    int sendto(SOCKET s, const char FAR* buf, int len, int flags, const struct socketaddr FAR* to, int tolen);

    7.4.3基于消息的协议
    面向连接的协议同时也是流式协议,无连接协议几乎都是基于消息的。

    7.5其它API函数
    1.getpeername() 用于获得通信方的套接字地址信息。
    2.getsockname()返回的是指定套接字的本地接口的地址信息。

    7.6Windows CE