Socket -- 阻塞模式

来源:互联网 发布:软件开发学徒骗局 编辑:程序博客网 时间:2024/05/16 06:41

(1)阻塞模式

  Windows套接字在阻塞和非阻塞两种模式下执行I/O操作。在阻塞模式下,在I/O操作完成前,执行的操作函数一直等候而不会立即返回,该 函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回,而不管I/O是否完成,该函数所在的线程会继续运行。在阻塞模式的套接字上,调用任何一个Windows Sockets API都会耗费不确定的等待时间。图所示,在调用recv()函数时,发生在内核中等待数据和复制数据的过程。当调用recv()函数时,系统首先查是否有准备好的数据。如果数据没有准备好,那么系统就处于等待状态。当数据准备好后,将数据从系统缓冲区复制 到用户空间,然后该函数返回。在套接应用程序中,当调用recv()函数时,未必用户空间就已经存在数据,那么此时recv()函数就会处于等待状态。

                                

Windows套接字程序使用“生产者-消费者”模式来解决上述问题。在程序中,“生产者”读入数据,“消费者”根据需求对读入数据进行处理。通常“生产 者”和“消费者”存在于两个线程中,当“生产者”完成读入数据时,使用线程同步机制,例如设置一个事件通知“消费者”,“消费者”接收到这个事件后对读入 的数据进行处理。

  当使用socket()函数和WSASocket()函数创建套接字时,默认的套接字都是阻塞的。这意味着当调用Windows Sockets API不能立即完成时,线程处于等待状态,直到操作完成。并不是所有Windows Sockets API以阻塞套接字为参数调用都会发生阻塞。例如,以阻塞模式的套接字为参数调用bind()、listen()函数时,函数会立即返回。将可能阻塞套接字的Windows Sockets API调用分为以下四种:

1.输入操作

recv()、recvfrom()、WSARecv()和WSARecvfrom()函数。以阻塞套接字为参数调用该函数接收数据。如果此时套接字缓冲区内没有数据可读,则调用线程在数据到来前一直睡眠。

2.输出操作

send()、sendto()、WSASend()和WSASendto()函数。以阻塞套接字为参数调用该函数发送数据。如果套接字缓冲区没有可用空间,线程会一直睡眠,直到有空间。

3.接受连接

accept()和WSAAcept()函数。以阻塞套接字为参数调用该函数,等待接受对方的连接请求。如果此时没有连接请求,线程就会进入睡眠状态。

4.外出连接

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

阻塞模式套接字的不足表现为,在大量建立好的套接字线程之间进行通信时比较困难。当使用“生产者-消费者”模型开发网络程序时,为每个套接字都分别 分配一个读线程、一个处理数据线程和一个用于同步的事件,那么这样无疑加大系统的开销。其最大的缺点是当希望同时处理大量套接字时,将无从下手,其扩展性 很差。

 (2)Server 端源码

#include "stdafx.h"#include <iostream>#include <WINSOCK2.H>#pragma comment(lib, "wsock32.lib")using namespace std;#define SERVER_EXIT_OK0//服务器正常退出#define SERVER_DLL_REEOR1//调用Windows sockets DLL失败#define SERVER_API_ERROR2//调用Windows sockets API失败#defineSERVERPORT5555//服务器TCP端口#define MAX_NUM_BUF64//缓冲区最大尺寸//变量charbufRecv[MAX_NUM_BUF];//读缓冲区charbufSend[MAX_NUM_BUF];//写缓冲区SOCKETsServer;//服务器监听套接字SOCKETsClient;//接受客户端套接字BOOLbConning;//与客户端的连接状态//函数voidInitMember(void);//初始化成员变量 intExitClient(int nExit);//客户端退出BOOLRecvLine(SOCKET s, char* buf);//读取一行数据BOOLSendLine(SOCKET s, char* buf);//发送一行数据intHandleSocketError(char *str);//对Windows sockets API调用错误处理voidShowSocketMsg(char* str);//显示错误信息//主函数int main(int argc, char* argv[]){InitMember();//初始化变量WORDwVersionRequested;//应用程序需要Windows sockets DLL的版本WSADATAwsaData;//Windows sockets DLL版本信息intretVal;//调用Windows sockets API返回值//初始化Windows Sockets DLLwVersionRequested = MAKEWORD(2,2);retVal = WSAStartup(wVersionRequested, &wsaData);if ( 0 != retVal ) {ShowSocketMsg("Can not find a usable Windows Sockets dll!");return SERVER_DLL_REEOR;}//确保WinSock DLL支持2.2if ( LOBYTE( wsaData.wVersion ) != 2 ||HIBYTE( wsaData.wVersion ) != 2){ShowSocketMsg("Can not find a usable Windows Sockets dll!");WSACleanup( );return SERVER_DLL_REEOR; }//创建套接字sServer= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if(INVALID_SOCKET == sServer){return HandleSocketError("Failed socket()!");}//服务器套接字地址 SOCKADDR_IN addrServ;addrServ.sin_family = AF_INET;addrServ.sin_port = htons(SERVERPORT);addrServ.sin_addr.s_addr = INADDR_ANY;//绑定套接字retVal = bind(sServer, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));if(SOCKET_ERROR == retVal){closesocket(sServer);//关闭套接字return HandleSocketError("Failed bind()!");//错误处理}//开始监听 retVal = listen(sServer, 1);if(SOCKET_ERROR == retVal){closesocket(sServer);//关闭套接字return HandleSocketError("Failed listen()!");//错误处理}//等待客户端的连接cout << "Start Server succeeded!" << endl;cout << "Waiting for new clients..." << endl;//接受客户端请求sockaddr_in addrClient;int addrClientlen = sizeof(addrClient);sClient = accept(sServer,(sockaddr FAR*)&addrClient, &addrClientlen);if(INVALID_SOCKET == sClient){closesocket(sServer);//关闭套接字return HandleSocketError("Failed accept()!");//错误处理}else{bConning = TRUE;//客户端请求成功}//显示客户端的IP和端口char *pClientIP = inet_ntoa(addrClient.sin_addr);u_short  clientPort = ntohs(addrClient.sin_port);cout<<"Accept a client."<<endl;cout<<"IP: "<<pClientIP<<endl;cout<<"Port: "<<clientPort<<endl;//接收客户端数据if (!RecvLine(sClient, bufRecv)){returnExitClient(SERVER_API_ERROR);//退出}//显示客户端数据cout << bufRecv<<endl;//向客户端发送数据strcpy(bufSend, "Hello,Client!\n");if (!SendLine(sClient, bufSend)){returnExitClient(SERVER_API_ERROR);}//显示退出信息cout << "Server exiting..." << endl;//退出return ExitClient(SERVER_EXIT_OK);}/* *初始化成员变量 */voidInitMember(void){//初始化读和写缓冲区memset(bufRecv, 0, MAX_NUM_BUF);memset(bufSend, 0, MAX_NUM_BUF);//初始化sServer = INVALID_SOCKET;sClient = INVALID_SOCKET;//没有连接状态bConning = FALSE;}/* *退出 */intExitClient(int nExit){closesocket(sServer);//关闭监听套接字closesocket(sClient);//关闭连接客户端套接接WSACleanup();//卸载Windows sockets DLL 清理内存return nExit;//退出}/* *读一行数据 */BOOLRecvLine(SOCKET s, char* buf){BOOLretVal = TRUE;//返回值BOOLbLineEnd = FALSE;//行结束intnReadLen = 0;//读入字节数intnDataLen = 0;//数据长度while (!bLineEnd && bConning)//与客户端连接 没有换行{nReadLen = recv(s, buf + nDataLen, 1, 0);//每次接收一个字节//错误处理if (SOCKET_ERROR == nReadLen){int nErrCode = WSAGetLastError();//错误代码if (WSAENOTCONN == nErrCode){ShowSocketMsg("The socket is not connected!");}else if(WSAESHUTDOWN == nErrCode){ShowSocketMsg("The socket has been shut down!");}else if (WSAETIMEDOUT == nErrCode){ShowSocketMsg("The connection has been dropped!");}else if (WSAECONNRESET == nErrCode){ShowSocketMsg("The virtual circuit was reset by the remote side!");}else{//}retVal = FALSE;//读数据失败break;//跳出循环}if (0 == nReadLen)//客户端关闭{retVal = FALSE;//读数据失败break ;//跳出循环}//读入数据if ('\n' == *(buf + nDataLen))//换行符{bLineEnd = TRUE;//接收数据结束}else{nDataLen += nReadLen;//增加数据长度}}return retVal;}/* *//发送一行数据 */BOOLSendLine(SOCKET s, char* str){int retVal;//返回值retVal = send(s, str, strlen(str), 0);//一次发送//错误处理if (SOCKET_ERROR == retVal){int nErrCode = WSAGetLastError();//错误代码if (WSAENOTCONN == nErrCode){ShowSocketMsg("The socket is not connected!");}else if(WSAESHUTDOWN == nErrCode){ShowSocketMsg("The socket has been shut down!");}else if (WSAETIMEDOUT == nErrCode){ShowSocketMsg("The connection has been dropped!");}else{//}return FALSE;//发送失败}return TRUE;//发送成功}/* *错误处理 */intHandleSocketError(char *str){ShowSocketMsg(str);//显示错误消息WSACleanup();//卸载Windows socket DLLreturn SERVER_API_ERROR;//退出应用程序}/* *显示错误 */voidShowSocketMsg(char* str){MessageBox(NULL, str, "SERVER ERROR", MB_OK);}

 (3)Client 端源码

// Client.cpp : Defines the entry point for the console application.//#include "stdafx.h"#include <windows.h>#include <winsock.h>#include <iostream>#pragma comment(lib, "wsock32.lib")using namespace std;#define CLIENT_EXIT_OK0//客户端正常退出#define CLIENT_DLL_REEOR1//调用Windows socket dll失败#define CLIENT_API_ERROR2//调用Windows socket api失败#define MAX_NUM_BUF64//缓冲区的最大长度#defineSERVERPORT5555//服务器TCP端口//变量charbufRecv[MAX_NUM_BUF];//读缓冲区charbufSend[MAX_NUM_BUF];//写缓冲区SOCKETsHost;//socketBOOLbConning;//连接服务器状态//函数voidInitMember(void);//初始化变量intExitClient(int nExit);//退出BOOLRecvLine(SOCKET s, char* buf);//读取一行数据voidShowErrorMsg(void);//显示错误信息//主函数int main(){//初始化变量InitMember();WORDwVersionRequested;//应用程序需要Windows sockets DLL的版本WSADATAwsaData;//Windows sockets DLL版本信息intretVal;//调用Windows sockets API返回值//初始化Windows Sockets DLLwVersionRequested = MAKEWORD(2,2);retVal = WSAStartup(wVersionRequested,(LPWSADATA)&wsaData);if ( 0 != retVal ) {MessageBox(NULL, "Can not find a usable Windows Sockets dll!", "ERROR", MB_OK);return CLIENT_DLL_REEOR;}//创建Windows socketsHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if(INVALID_SOCKET == sHost){ShowErrorMsg();//显示错误信息WSACleanup();//释放资源return CLIENT_API_ERROR;//退出}//准备连接服务器cout << "Client succeeded!" << endl;cout<<"Be ready to connect to server..."<<endl;//获取主机的信息LPHOSTENT hostEntry;char hostname[MAX_NUM_BUF];gethostname(hostname,MAX_NUM_BUF);//获取主机名称hostEntry = gethostbyname(hostname);//获取主机信息if(!hostEntry){ShowErrorMsg();//显示错误信息return ExitClient(CLIENT_API_ERROR);//退出}//设置sockaddr_inSOCKADDR_IN addrServ;addrServ.sin_family = AF_INET;addrServ.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);addrServ.sin_port = htons(SERVERPORT);//连接服务器retVal=connect(sHost,(LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));if(SOCKET_ERROR == retVal){ShowErrorMsg();//显示错误信息return ExitClient(CLIENT_API_ERROR);//退出}else{bConning = TRUE;//连接服务器成功}     //连接服务器成功cout<<"Connect successfully!"<<endl;//向服务器发送数据strcpy(bufSend, "Hello, Server!\n");retVal = send(sHost, bufSend, strlen(bufSend), 0);if (SOCKET_ERROR == retVal){ShowErrorMsg();//显示错误信息return ExitClient(CLIENT_API_ERROR);//退出}//从服务器接收数据if (!RecvLine(sHost, bufRecv)){ShowErrorMsg();//显示错误信息return ExitClient(CLIENT_API_ERROR);//退出}//显示服务器的应答cout<<bufRecv<<endl;//退出return ExitClient(CLIENT_EXIT_OK);}/* *显示错误信息 */voidShowErrorMsg(void){int nErrCode = WSAGetLastError();//获取错误代码HLOCAL hlocal = NULL;  //获取错误的文本字符串BOOL fOk = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,NULL, nErrCode, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(PTSTR)&hlocal, 0, NULL);//显示错误信息if (hlocal != NULL){MessageBox(NULL, (char*)LocalLock(hlocal), "CLIENT ERROR", MB_OK);LocalFree(hlocal);}}/* *初始化成员变量 */voidInitMember(void){//初始化读和写缓冲区memset(bufRecv, 0, MAX_NUM_BUF);memset(bufSend, 0, MAX_NUM_BUF);//初始化sHost = INVALID_SOCKET;//没有连接状态bConning = FALSE;}/* *退出 */intExitClient(int nExit){closesocket(sHost);//关闭套接字WSACleanup();//卸载Windows sockets DLL 清理内存//显示退出信息cout << "Client exiting..." << endl;Sleep(20000);return nExit;//退出}/* *读取一行数据 */BOOL  RecvLine(SOCKET s, char* buf){BOOLretVal = TRUE;//返回值BOOLbLineEnd = FALSE;//行结束intnReadLen = 0;//读入字节数intnDataLen = 0;//数据长度while (!bLineEnd && bConning)//与客户端连接 没有换行{nReadLen = recv(s, buf + nDataLen, 1, 0);//每次接收一个字节//错误处理if (SOCKET_ERROR == nReadLen){retVal= FALSE;//读数据失败break;//跳出循环}if (0 == nReadLen)//客户端关闭{retVal = FALSE;//读数据失败break ;//跳出循环}//读入数据if ('\n' == *(buf + nDataLen))//换行符{bLineEnd = TRUE;//接收数据结束}else{nDataLen += nReadLen;//增加数据长度}}return retVal;}

 (4)调试结果

                        

                        




1 0
原创粉丝点击