网站建议:179001057@qq.com

套接字模式—阻塞模式开发

技术2022-05-11  1

1、阻塞模式概念

     当使用socket()函数和WSASocket()函数创建的套接字时,默认都是阻塞模式的。阻塞模式是指套接字在执行操作时,调用函数在没有完成操作之前不会立即返回的工作模式。这意味着当调用Windows Sockets API不能立即完成时,线程处于等待窗台,直到操作完成。

 

     并不是所有的Windows Sockets API以阻塞套接字为参数调用都会发生阻塞。例如,以阻塞模式的套接字为参数调用bind()、listen()时,函数会立即返回。这里将可能阻塞套接字的Windows Sockets API调用分为以下四种:

     A、输入操作

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

     B、输出操作

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

     C、接受连接

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

     D、外出连接

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

 

2、阻塞模式的优势与不足

     使用阻塞模式的套接字开发网络程序比较简单,容易实现。当希望能够立即发送和接受数据,且处理的套接字数量较少的情况下。使用阻塞套接字模式来开发网络程序比较合适。

     而不足之处表现为,在大量建立好的套接字线程之间进行通讯时比较困难。当希望同时处理大量套接字时,将无从下手,扩展性差。

 

3、C++源码

     将书本上本章节的理论知识认真的看了半个晚上,被过滤一下也就只有这么多了。看源码也许会更容易理解吧!

     (短短两三五百行代码,居然敲了近两个小时,实在不敢恭维打字速度....)

// // BlockModeSrv.cpp // 阻塞模式 — 服务端实现 // by Koma 2009-09-19 00:12 #include <iostream.h> #include <winsock2.h> #pragma comment(lib,"wsock32.lib") #define SERVER_EXIT_OK 0 // 服务器正常退出 #define SERVER_DLL_ERROR 1 // 调用DLL失败 #define SERVER_API_ERROR 2 // 调用API失败 #define SERVERPORT 5555// 服务器端口 #define MAX_NUM_BUF 64 // 缓冲区最大值 char bufRecv[MAX_NUM_BUF]; // 接受数据 char bufSend[MAX_NUM_BUF]; // 发送数据 SOCKET sServer; // 服务器套接字 SOCKET sClient; // 客户端套接字 BOOL bConning; // 与客户端连接状态 void InitMember(); // 初始化函数 void ShowSocketMsg(char *str); // 显示错误信息 int ExitClient(int nExit); // 客户端结束连接 int HandleSocketError(char *str); // 调用API错误信息 BOOL RecvLine(SOCKET s, char* buf); // 接受数据 BOOL SendLine(SOCKET s, char* buf); // 发送数据 /************************************************************************/ /* 函数作用:程序入口main函数 /* 参数说明:无 /* 返 回 值:客户端退出代码 /* 备 注: /************************************************************************/ int main(void) { InitMember(); // 初始化全局变量 WSADATA wsa; // Sockets DLL版本信息 int nRetVal;// 调用Sockets API返回值 // 初始化套接字 nRetVal = WSAStartup(MAKEWORD(1,1),&wsa); if(0 != nRetVal) { ShowSocketMsg("无法找到Windows套接字核心Dll!"); return SERVER_DLL_ERROR; } // 判断是否支持1.1 if(LOBYTE(wsa.wVersion) != 1 || HIBYTE(wsa.wVersion) != 1) { ShowSocketMsg("无法找到Windows套接字核心Dll!"); return SERVER_DLL_ERROR; } // 创建套接字 sServer = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(INVALID_SOCKET == sServer) { return HandleSocketError("创建套接字失败!"); } // 设置服务器选项 SOCKADDR_IN addrServ; addrServ.sin_family = AF_INET; addrServ.sin_port = htons(SERVERPORT); addrServ.sin_addr.S_un.S_addr = INADDR_ANY; // 绑定套接字 nRetVal = bind(sServer,(LPSOCKADDR)&addrServ,sizeof(SOCKADDR_IN)); if(SOCKET_ERROR == nRetVal) { closesocket(sServer); return HandleSocketError("绑定套接字失败!"); } // 开始监听 nRetVal = listen(sServer,1); if(SOCKET_ERROR == nRetVal) { closesocket(sServer); return HandleSocketError("监听失败!"); } // 等待客户端的连接 cout<<"服务器创建成功!"<<endl; cout<<"正在等待客户端请求..."<<endl; SOCKADDR_IN addrClient; int addrClientLen; // 接受客户端请求 addrClientLen = sizeof(addrClient); sClient = accept(sServer,(sockaddr FAR*)&addrClient,&addrClientLen); if(INVALID_SOCKET == sClient) { closesocket(sServer); return HandleSocketError("连接失败!"); } else { bConning = TRUE; } // 显示客户端的IP地址与端口 char *pClientIP = inet_ntoa(addrClient.sin_addr); u_short clientPort = ntohs(addrClient.sin_port); cout<<"已检测到连接一个客户端:"<<endl; cout<<"IP地址:"<<pClientIP<<" 端口:"<<clientPort<<endl; // 接受客户端数据 if(!RecvLine(sClient,bufRecv)) { return ExitClient(SERVER_API_ERROR); } cout<<bufRecv<<endl; // 向客户端发送数据 strcpy(bufSend,"Hello World !/n"); if(!SendLine(sClient,bufSend)) { return ExitClient(SERVER_API_ERROR); } // 显示退出信息 cout<<"服务器退出..."<<endl; // 程序退出 return ExitClient(SERVER_EXIT_OK); } /************************************************************************/ /* 函数作用:初始化各全局变量 /* 参数说明:无 /* 返 回 值:无 /* 备 注: /************************************************************************/ void InitMember() { memset(bufRecv,0,MAX_NUM_BUF); memset(bufSend,0,MAX_NUM_BUF); sServer = INVALID_SOCKET; sClient = INVALID_SOCKET; bConning = FALSE; } /************************************************************************/ /* 函数作用:显示错误信息 /* 参数说明:错误信息 /* 返 回 值:无 /* 备 注: /************************************************************************/ void ShowSocketMsg(char *str) { MessageBox(NULL,str,"Server Error",MB_OK); } /************************************************************************/ /* 函数作用:客户端结束连接 /* 参数说明:错误代码 /* 返 回 值:将参数作为返回值 /* 备 注: /************************************************************************/ int ExitClient(int nExit) { closesocket(sServer); // 关闭监听套接字 closesocket(sClient); // 关闭连接客户端套接字 WSACleanup(); // 卸载Sockets Dll return nExit; // 退出代码 } /************************************************************************/ /* 函数作用:调用API错误信息 /* 参数说明:错误信息字符串 /* 返 回 值:SERVER_API_ERROR /* 备 注: /************************************************************************/ int HandleSocketError(char *str) { ShowSocketMsg(str); // 显示错误信息 WSACleanup(); // 卸载Sockets Dll return SERVER_API_ERROR;// 退出应用程序 } /************************************************************************/ /* 函数作用:接受数据 /* 参数说明:接收套接字、接收内容 /* 返 回 值:成功返回TRUE,失败返回FALSE /* 备 注: /************************************************************************/ BOOL RecvLine(SOCKET s, char* buf) { BOOL bnRetVal = TRUE; // 返回值 BOOL bLineEnd = FALSE; // 行结束 int nReadLen = 0; // 读入字节数 int nDataLen = 0; // 数据长度 while(!bLineEnd && bConning) { nReadLen = recv(s,buf + nDataLen,1,0); // 每次接收一个字节 if(SOCKET_ERROR == nReadLen) { int nErrCode = WSAGetLastError(); // 获取错误代码 if(WSAENOTCONN == nErrCode) { ShowSocketMsg("套接字没有连接!"); } else if(WSAESHUTDOWN == nErrCode) { ShowSocketMsg("套接字已经关闭!"); } else if(WSAETIMEDOUT == nErrCode) { ShowSocketMsg("请求超时,主机没有反应!"); } else if(WSAECONNRESET == nErrCode) { ShowSocketMsg("远程主机强迫关闭了现有的连接!"); } else {} bnRetVal = FALSE; // 读取数据失败 break; // 跳出循环 } if(0 == nReadLen) // 客户端关闭 { bnRetVal = FALSE; // 读取数据失败 break; // 跳出循环 } // 读入数据,检测是否输入换行符 if('/n' == *(buf + nDataLen)) { bLineEnd = TRUE; // 接收数据结束 } else{ // 继续读入数据 nDataLen += nReadLen; } } return bnRetVal; } /************************************************************************/ /* 函数作用:发送数据 /* 参数说明:发送套接字、发送内容 /* 返 回 值:成功返回TRUE,失败返回FALSE /* 备 注: /************************************************************************/ BOOL SendLine(SOCKET s, char* buf) { int nnRetVal; // 返回值 nnRetVal = send(s,buf,strlen(buf),0); // 发送数据 if(SOCKET_ERROR == nnRetVal) // 错误处理 { int nErrCode = WSAGetLastError(); // 获取错误代码 if(WSAENOTCONN == nErrCode) { ShowSocketMsg("套接字没有连接!"); } else if(WSAESHUTDOWN == nErrCode) { ShowSocketMsg("套接字已经关闭!"); } else if(WSAETIMEDOUT == nErrCode) { ShowSocketMsg("请求超时,主机没有反应!"); } else {} return FALSE; // 发送失败 } return TRUE; // 发送成功 }

 

// // BlockModeClient.cpp // 阻塞模式 — 客户端实现 // by Koma 2009-09-19 01:45 #include <iostream.h> #include <winsock2.h> #pragma comment(lib,"wsock32.lib") #define CLIENT_EXIT_OK 0 // 客户端正常退出 #define CLIENT_DLL_ERROR 1 // 调用Socket Dll失败 #define CLIENT_API_ERROR 2 // 调用Socket API失败 #define MAX_NUM_BUF 64 // 缓冲区大小 #define SERVERPORT 5555 // 服务器TCP端口 char bufRecv[MAX_NUM_BUF]; // 接收数据缓冲区 char bufSend[MAX_NUM_BUF]; // 发送数据缓冲区 SOCKET sHost; // 连接服务器socket BOOL bConning; // 连接状态 void ShowErrorMsg(void); // 显示错误信息 void InitMember(void); // 初始化全局变量 int ExitClient(int nExit); // 客户端退出 BOOL RecvLine(SOCKET s,char* buf); // 接收数据 /************************************************************************/ /* 函数作用:程序入口main函数 /* 参数说明:无 /* 返 回 值:客户端退出代码 /* 备 注: /************************************************************************/ int main(void) { InitMember(); // 初始化全局变量 WSADATA wsa; // Sockets DLL版本信息 int nRetVal;// 调用Sockets API返回值 // 初始化套接字 nRetVal = WSAStartup(MAKEWORD(1,1),&wsa); if(0 != nRetVal) { MessageBox(NULL,"无法找到Windows套接字核心Dll!","提示",MB_OK); return CLIENT_DLL_ERROR; } // 判断是否支持1.1 if(LOBYTE(wsa.wVersion) != 1 || HIBYTE(wsa.wVersion) != 1) { MessageBox(NULL,"无法找到Windows套接字核心Dll!","提示",MB_OK); return CLIENT_DLL_ERROR; } //创建Windows socket sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(INVALID_SOCKET == sHost) { ShowErrorMsg(); //显示错误信息 WSACleanup(); //释放资源 return CLIENT_API_ERROR;//退出 } // 准备连接服务器 cout<<"客户端创建成功!"<<endl; cout<<"正在准备连接服务器..."<<endl; // 设置服务器信息 SOCKADDR_IN servAddr; servAddr.sin_family = AF_INET; servAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.254"); servAddr.sin_port = htons(SERVERPORT); // 连接服务器 nRetVal = connect(sHost,(LPSOCKADDR)&servAddr,sizeof(servAddr)); if(SOCKET_ERROR == nRetVal) { ShowErrorMsg(); // 显示错误信息 return ExitClient(CLIENT_API_ERROR);// 退出程序 } else{ bConning = TRUE; // 连接成功 } // 向服务器发送数据 strcpy(bufSend,"Hello World !/n"); nRetVal = send(sHost,bufSend,strlen(bufSend),0); if(SOCKET_ERROR == nRetVal) { ShowErrorMsg(); // 显示错误信息 return ExitClient(CLIENT_API_ERROR);// 退出程序 } // 从服务器接收数据 if(!RecvLine(sHost,bufRecv)) { ShowErrorMsg(); // 显示错误信息 return ExitClient(CLIENT_API_ERROR);// 退出程序 } // 显示服务器应答 cout<<bufRecv<<endl; // 退出程序 return ExitClient(CLIENT_EXIT_OK); } /************************************************************************/ /* 函数作用:显示错误信息 /* 参数说明:无 /* 返 回 值:无 /* 备 注: /************************************************************************/ void ShowErrorMsg(void) { int nErrCode = WSAGetLastError(); HLOCAL hLocal = NULL; BOOL bOK; // 获取错误的文本字符串 bOK = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL,nErrCode,0, (PTSTR)&hLocal,0,NULL); // 显示错误信息 if(hLocal != NULL) { MessageBox(NULL,(char*)LocalLock(hLocal),"Client Error",MB_OK); LocalFree(hLocal); } } /************************************************************************/ /* 函数作用:初始化全局变量 /* 参数说明:无 /* 返 回 值:无 /* 备 注: /************************************************************************/ void InitMember(void) { memset(bufRecv,0,MAX_NUM_BUF); memset(bufSend,0,MAX_NUM_BUF); sHost = INVALID_SOCKET; bConning = FALSE; } /************************************************************************/ /* 函数作用:客户端退出 /* 参数说明:错误代码 /* 返 回 值:将参数作为返回值 /* 备 注: /************************************************************************/ int ExitClient(int nExit) { closesocket(sHost); WSACleanup(); cout<<"客户端退出..."<<endl; Sleep(20000); return nExit; } /************************************************************************/ /* 函数作用:接收数据 /* 参数说明:接收套接字、接收内容 /* 返 回 值:成功返回TRUE,失败返回FALSE /* 备 注: /************************************************************************/ BOOL RecvLine(SOCKET s,char* buf) { BOOL retVal = TRUE; // 返回值 BOOL bLineEnd = FALSE; // 行结束 int nReadLen = 0; // 读入字节数 int nDataLen = 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、程序运行效果

     源代码在VC6.0 + WIN XP SP2环境下编程运行正常:

    

 

5、源代码下载地址

     http://download.csdn.net/source/1679786

 

 


最新回复(0)