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