面试总结(9.15)

1. Socket编程的几种模型:

        在Windows平台上构建服务器应用:

Socket根据I/O模型的不同,可以分为阻塞模式和非阻塞模式:

        阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序。套接字默认为阻塞模式。可以通过多线程技术进行处理。

        非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权。这种模式使用起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误,但功能强大。

 

Socket可以对应五种I/O模型:

        选择模型(Select):之所以称其为“Select模型”,是由于它的“中心思想”便是利用Select函数实现对I/O的管理。避免应用程序在套接字调用过程中被无辜“锁定”,采取一种有序的方式,同时进行对多个套接字的管理。

       需要设置多线程,在一个线程中调用Select来确定Socket是否有数据读取或写出。     

        ret = select(0,&fdread, NULL, NULL, &tv);//每隔一段时间,检查可读性的套接口

        第一个参数nfds是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,在Windows中这个参数的值无所谓,可以设置不正确。第二个参数readfds为可读集合指针,指向一组等待可读性检查的套接口。第三个参数writefds为可写集合指针,指向一组等待可写性检查的套接口。第四个参数exceptfds为一组等待错误检查的套接口的集合的指针。第五个参数timeout为select()最多等待时间,对阻塞操作则为NULL。

 

        异步选择(WSAAsyncSelect):利用这个模型,应用程序可在一个套接字上接收以Windows消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。

WSAAsyncSelect(sListen,hwnd, WM_SOCKET, FD_ACCEPT);

        第一个参数为服务器端的监听socket,第二个参数为消息队列的窗口句柄,第三个参数为消息的类型(需要自定义一个事件类型),第四个参数为位屏蔽码,用于指明感兴趣的网络事件集合。

 

        事件选择(WSAEventSelect):将每个套接字都和一个WSAEVENT对象对应起来,并且在关联的时候指定需要关注的哪些网络事件。一旦在某个套接字上发生了我们关注的事件(FD_READ和FD_CLOSE),与之相关联的WSAEVENT对象被Signaled。程序定义了两个全局数组,一个套接字数组,一个WSAEVENT对象数组,其大小都是MAXIMUM_WAIT_OBJECTS(64),两个数组中的元素是一一对应。

        同样存在问题:不能无条件的调用accept,支持的并发连接数有限。解决方法是将套接字按MAXIMUM_WAIT_OBJECTS分组,每MAXIMUM_WAIT_OBJECTS个套接字一组,每一组分配一个工作者线程;或者采用WSAAccept代替accept,并回调自己定义的ConditionFunction

       接受了客户端连接之后,要加入到套接字和对应事件集中去。

        // 将socket和网络事件接合

        g_CliSocketArr[g_iTotalConn]= sClient;                   //接受连接的套接口

        g_CliEventArr[g_iTotalConn]= WSACreateEvent();  //返回事件对象句柄

      

        //在套接口上将一个或多个网络事件与 事件对象关联在一起

        WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn],FD_READ | FD_CLOSE);

        第一个参数为对应的socket,第二个参数为该Socket对应的事件,第三个参数为该Socket中关注的事件。

 

        ret= WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000,FALSE);

        第一个参数为数组中的句柄数目,第二个参数为在套接口上查询与事件对象关联的网络事件,第三个参数为指向一个事件对象句柄数组的指针,第三个参数有几个事件才返回,T表示都进才返回;F表示一进就返回,第四个参数为超时间隔,第五个参数表示是否执行完成例程

 

        WSAEnumNetworkEvents( g_CliSocketArr[index], g_CliEventArr[index],&NetworkEvents);

       第一个参数为要枚举的Socket,第二个参数为其对应的事件,第三个参数为返回的网络事件。

      

        这个模型与上述其他模型不同的是它使用Winsock2提供的异步I/O函数WSARecv。在调用WSARecv时,指定一个WSAOVERLAPPED结构,这个调用不是阻塞的,也就是说,它会立刻返回。一旦有数据到达的时候,被指定的WSAOVERLAPPED结构中的hEvent被Signaled。

 

        Winsock2的发布使得Socket I/O有了和文件I/O统一的接口,这样可以通过使用Win32文件操纵函数ReadFile和WriteFile来进行Socket I/O。用于普通文件I/O的重叠I/O模型和完成端口模型对Socket I/O也适用了。这些模型的优点是可以达到更佳的系统性能,但是实现较为复杂。


        重叠I/O(Overlapped I/O):有两种实现方式,一种是事件通知方式实现的重叠I/O模型,另外一种可以使用完成例程方式实现重叠I/O模型。

        使用事件通知方式实现重叠I/O模型

 

       定义重叠结构体:

        typedef struct

        {

                WSAOVERLAPPED overlap;

                WSABUF Buffer;

                char szMessage[MSGSIZE];

                DWORD NumberOfBytesRecvd;

                DWORD Flags;

        }PER_IO_OPERATION_DATA,*LPPER_IO_OPERATION_DATA;

      

       接受了客户端连接之后,要加入到套接字和对应事件集中去,同时还需要在对应的重叠结构体变量中初始化。

        // 将socket和网络事件接合

        g_CliSocketArr[g_iTotalConn]= sClient;                   // 接受连接的套接口

        g_CliEventArr[g_iTotalConn]= WSACreateEvent();  // 返回事件对象句柄

        g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];    // 异步操作结构体

      

        调用WSARecv(    g_CliSocketArr[g_iTotalConn],

                                        &g_pPerIODataArr[g_iTotalConn]->Buffer,

                                        1,

                                        &g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,

                                        &g_pPerIODataArr[g_iTotalConn]->Flags,

                                        &g_pPerIODataArr[g_iTotalConn]->overlap,

                                        NULL);

        启动一个异步操作。

        第一个参数为Socket标识符,第二个参数为接受数据缓存(在重叠结构体中),第三个参数为缓存数量,第四个参数存储的是返回的接收数据量,第五个参数为标识,第六个参数为绑定重叠结构,第七个参数为完成例程中用到的参数。

       调用WSAWaitForMultipleEvents()等待Socket对应的事件发生。

        ret= WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000,FALSE);

        第一个参数为数组中的句柄数目,第二个参数为在套接口上查询与事件对象关联的网络事件,第三个参数为指向一个事件对象句柄数组的指针,第三个参数有几个事件才返回,T表示都进才返回;F表示一进就返回,第四个参数为超时间隔,第五个参数表示是否执行完成例程


        调用WSAGetOverlappedResult()方法查找重叠结构的结果:

        WSAGetOverlappedResult(g_CliSocketArr[index], &g_pPerIODataArr[index]->overlap,

        &cbTransferred,TRUE, &g_pPerIODataArr[g_iTotalConn]->Flags);

        第一个参数为socket标识符,第二参数为对应的叠加结构体,第三个参数为本次传输的数据量的大小,第四个参数为是否等待挂起的重叠操作结束。第五个参数为完成状态的附加标志位。      

       操作完成后,重新启动一个异步的操作

 

        使用完成端口实现重叠I/O模型

        只是将事件通知改为了完成端口,不需要每次检测是否有重叠结果。

        用完成例程来实现重叠I/O比用事件通知简单得多。在这个模型中,主线程只用不停的接受连接即可;辅助线程判断有没有新的客户端连接被建立,如果有,就为那个客户端套接字激活一个异步的WSARecv操作,然后调用SleepEx使线程处于一种可警告的等待状态,以使得I/O完成后CompletionROUTINE可以被内核调用。如果辅助线程不调用SleepEx,则内核在完成一次I/O操作后,无法调用完成例程(因为完成例程的运行应该和当初激活WSARecv异步操作的代码在同一个线程之内)。

 

        完成端口(CompletionPort):“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。

      

       定义重叠结构体:

        typedef struct

        {

                WSAOVERLAPPED overlap;

                WSABUF Buffer;

                char szMessage[MSGSIZE];

                DWORD NumberOfBytesRecvd;

                DWORD Flags;

        }PER_IO_OPERATION_DATA,*LPPER_IO_OPERATION_DATA;

 

        创建完成端口

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

        创建工作者线程

        GetSystemInfo(&systeminfo);

        for (i = 0; i< systeminfo.dwNumberOfProcessors; i++)

        {

                CreateThread(NULL, 0, WorkerThread, CompletionPort, 0,&dwThreadId);

        }

      

        将最新到达的客户端Socket与完成端口关联

        CreateIoCompletionPort((HANDLE)sClient,CompletionPort, (DWORD)sClient, 0);

      

        为新的连接启动一个异步的操作

        lpPerIOData =(LPPER_IO_OPERATION_DATA)HeapAlloc( GetProcessHeap(),

        HEAP_ZERO_MEMORY,sizeof(PER_IO_OPERATION_DATA));

        lpPerIOData->Buffer.len= MSGSIZE;

        lpPerIOData->Buffer.buf= lpPerIOData->szMessage;

        lpPerIOData->OperationType= RECV_POSTED;

        WSARecv(sClient,&lpPerIOData->Buffer, 1, &lpPerIOData->NumberOfBytesRecvd,

             &lpPerIOData->Flags, &lpPerIOData->overlap, NULL);

 

        获得完成状态,根据状态进行操作:

        GetQueuedCompletionStatus(CompletionPort, &dwBytesTransferred, &sClient, (LPOVERLAPPED *)&lpPerIOData, INFINITE);

 

        首先,说说主线程:

        1. 创建完成端口对象

        2. 创建工作者线程(线程数量按照CPU个数来确定,这样能达到最佳性能)

        3. 创建监听套接字,绑定,监听,然后程序进入循环

        4. 在循环中,我做了以下几件事情:

        (1). 接受一个客户端连接

        (2). 将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort,但这次的作用不同),注意,按道理来讲,此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键,一般来讲,程序都是传递一个单句柄数据结构的地址,该单句柄数据包含了和该客户端连接有关的信息,由于我们只关心套接字句柄,所以直接将套接字句柄作为完成键传递;

        (3). 触发一个WSARecv异步调用,这次又用到了“尾随数据”,使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后,还有操作类型等重要信息。

 

        在工作者线程的循环中:

        1. 调用GetQueuedCompletionStatus取得本次I/O的相关信息(例如套接字句柄、传送的字节数、单I/O数据结构地址等)

        2. 通过单I/O数据结构找到接收数据缓冲区,然后将数据原封不动的发送到客户端

        3. 再次触发一个WSARecv异步操作

 

 

        Linux下Socket模型

 

        Linux下的Socket也是分为阻塞式和非阻塞式两种。

      

       使用的I/O模型有select,信号驱动I/O模型,两种方式,其中select与Windows下的select类似。

epoll是多路复用I/O(I/O Multiplexing) 中的一种方式,epoll模型所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048。

 

        传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。epoll不存在这个问题,它只会对“活跃”的socket进行操作--这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。

 

2. MFC封装的Socket模型

       MFC封装的Socket有两个,一个是CAsyncSocket,一个是CSocket,其中CSocket是CAsyncSocket的派生类。

      

        CAsyncSocket类

        一个异步非阻塞Socket封装类,CAsyncSocket::Create()的一个参数指明了你要处理的Socket事件。关心的事件被指定以后,这个Socket默认就被用作了异步方式。那么CAsyncSocket内部到底是如何将事件交给你的呢?

        CAsyncSocket的Create()函数,除了创建了一个SOCKET以外,还创建了CSocketWnd窗口对象,并使用WSAAsyncSelect()将这个SOCKET与该窗口对象关联,以让该窗口对象处理来自Socket的事件消息,然而CSocketWnd收到Socket事件之后,只是简单地回调CAsyncSocket::OnReceive(),CAsyncSocket::OnSend(),CAsyncSocket::OnAccept(),CAsyncSocket::OnConnect()等虚函数。所以CAsyncSocket的派生类,只需要在这些虚函数里添加发送和接收的代码。

 

        简化后,大致的代码为:

        bool CAsyncSocket::Create( long lEvent)    //参数lEvent是指定你所关心的Socket事件

        {

                m_hSocket =socket( PF_INET, SOCK_STREAM, 0 );   //创建Socket本身 

                // 创建响应事件的窗口,实际的这个窗口在AfxSockInit()调用时就被创建了。

                CSocketWnd*pSockWnd = new CSocketWnd;  

                pSockWnd->Create(...); 

                //Socket事件和窗口关联

                WSAAsyncSelect(m_hSocket, pSockWnd->m_hWnd, WM_SOCKET_NOTIFY, lEvent);

        }

        static void PASCALCAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)

        {

                CAsyncSocketSocket;

                Socket.Attach((SOCKET)wParam ); //wParam就是触发这个事件的Socket的句柄

                // lParam是错误码与事件码的合成

                int nErrorCode =WSAGETSELECTERROR(lParam);

                switch(WSAGETSELECTEVENT(lParam))

                {

                case FD_READ:

                        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;

                }

        }

 

        CSocketWnd类大致为:

        BEGIN_MESSAGE_MAP(CSocketWnd,CWnd)

                ON_MESSAGE(WM_SOCKET_NOTIFY, OnSocketNotify)

        END_MESSAGE_MAP()

 

        LRESULT CSocketWnd::OnSocketNotify(WPARAMwParam, LPARAM lParam)

        {

                // 收到Socket事件消息,回调CAsyncSocket的DoCallBack()函数

                CAsyncSocket::DoCallBack(wParam, lParam );

                return 0L;

        }

        本文最要提醒的一点是客户方在使用CAsyncSocket::Connect()时,往往返回一个WSAEWOULDBLOCK的错误(其它的某些函数调用也如此),实际上这不应该算作一个错误,它是提醒由于使用了非阻塞Socket方式,连接操作需要时间,不能瞬间建立。既然如此,我们可以等待呀,等它连接成功为止,于是许多程序员就在调用Connect()之后,Sleep(0),然后不停地用WSAGetLastError()或者CAsyncSocket::GetLastError()查看Socket返回的错误,直到返回成功为止。这是一种错误的做法,不能达到预期目的。事实上可以在Connect()调用之后等待CAsyncSocket::OnConnect()事件被触发,CAsyncSocket::OnConnect()是要表明Socket要么连接成功了,要么连接彻底失败了。在CAsyncSocket::OnConnect()被调用之后就知道是否Socket连接成功了,还是失败了。

        类似地Send()如果返回WSAEWOULDBLOCK错误,在OnSend()处等待,Receive()如果返回WSAEWOULDBLOCK错误,我们在OnReceive()处等待,以此类推。

        还有一点,也许是个难点,那就是在客户方调用Connect()连接服务方,那么服务方如何Accept(),以建立连接的问题。简单的做法就是在监听的Socket收到OnAccept()时,用一个新的CAsyncSocket对象去建立连接,例如:

        void CMySocket::OnAccept( int ErrCode )

        {

                CMySocket*pSocket = new CMySocket;

                Accept( *pSocket);

        }

        于是,上面的pSocket和客户方建立了连接,以后的通信就是这个pSocket对象去和客户方进行,而监听的Socket仍然继续在监听,一旦又有一个客户方要连接服务方,则上面的OnAccept()又会被调用一次。当然pSocket是和客户方通信的服务方,它不会触发OnAccept()事件,因为它不是监听Socket。

 

CSocket

        CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类。它是如何又把CAsyncSocket变成同步的,而且还能响应同样的Socket事件呢?

        其实很简单,CSocket在Connect()返回WSAEWOULDBLOCK错误时,不是在OnConnect(),OnReceive()这些事件终端函数里去等待。你先必须明白Socket事件是如何到达这些事件函数里的。这些事件处理函数是靠CSocketWnd窗口对象回调的,而窗口对象收到来自Socket的事件,又是靠线程消息队列分发过来的。总之,Socket事件首先是作为一个消息发给CSocketWnd窗口对象,这个消息肯定需要经过线程消息队列的分发,最终CSocketWnd窗口对象收到这些消息就调用相应的回调函数(OnConnect()等)。

        所以,CSocket在调用Connect()之后,如果返回一个WSAEWOULDBLOCK错误时,它马上进入一个消息循环,就是从当前线程的消息队列里取关心的消息,如果取到了WM_PAINT消息,则刷新窗口,如果取到的是Socket发来的消息,则根据Socket是否有操作错误码,调用相应的回调函数(OnConnect()等)。

        大致的简化代码为:

        BOOL CSocket::Connect( ... )

        {

                if(!CAsyncSocket::Connect( ... ) )

                {

                        // 由于异步操作需要时间,不能立即完成,所以Socket返回这个错误

                        if( WSAGetLastError() == WSAEWOULDBLOCK )

                        {

                                //进入消息循环,以从线程消息队列里查看FD_CONNECT消息,直到收到FD_CONNECT消息,认为连接成功。

                                while( PumpMessages( FD_CONNECT ) );

                       }

                }

        }

 

        BOOL CSocket::PumpMessages( UINT uEvent )

        {

                CWinThread*pThread = AfxGetThread();

                while( bBlocking) //bBlocking仅仅是一个标志,看用户是否取消对Connect()的调用

                {

                        MSG msg;

                        if( PeekMessage( &msg, WM_SOCKET_NOTIFY ) )

                        {

                                if( msg.message == WM_SOCKET_NOTIFY &&WSAGETSELECTEVENT(msg.lParam) == uStopFlag )

                                {

                                        CAsyncSocket::DoCallBack( msg.wParam, msg.lParam );

                                        return TRUE;

                                }

                        }

                        else

                        {

                                OnMessagePending(); //处理消息队列里的其它消息

                                pThread->OnIdle(-1);

                        }

                }

        }

        BOOL CSocket::OnMessagePending()

        {

               MSG msg;

                if( PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE ) )

                { // 这里仅关心WM_PAINT消息,以处理阻塞期间的主窗口重画

                        ::DispatchMessage( &msg );

                        return FALSE;

                }

                return FALSE;

        }

        其它的CSocket函数,诸如Send(),Receive(),Accept()都在收到WSAEWOULDBLOCK错误时,进入PumpMessages()消息循环,这样一个原本异步的CAsyncSocket,到了派生类CSocket,就变成同步的了。

        明白之后,我们可以对CSocket应用自如了。比如有些程序员将CSocket的操作放入一个线程,以实现多线程的异步Socket(通常,同步+多线程相似于异步)

 

3. Socket的编程过程

       在Socket编程中要分为客户端和服务器端进行分别编程,过程分别如下:

       1. TCP

      

客户端:

       初始化Socket库:WSAStartup( 0x0202, &wsaData);

       第一个参数为版本,0x0202,第二个参数为传出的Windows Sockets 数据。

       返回值为0表示成功,其他表示错误。

      

       创建Socket:socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

       第一个参数地址族,第二个参数为Sokcet类型:报文,流,原始,第三个参数为所使用的通信协议,一般为0,默认传输层协议。

       返回值为socket描述符

      

       填写服务器端的SOCKADDR:SOCKADDR_IN server;

       其中需要填写的包括地址族:AF_INET,IP地址,端口地址

      

       调用连接函数:connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));

       第一个参数为本地socket描述符,第二个参数为服务器地址信息,第三个参数为地址信息大小。

       返回值为0表示连接成功,否则连接错误,错误要根据返回值确定。

      

       循环发送和接收数据:

       ret= send(sClient, szMessage, strlen(szMessage), 0);

ret= recv(sClient, szMessage, MSGSIZE, 0);

      

       send第一个参数socket描述符,第二个为消息,第三个为消息的长度,第四个参数为调用方式,具体作用不详。

       返回值为发送的数据的大小,多少字节数据。

       recv第一个参数为socket描述符,第二个为存放数据的缓存,第三个参数为缓存的大小,第四个参数为调用方式,一般设置为0,作用不详

       返回值为接收到的数据的大小

      

       关闭Socket:closesocket(sClient);

       关闭Socket,参数为socket的描述符

      

        清理加载的Socket库:WSACleanup();

        清理Socket库,卸载掉ws2_32.dll动态库

      

服务器:

       初始化Socket库:WSAStartup( 0x0202, &wsaData);

       第一个参数为版本,0x0202,第二个参数为传出的Windows Sockets 数据。

       返回值为0表示成功,其他表示错误。

      

       创建Socket:socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

       第一个参数地址族,第二个参数为Sokcet类型:报文,流,原始,第三个参数为所使用的通信协议,一般为0,默认传输层协议。

       返回值为socket描述符

      

       填写服务器端的SOCKADDR,并绑定Socket:SOCKADDR_IN local;

       其中需要填写的包括地址族:AF_INET,IP地址,端口地址


        bind(sListen,(struct sockaddr *)&local, sizeof(SOCKADDR_IN));  

       第一个参数为Socket描述符,唯一标识本地的监听Socket,第二个参数为本地的地址与端口地址信息,与Socket进行绑定,第三个参数为地址信息的大小

       返回值为0表示调用成功,否则出现错误,返回的为错误ID号

      

       服务器进行监听:listen(sListen,3);

        第一个参数为socket描述符,本地的监听socket,第二个参数backlog为连接的请求队列(queue of pendingconnections)的最大长度(一般由2到4)。用SOMAXCONN则由系统确定。

        返回值为0表示连接成功,其他表示出现的错误ID号

      

       接受连接:sClient = accept(sListen, (struct sockaddr *)&client,&iaddrSize);

       第一个参数为Socket描述符,第二个参数为地址结构体,用于保存所接受的连接的客户端的信息,第三个参数为保存地址长度变量,需要传递一个地址值。

       返回值为本连接在服务器端的标识符,即一个Socket,用于与该连接进行通信。

      

       循环发送和接收数据:

       ret= send(sClient, szMessage, strlen(szMessage), 0);

ret= recv(sClient, szMessage, MSGSIZE, 0);

      

       send第一个参数socket描述符,第二个为消息,第三个为消息的长度,第四个参数为调用方式,具体作用不详。

       返回值为发送的数据的大小,多少字节数据。

       recv第一个参数为socket描述符,第二个为存放数据的缓存,第三个参数为缓存的大小,第四个参数为调用方式,一般设置为0,作用不详

       返回值为接收到的数据的大小

 

       关闭Socket:closesocket(sClient);

       关闭Socket,参数为socket的描述符

      

        清理加载的Socket库:WSACleanup();

        清理Socket库,卸载掉ws2_32.dll动态库

 

2. UDP

       客户端:

       初始化Socket库:WSAStartup( 0x0202, &wsaData);

       第一个参数为版本,0x0202,第二个参数为传出的Windows Sockets 数据。

       返回值为0表示成功,其他表示错误。   

 

       创建socket:sockfd = socket(AF_INET,SOCK_DGRAM, 0);

       第一个参数地址族,第二个参数为Sokcet类型:报文,流,原始,第三个参数为所使用的通信协议,一般为0,默认传输层协议。

       返回值为socket描述符

      

       发送数据:len = sendto( sockfd, sendBuf, strlen(sendBuf), 0, (sockaddr*) &srvAddr,sizeof(srvAddr));

        第一个参数为socket描述符,第二个参数为要发送的数据的缓存,第三个参数为要发送数据长度,第四个参数为调用方式,默认为0,作用不详,第五个参数为服务器端的地址信息,为地址结构体包含了ip地址和端口,第六个参数为地址结构体的大小

        返回值为数据长度,失败返回-1。

 

        接收数据:len =recvfrom( sockfd, sendBuf, 2048, 0, NULL, NULL);

        第一个参数为socket描述符,第二个参数为接收数据的缓存,第四个参数为缓存的大小,第四个参数为调用方式,一般默认为0,第五个参数发送方的地址信息结构体,第六个参数为地址信息结构体长度。

        返回值,调用成功则返回值为数据长度,如果为0表示错误。

      

        关闭Socket:close(sockfd);

        参数为socket描述符

      

服务端:

       初始化Socket库:WSAStartup( 0x0202, &wsaData);

       第一个参数为版本,0x0202,第二个参数为传出的Windows Sockets 数据。

       返回值为0表示成功,其他表示错误。   

 

       创建socket:sockfd = socket(AF_INET,SOCK_DGRAM, 0);

       第一个参数地址族,第二个参数为Sokcet类型:报文,流,原始,第三个参数为所使用的通信协议,一般为0,默认传输层协议。

       返回值为socket描述符

 

        绑定地址:bind( sockfd,(struct sockaddr*)&bindAddr, sizeof(bindAddr))

       第一个参数为socket描述符,第二个参数为绑定的本地sockaddr,包括地址和端口号。第三个参数为地址结构体的大小。

       返回值为0表示成功,否则为错误码

 

       发送数据:len = sendto( sockfd, sendBuf, strlen(sendBuf), 0, (sockaddr*) &srvAddr,sizeof(srvAddr));

        第一个参数为socket描述符,第二个参数为要发送的数据的缓存,第三个参数为要发送数据长度,第四个参数为调用方式,默认为0,作用不详,第五个参数为服务器端的地址信息,为地址结构体包含了ip地址和端口,第六个参数为地址结构体的大小

返回值为数据长度,失败返回-1。

 

        接收数据:len =recvfrom( sockfd, sendBuf, 2048, 0, NULL, NULL);

        第一个参数为socket描述符,第二个参数为接收数据的缓存,第四个参数为缓存的大小,第四个参数为调用方式,一般默认为0,第五个参数发送方的地址信息结构体,第六个参数为地址信息结构体长度。

        返回值,调用成功则返回值为数据长度,如果为0表示错误。

      

        关闭Socket:close(sockfd);

        参数为socket描述符

 

4. TCP连接和断开过程(三次握手和四次断开)

        三次握手:服务器进入Listen状态,开始监听。客户端发送连接请求,进入SYN_SEND状态,服务器接收到之后,进入SYN_RCVD状态,并回复客户端ACK,客户端接收到ACK,向服务器端回复ACK,并进入ESTABLISHED状态,服务器接收到客户端的ACK之后,进入ESTABLISHED状态。

        四次断开:客户端发送断开请求,进入FIN_WAIT_1状态,服务器接收到断开请求后,向客户端回复ACK,并进入CLOSE_WAIT状态。客户端接收到服务器的ACK之后,进入到FIN_WAIT_2状态,从客户端到服务器之间的连接断开。服务器向客户端发送断开请求,并进入LAST_ACK状态,客户端收到之后,给服务器回复ACK,并进入TIME_WAIT,服务器接收到客户端回复的ACK之后,进入到CLOSED状态。客户端在2倍的超时时间后进入CLOSED状态。


5. 无线拥塞控制和有线网络拥塞控制的区别:

       传统的拥塞控制方法,以有线网络中链路错误率比较低为前提的,因此在有线网络中可以非常好地运行,而无线网络中,由于信号干扰,终端移动等因素造成无线信道的错误率比有线信道高很多,因此会造成传统方法中丢包预测错误,从而造成网络吞吐量小,且波动较大。当前多媒体流不断增多,网络吞吐量的波动会严重影响多媒体数据传输的质量。

      

6. UDP与TCP的区别:

       理论上:

       TCP是基于连接的传输协议,在传输之前需要建立连接;UDP是无连接的传输协议。

        TCP需要连接,因此其开销较大,需要较多网络资源,而UDP不需要连接开销小。

        TCP是流式传输协议,而UDP基于数据报。

        TCP保证数据的正确性,有序到达;UDP既不保证数据的正确性,又不能保证数据时按序到达接收方。

      

       编程上:

       TCP对于不同的系统平台,有不同的并发数,有并发数上限,如果要实现大并发,需要做特殊处理,如使用select模型,完成端口或是Linux下的epoll模型;而UDP不需要连接,本身就很容易实现并发。

       TCP是基于流的传输协议,因此数据是以数据流的方式到达接收端,需要开发人员实现数据包的切分,以实现数据包解析;而UDP本身就是基于数据报的,因此即使不为其定制协议,也可以很容易区分两次的数据报。

 

7. C++的基础知识

 

8. 大数据处理的专题

 

9. 基础算法进行总结与分析

 

By Andy @ 2013-09-17

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值