select 模型详解

来源:互联网 发布:中国电信网络测速112 编辑:程序博客网 时间:2024/06/15 21:48
nt SelectRecv(SOCKET hSocket, char *pszBuffer, int nBufferSize,          DWORD dwTimeout){     ASSERT(hSocket != NULL);    if(hSocket==NULL)        return ( SOCKET_ERROR );     FD_SET fd = {1, hSocket};     TIMEVAL tv = {dwTimeout, 0};    int nBytesReceived=0;    if(select(0, &fd, NULL, NULL, &tv) == 0)         goto CLEAR;    if((nBytesReceived = recv(hSocket, pszBuffer, nBufferSize, 0)) == SOCKET_ERROR)        goto CLEAR;    return nBytesReceived;CLEAR:     SetLastError(WSAGetLastError());//超时    return(SOCKET_ERROR);}

int SelectSend(SOCKET hSocket,char const * pszBuffer,         int nBufferSize, DWORD dwTimeout){    if(hSocket==NULL)        return(SOCKET_ERROR);    int nBytesSent = 0;    int nBytesThisTime;    const char* pszTemp = pszBuffer;    do {         nBytesThisTime = Send_Block(hSocket,pszTemp, nBufferSize-nBytesSent, dwTimeout);        if(nBytesThisTime<0)            return(SOCKET_ERROR);        //如果一次没有发送成功         nBytesSent += nBytesThisTime;        //改变当前字符指针         pszTemp += nBytesThisTime;     } while(nBytesSent < nBufferSize);    return nBytesSent;}


利用上面两个函数写发送和接收

下面来看看如何使用select模型建立我们的Server,

下面分了4个文件,大家可以拷贝下来实际编译一下。

首先有两个公用的文件:CommonSocket.h、CommonCmd.h。

他们都是放在 Common Include 目录中的!

然后就是SelectServer.cpp和SocketClient.cpp这两个文件,可以分别建立两个工程把他们加进去编译!

有些关键的地方我都写在文件注释里了,可以自己看看。


/********************************************************************************************************************/**********************************************************************************************************************/
第一个文件
/*/
文件:SelectServer.cpp
说明:
此文件演示了如何使用select模型来建立服务器,难点是select的writefds在什么时候使用。
好好看看代码就能很明白的了,可以说我写这些代码就是为了探索这个问题的!找了很多资料都找不到!!
在这里我怀疑是否可以同时读写同一个SOCKET,结果发现是可以的,但是最好别这样做。因为会导致包的顺序不一致。
这里说一下SELECT模型的逻辑:
我们如果不使用select模型,在调用recv或者send时候会导致程序阻塞。

如果使用了select,就给我们增加了一层保护,就是说在调用了select函数之后,对处于读集合的socket进行recv操作
是一定会成功的(这是操作系统给我们保证的)。对于判断SOCKET是否可写时也一样。
而且就算不可读或不可写,使用了select也不会锁死!因为 select 函数提供了超时!利用这个特性还可以
做异步connect,也就是可以扫描主机,看哪个主机开了服务(远程控制软件经常这样干哦!)


我们如何利用这种逻辑来设计我们的server呢?


这里用的方法是建立一个SocketInfo,这个SocketInfo包括了对Socket当前进行的操作,我把它分为:

{RecvCmd, RecvData, ExecCmd}

一开始socket是处于一个RecvCmd的状态,
然后取到了CMD(也就是取到了指令,可想象一下CPU得到了指令后干什么),然后就要取数据了,取得指令知道要干什么,

取得了数据就可以实际开始干了。

实际开始干就是ExecCmd,在这个状态之后都是需要发送数据的了,

所以把他们都放在判断SOCKET可写下面<就是 if(FD_ISSET(vecSocketInfo.sock, &fdWrite)) >,
即当Socket可写就可以发送信息给客户端了。


发送的根本协议是这样的:先发一个SCommand的结构体过去,这个结构体说明了指令和数据的长度。
然后就根据这个长度接收数据。最后再给客户端做出相应的响应!
根据这种代码结构,可以很方便的添加新的功能。

错误处理做得不太好,以后再补充了。
其他的如注释,结构,命名等的编码规范都用了个人比较喜欢的方式。
输出:
..BinSelectServer.exe
用法:
直接启动就可以了
Todo:
下一步首先完成各个SOCKET的模型,然后公开自己的研究代码。
功能方面就是:
1、服务器可以指定共享文件夹
2、客户端可以列出服务器共享了哪些文件
3、客户端可以列出哪些用户在线,并可以发命令和其他用户聊天
4、加上界面

/*/#include <winsock2.h>#pragma comment(lib, "WS2_32")#include <windows.h>#pragma warning(disable: 4786)#include <iostream>#include <vector>#include <map>#include <string>#include <algorithm>using namespace std;#include "..Common IncludeCommonSocket.h"#include "..Common IncludeCommonCmd.h"typedef struct tagSocketInfo{SOCKET sock;ECurOp eCurOp;SCommand cmd;char *data;}SSocketInfo;// 登录用户的列表map<string, SOCKET> g_LoginUsers;// 注册用户的列表(用户名,密码)map<string, string> g_RegUSers;// 用于退出服务器bool g_bExit = false;void DoRecvCmd(vector<SSocketInfo> &vecSockInfo, int idx);void DoRecvData(vector<SSocketInfo> &vecSockInfo, int idx);void DoExecCmd(vector<SSocketInfo> &vecSockInfo, int idx);bool DoAuthen(SOCKET sock, char *data, DWORD len);bool DoGetFile(SOCKET sock, char *data, DWORD len);bool DoRegister(SOCKET sock, char *data, DWORD len);void GetRegUsers();/////////////////////////////////////////////////////////////////////////// 函数名       : RemoveByIndex// 功能描述     : 根据 index 来删除 VECTOR 里的元素// 参数         : vector<T> &vec [in]// 参数         : int nIdx   [in]// 返回值       : void /////////////////////////////////////////////////////////////////////////template<class T>void EraseByIndex(vector<T> &vec, int nIdx){vector<T>::iterator it;it = vec.begin() + nIdx;vec.erase(it);}void main(){InitWinsock();vector<SSocketInfo> vecSocketInfo;SOCKET sockListen = BindServer(PORT);ULONG NonBlock = 1;ioctlsocket(sockListen, FIONBIO, &NonBlock);SOCKET sockClient;GetRegUsers();FD_SET fdRead;FD_SET fdWrite;while(!g_bExit){   // 每次调用select之前都要把读集和写集清空   FD_ZERO(&fdRead);   FD_ZERO(&fdWrite);     // 设置好读集和写集   FD_SET(sockListen, &fdRead);   for(int i = 0; i < vecSocketInfo.size(); i++)   {    FD_SET(vecSocketInfo[i].sock, &fdRead);    FD_SET(vecSocketInfo[i].sock, &fdWrite);   }   // 调用select函数   if(select(0, &fdRead, &fdWrite, NULL, NULL) == SOCKET_ERROR)   {    OutErr("select() Failed!");    break;   }   // 说明可以接受连接了   if(FD_ISSET(sockListen, &fdRead))   {    char szClientIP[50];    sockClient = AcceptClient(sockListen, szClientIP);    cout << szClientIP << " 连接上来" << endl;    ioctlsocket(sockClient, FIONBIO, &NonBlock);    SSocketInfo sockInfo;    sockInfo.sock = sockClient;    sockInfo.eCurOp = RecvCmd;    // 把接收到的这个socket加入自己的队列中    vecSocketInfo.push_back(sockInfo);   }   for(i = 0; i < vecSocketInfo.size(); i++)   {    // 如果可读    if(FD_ISSET(vecSocketInfo[i].sock, &fdRead))    {     switch(vecSocketInfo[i].eCurOp)     {     case RecvCmd:      DoRecvCmd(vecSocketInfo, i);      break;     case RecvData:      DoRecvData(vecSocketInfo, i);      break;          default:      break;     }    }    // 如果可写    if(FD_ISSET(vecSocketInfo[i].sock, &fdWrite))    {     switch(vecSocketInfo[i].eCurOp)     {     case ExecCmd:      DoExecCmd(vecSocketInfo, i);      break;         default:      break;     }    }   }}}/////////////////////////////////////////////////////////////////////////// 函数名       : DoRecvCmd// 功能描述     : 获取客户端传过来的cmd// 参数         : vector<SSocketInfo> &vecSockInfo// 参数         : int idx// 返回值       : void /////////////////////////////////////////////////////////////////////////void DoRecvCmd(vector<SSocketInfo> &vecSockInfo, int idx){SSocketInfo *sockInfo = &vecSockInfo[idx];int nRet = RecvFix(sockInfo->sock, (char *)&(sockInfo->cmd), sizeof(sockInfo->cmd));// 如果用户正常登录上来再用 closesocket 关闭 socket 会返回0 // 如果用户直接关闭程序会返回 SOCKET_ERROR,强行关闭if(nRet == SOCKET_ERROR || nRet == 0){   OutMsg("客户端已退出。");   closesocket(sockInfo->sock);   sockInfo->sock = INVALID_SOCKET;        EraseByIndex(vecSockInfo, idx);   return;}sockInfo->eCurOp = RecvData;}/////////////////////////////////////////////////////////////////////////// 函数名       : DoRecvData// 功能描述     : DoRecvCmd 已经获得了指令,接下来就要获得执行指令所需要的数据// 参数         : vector<SSocketInfo> &vecSockInfo// 参数         : int idx// 返回值       : void /////////////////////////////////////////////////////////////////////////void DoRecvData(vector<SSocketInfo> &vecSockInfo, int idx){SSocketInfo *sockInfo = &vecSockInfo[idx];// 为数据分配空间,分配多一位用来放最后的0sockInfo->data = new char[sockInfo->cmd.DataSize + 1];memset(sockInfo->data, 0, sockInfo->cmd.DataSize + 1);// 接收数据int nRet = RecvFix(sockInfo->sock, sockInfo->data, sockInfo->cmd.DataSize);if(nRet == SOCKET_ERROR || nRet == 0){   OutMsg("客户端已退出。");   closesocket(sockInfo->sock);   sockInfo->sock = INVALID_SOCKET;        EraseByIndex(vecSockInfo, idx);   return;}  sockInfo->eCurOp = ExecCmd;}/////////////////////////////////////////////////////////////////////////// 函数名       : DoExecCmd// 功能描述     : 指令和执行指令所需数据都已经准备好了,接下来就可以执行命令// 参数         : vector<SSocketInfo> &vecSockInfo// 参数         : int idx// 返回值       : void /////////////////////////////////////////////////////////////////////////void DoExecCmd(vector<SSocketInfo> &vecSockInfo, int idx){SSocketInfo *sockInfo = &vecSockInfo[idx];switch(sockInfo->cmd.CommandID) {case CMD_AUTHEN:   DoAuthen(sockInfo->sock, sockInfo->data, sockInfo->cmd.DataSize);    break;case CMD_GETFILE:   DoGetFile(sockInfo->sock, sockInfo->data, sockInfo->cmd.DataSize);   break;case CMD_REGISTER:   DoRegister(sockInfo->sock, sockInfo->data, sockInfo->cmd.DataSize);   break;default:   break;}// 执行完命令后就设置回接收指令状态sockInfo->eCurOp = RecvCmd;}/////////////////////////////////////////////////////////////////////////// 函数名       : DoAuthen// 功能描述     : 对用户名和密码做验证// 参数         : SOCKET sock// 参数         : char *data// 参数         : DWORD len// 返回值       : bool /////////////////////////////////////////////////////////////////////////bool DoAuthen(SOCKET sock, char *data, DWORD len){// 取得用户名和密码的字符串// 格式为 "dyl 123"char *pBuf = data;int nIdx = 0;char szName[10];memset(szName, 0, 10);char szPass[10];memset(szPass, 0, 10);while (*pBuf != ' '){   szName[nIdx++] = *pBuf++;}szName[nIdx] = '';*pBuf++;nIdx = 0;while (*pBuf != ''){   szPass[nIdx++] = *pBuf++;}szPass[nIdx] = '';char szSend[30];memset(szSend, 0, 30);bool bUserExist = false;if( g_RegUSers.find(string(szName)) != g_RegUSers.end() ){   if(strcmp(g_RegUSers[szName].c_str(), szPass) == 0)   {    strcpy(szSend, "UP OK!");    g_LoginUsers[szName] = sock;   }   else   {    strcpy(szSend, "P Err!");   }  }else{// 不存在这个用户   strcpy(szSend, "U Err!");}int nRet = SendFix(sock, szSend, strlen(szSend));if(nRet == SOCKET_ERROR)   return false;// 执行完了就释放datadelete []data;return true;}/////////////////////////////////////////////////////////////////////////// 函数名       : DoGetFile// 功能描述     : 为用户提供文件// 参数         : SOCKET sock// 参数         : char *data// 参数         : DWORD len// 返回值       : bool /////////////////////////////////////////////////////////////////////////bool DoGetFile(SOCKET sock, char *data, DWORD len){ // 打开文件,判断文件是否存在 HANDLE hFile = CreateFile(data, GENERIC_READ, FILE_SHARE_READ,   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) {  OutMsg("文件不存在!");  DWORD dwSize = 0;  SendFix(sock, (char *)&dwSize, sizeof(dwSize));  return false; } else {// 发送文件信息  // 发送文件大小,发送过去  DWORD dwFileSize = GetFileSize(hFile, NULL);  int nRet = SendFix(sock, (char *)&dwFileSize, sizeof(dwFileSize));  if(nRet == SOCKET_ERROR)   return false;    // 读文件记录并发送  DWORD nLeft = dwFileSize;  char szBuf[1024];  DWORD nCurrRead = 0;  while(nLeft > 0)  {   if(!ReadFile(hFile, szBuf, 1024, &nCurrRead, NULL))   {    OutErr("ReadFile failed!");    return false;   }   SendFix(sock, szBuf, nCurrRead);   nLeft -= nCurrRead;  }    CloseHandle(hFile); } delete []data; return true;}bool DoRegister(SOCKET sock, char *data, DWORD len){ // 取得用户名和密码的字符串 // 格式为 "dyl 123" bool bReturn = true; char *pBuf = data; int nIdx = 0; char szName[10]; memset(szName, 0, 10); char szPass[20]; memset(szPass, 0, 20); while (*pBuf != ' ') {  szName[nIdx++] = *pBuf++; } szName[nIdx] = '\0'; *pBuf++; nIdx = 0; while (*pBuf != '\0') {  szPass[nIdx++] = *pBuf++; } szPass[nIdx] = '\0'; char szSend[30]; memset(szSend, 0, 30);  HANDLE hFile = CreateFile("Users.lst", GENERIC_WRITE, FILE_SHARE_READ, NULL,   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) {  hFile = CreateFile("Users.lst", GENERIC_WRITE, FILE_SHARE_READ, NULL,    CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);  if(hFile == INVALID_HANDLE_VALUE)   {   OutMsg("创建文件Users.lst失败!");   strcpy(szSend, "REG ERR!");   bReturn = false;  }  else  {   // 在开始加   SetFilePointer(hFile, 0, 0, FILE_BEGIN);   DWORD dwWritten = 0;   if(!WriteFile(hFile, szName, 10, &dwWritten, NULL))   {    OutMsg("WriteFile failed!");    strcpy(szSend, "REG ERR!");    bReturn = false;   }   if(!WriteFile(hFile, szPass, 20, &dwWritten, NULL))   {    OutMsg("WriteFile failed!");    strcpy(szSend, "REG ERR!");    bReturn = false;   }      CloseHandle(hFile);   // 读回到已注册用户列表中   GetRegUsers();   strcpy(szSend, "REG OK!");  } } else {  // 移动到最后追加  SetEndOfFile(hFile);  DWORD dwWritten = 0;  if(!WriteFile(hFile, szName, 10, &dwWritten, NULL))  {   OutMsg("WriteFile failed!");   strcpy(szSend, "REG ERR!");   bReturn = false;  }  if(!WriteFile(hFile, szPass, 20, &dwWritten, NULL))  {   OutMsg("WriteFile failed!");   strcpy(szSend, "REG ERR!");   bReturn = false;  }  CloseHandle(hFile);  // 读回到已注册用户列表中  GetRegUsers();  strcpy(szSend, "REG OK!");   } int nRet = SendFix(sock, szSend, strlen(szSend)); if(nRet == SOCKET_ERROR)  bReturn = false; // 执行完了就释放data delete []data;  return bReturn;}void GetRegUsers(){ g_RegUSers.clear(); char szName[10]; char szPwd[20]; HANDLE hFile = CreateFile("Users.lst", GENERIC_READ, FILE_SHARE_READ, NULL,   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) {  OutMsg("用户列表不存在!"); } else {  DWORD dwFileSize = 0;  dwFileSize = GetFileSize(hFile, NULL);    SetFilePointer(hFile, 0, 0, FILE_BEGIN);  DWORD dwRead = 0;      DWORD dwLeft = dwFileSize;  while(dwLeft > 0)  {   memset(szName, 0, 10);   memset(szPwd, 0, 20);   if(!ReadFile(hFile, szName, 10, &dwRead, NULL))   {    DWORD dwErr = GetLastError();    OutMsg("ReadFile failed!");   }   dwLeft -= dwRead;   if(!ReadFile(hFile, szPwd, 20, &dwRead, NULL))   {    DWORD dwErr = GetLastError();    OutMsg("ReadFile failed!");   }   dwLeft -= dwRead;   g_RegUSers[szName] = szPwd;  }  } CloseHandle(hFile);}

第二个文件

/*/
文件:SocketClient.cpp

说明:
此文件是作为测试的客户端,实现了登录和取文件的功能。
和服务端的交互就是采用了发送命令、数据长度,然后发送具体的数据这样的顺序。
详细可看服务端的说明。

基本逻辑是这样的,客户端要先登录服务端,然后登录成功之后,才能进行相应的操作。

错误处理做得不太好,以后再补充了。

其他的如注释,结构,命名等的编码规范都用了个人比较喜欢的方式。

输出:
..\Bin\SocketClient.exe

用法:
可以 SocketClient Server_IP
或者直接启动SocketClient,会提示你输入服务端的IP

Todo:
下一步首先完成各个SOCKET的模型,然后公开自己的研究代码。
功能方面就是:
1、服务器可以指定共享文件夹
2、客户端可以列出服务器共享了哪些文件
3、客户端可以列出哪些用户在线,并可以发命令和其他用户聊天


#include <winsock2.h>#pragma comment(lib, "WS2_32")#include <iostream>using namespace std;#include <stdlib.h>#include "..\Common Include\CommonSocket.h"#include "..\Common Include\CommonCmd.h"bool g_bAuth = false;void GetFile(SOCKET sock);bool Auth(SOCKET sock, char *szName, char *szPwd);bool RegisterUser(SOCKET sock, char *szName, char *szPwd);/////////////////////////////////////////////////////////////////////////// 函数名       : Usage// 功能描述     : 提示程序用法// 返回值       : void /////////////////////////////////////////////////////////////////////////void Usage() {  printf("*******************************************\n");  printf("Socket Client                            \n");  printf("Written by DYL                         \n");  printf("Email: dylgsy@163.com                 \n");  printf("Usage: SocketClient.exe Server_IP          \n");  printf("*******************************************\n"); }/////////////////////////////////////////////////////////////////////////// 函数名       : Menu// 功能描述     : 选择服务的界面// 返回值       : void /////////////////////////////////////////////////////////////////////////void Menu(){ system("cls"); printf("********************************************\n");  printf("请选择操作:        \n\n");  printf("1、取得文件         \n");  printf("2、退出          \n");  printf("********************************************\n"); }/////////////////////////////////////////////////////////////////////////// 函数名       : LoginMenu// 功能描述     : 用户登录的界面// 返回值       : void /////////////////////////////////////////////////////////////////////////void LoginMenu(){ cout << "请按任意键继续操作." <<endl; getchar(); system("cls"); printf("********************************************\n");  printf("请选择操作:        \n\n");  printf("1、登录          \n");  printf("2、注册          \n");  printf("3、退出          \n");  printf("********************************************\n"); }/////////////////////////////////////////////////////////////////////////// 函数名       : Login// 功能描述     : 用户登录的界面逻辑// 参数         : SOCKET sock// 返回值       : bool /////////////////////////////////////////////////////////////////////////bool Login(SOCKET sock){ bool bGoOn = true; while(bGoOn) {  LoginMenu();  int nChoose = 0;  cin >> nChoose;  char szName[10];  char szPwd[20];  char szConfirmPwd[20];  memset(szName, 0, 10);  memset(szPwd, 0, 20);  memset(szConfirmPwd, 0, 20);  bool bGoOnLogin = true;  switch(nChoose)   {  case 1:   while(bGoOnLogin)   {    cout << "请输入你的用户名:";    cin >> szName;    cout << "请输入你的密码:";    cin >> szPwd;    if(Auth(sock, szName, szPwd))    {     return true;     }    else    {     char c;     cout << "继续登录?y/n" << endl;     cin >> c;     switch(c)      {     case 'y':      bGoOnLogin = true;      break;     case 'n':      bGoOnLogin = false;      break;     default:      break;     }    }   }   break;     case 2:   cout << "请输入你的用户名:";   cin >> szName;   cout << "请输入你的密码:";   cin >> szPwd;   cout << "请再次输入你的密码:";   cin >> szConfirmPwd;   if(strcmp(szPwd, szConfirmPwd) != 0)   {    cout << "前后密码不一致" << endl;   }   else   {    if(!RegisterUser(sock, szName, szPwd))    {     cout << "注册用户失败!" << endl;    }   }   break;  case 3:   bGoOn = false;   return false;  default:   break;  } } return false;}void main(int argc, char *argv[]){ system("cls"); char szServerIP[20]; memset(szServerIP, 0, 20); if(argc != 2) {  cout << "请输入服务器IP:";  cin >> szServerIP; } else {  strcpy(szServerIP, argv[1]); } InitWinsock(); SOCKET sockServer; sockServer = ConnectServer(szServerIP, PORT, 1); if(sockServer == NULL) {  OutErr("连接服务器失败!");  return; } else {  OutMsg("已和服务器建立连接!"); } // 要求用户登录 if(!Login(sockServer))  return; // 登录成功,让用户选择服务 int nChoose = 0; bool bExit = false; while(!bExit) {  Menu();  cin >> nChoose;  switch(nChoose)  {  case 1:  // 获取文件   GetFile(sockServer);   break;  case 2:   bExit = true;   break;     default:   break;  } } shutdown(sockServer, SD_BOTH); closesocket(sockServer);}/////////////////////////////////////////////////////////////////////////// 函数名       : Auth// 功能描述     : 用户登录认证// 参数         : SOCKET sock// 参数         : char *szName// 参数         : char *szPwd// 返回值       : bool /////////////////////////////////////////////////////////////////////////bool Auth(SOCKET sock, char *szName, char *szPwd){ char szCmd[50]; memset(szCmd, 0, 50); strcpy(szCmd, szName); strcat(szCmd, " "); strcat(szCmd, szPwd); SCommand cmd; cmd.CommandID = CMD_AUTHEN; cmd.DataSize = strlen(szCmd); int nRet; nRet = SendFix(sock, (char *)&cmd, sizeof(cmd)); if(nRet == SOCKET_ERROR) {  OutErr("SendFix() failed!");  return false; } else {  SendFix(sock, szCmd, strlen(szCmd));  char szBuf[10];  memset(szBuf, 0, 10);  recv(sock, szBuf, 10, 0);  if(strcmp(szBuf, "UP OK!") == 0)  {   cout << "登录成功。" << endl;   g_bAuth = true;  }  else if(strcmp(szBuf, "U Err!") == 0)  {   cout << "此用户不存在。" << endl;   g_bAuth = false;  }  else if(strcmp(szBuf, "P Err!") == 0)  {   cout << "密码错误。" << endl;   g_bAuth = false;  } } return g_bAuth;}/////////////////////////////////////////////////////////////////////////// 函数名       : GetFile// 功能描述     : 取得服务器的文件// 参数         : SOCKET sock// 返回值       : void /////////////////////////////////////////////////////////////////////////void GetFile(SOCKET sock){ if(!g_bAuth) {  OutMsg("用户还没登录!请先登录");  return; } char szSrcFile[MAX_PATH]; char szDstFile[MAX_PATH]; memset(szSrcFile, 0, MAX_PATH); memset(szDstFile, 0, MAX_PATH); cout << "你要取得Server上的文件:"; cin >> szSrcFile; cout << "你要把文件存在哪里:"; cin >> szDstFile; SCommand cmd; cmd.CommandID = CMD_GETFILE; cmd.DataSize = strlen(szSrcFile); // 发送命令 SendFix(sock, (char *)&cmd, sizeof(cmd)); // 发送文件名 SendFix(sock, szSrcFile, strlen(szSrcFile)); // 接收文件长度 DWORD dwFileSize = 0; RecvFix(sock, (char*)&dwFileSize, sizeof(dwFileSize)); if(dwFileSize == 0) {  OutMsg("文件不存在");  return; } // 接收文件内容 DWORD dwLeft = dwFileSize; char szBuf[1024]; HANDLE hFile = CreateFile(szDstFile, GENERIC_WRITE, FILE_SHARE_READ,   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) {  hFile = CreateFile(szDstFile, GENERIC_WRITE, FILE_SHARE_READ,    NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);  if(hFile == INVALID_HANDLE_VALUE)  {   OutErr("CreateFile failed!");   return;  } } while(dwLeft > 0) {  memset(szBuf, 0, 1024);  // 这里是不确定文件内容的,所以要用recv,不能用RecvFix  int nRead = recv(sock, szBuf, 1024, 0);  if(nRead == SOCKET_ERROR)   OutErr("RecvFix Error!");  DWORD dwWritten = 0;  if(!WriteFile(hFile, szBuf, nRead, &dwWritten, NULL))  {   OutErr("WriteFile error!");   return;  }  dwLeft -= dwWritten; } CloseHandle(hFile); OutMsg("接收文件成功!");}/////////////////////////////////////////////////////////////////////////// 函数名       : RegisterUser// 功能描述     : 注册新用户// 参数         : SOCKET sock// 参数         : char *szName// 参数         : char *szPwd// 返回值       : bool /////////////////////////////////////////////////////////////////////////bool RegisterUser(SOCKET sock, char *szName, char *szPwd){ char szCmd[50]; memset(szCmd, 0, 50); strcpy(szCmd, szName); strcat(szCmd, " "); strcat(szCmd, szPwd); SCommand cmd; cmd.CommandID = CMD_REGISTER; cmd.DataSize = strlen(szCmd); // 发送命令 int nRet = SendFix(sock, (char *)&cmd, sizeof(cmd)); if(nRet == SOCKET_ERROR) {  OutErr("SendFix() failed!");  return false; } else {  // 发送用户名和密码串  SendFix(sock, szCmd, strlen(szCmd));  char szBuf[10];  memset(szBuf, 0, 10);    recv(sock, szBuf, 10, 0);  if(strcmp(szBuf, "REG OK!") == 0)  {   cout << "注册成功。" << endl;   return true;  }  else if(strcmp(szBuf, "REG ERR!") == 0)  {   cout << "注册失败." << endl;   return false;  } } return false;}

第三个文件

文件: CommonSocket.h
说明:
实现了服务端和客户端一些公用的函数!


#ifndef __COMMONSOCKET_H__#define __COMMONSOCKET_H__#include <iostream>using namespace std;#define OutErr(a) cout << (a) << endl \      << "出错代码:" << WSAGetLastError() << endl \      << "出错文件:" << __FILE__ << endl  \      << "出错行数:" << __LINE__ << endl \#define OutMsg(a) cout << (a) << endl;/////////////////////////////////////////////////////////////////////////// 函数名       : InitWinsock// 功能描述     : 初始化WINSOCK// 返回值       : void /////////////////////////////////////////////////////////////////////////void InitWinsock(){ // 初始化WINSOCK WSADATA wsd; if( WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {  OutErr("WSAStartup()"); }}/////////////////////////////////////////////////////////////////////////// 函数名       : ConnectServer// 功能描述     : 连接SERVER// 参数         : char *lpszServerIP IP地址// 参数         : int nPort    端口// 返回值       : SOCKET    SERVER 的 Socket/////////////////////////////////////////////////////////////////////////SOCKET ConnectServer(char *lpszServerIP, int nPort, ULONG NonBlock){ SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //ioctlsocket(sServer, FIONBIO, &NonBlock); struct hostent *pHost = NULL; struct sockaddr_in servAddr; servAddr.sin_family = AF_INET; servAddr.sin_port = htons(nPort); servAddr.sin_addr.s_addr = inet_addr(lpszServerIP); // 如果给的是主机的名字而不是IP地址 if(servAddr.sin_addr.s_addr == INADDR_NONE) {  pHost = gethostbyname( lpszServerIP );  if(pHost == NULL)  {   OutErr("gethostbyname Failed!");   return NULL;  }  memcpy(&servAddr.sin_addr, pHost->h_addr_list[0], pHost->h_length); } int nRet = 0; nRet = connect(sServer, (struct sockaddr *)&servAddr, sizeof(servAddr)); if( nRet == SOCKET_ERROR ) {  OutErr("connect failed!");  return NULL; }   return sServer;}/////////////////////////////////////////////////////////////////////////// 函数名       : BindServer// 功能描述     : 绑定端口// 参数         : int nPort// 返回值       : SOCKET /////////////////////////////////////////////////////////////////////////SOCKET BindServer(int nPort){ // 创建socket  SOCKET sServer = socket(AF_INET, SOCK_STREAM, 0); // 绑定端口 struct sockaddr_in servAddr; servAddr.sin_family = AF_INET; servAddr.sin_port = htons(nPort); servAddr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sServer, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0) {  OutErr("bind Failed!");  return NULL; } // 设置监听队列为200 if(listen(sServer, 200) != 0) {  OutErr("listen Failed!");  return NULL; } return sServer;}/////////////////////////////////////////////////////////////////////////// 函数名       : AcceptClient// 功能描述     : // 参数         : SOCKET sServer [in]// 参数         : LPSTR lpszIP  [out] 返回客户端的IP地址 // 返回值       : SOCKET   [out] 返回客户端的socket/////////////////////////////////////////////////////////////////////////SOCKET AcceptClient(SOCKET sListen, LPSTR lpszIP){ struct sockaddr_in cliAddrTmp; int cliAddrSize = sizeof(struct sockaddr_in); SOCKET sClient = accept(sListen, (struct sockaddr *)&cliAddrTmp, &cliAddrSize); if(sClient == INVALID_SOCKET) {  OutErr("accept failed!");  return NULL; } sprintf(lpszIP, "%s", inet_ntoa(cliAddrTmp.sin_addr)); return sClient;}/////////////////////////////////////////////////////////////////////////// 函数名       : RecvFix// 功能描述     : 接收指定长度的数据,考虑非阻塞socket的情况// 参数         : SOCKET socket [in]// 参数         : char *data [in]// 参数         : DWORD len  [in]// 参数         : DWORD *retlen [out]// 返回值       : bool /////////////////////////////////////////////////////////////////////////int RecvFix(SOCKET socket, char *data, DWORD len){ int retlen = 0; int nLeft = len; int nRead = 0; char *pBuf = data; while(nLeft > 0) {  nRead = recv(socket, pBuf, nLeft, 0);  if(nRead == SOCKET_ERROR || nRead == 0)  {   if(WSAEWOULDBLOCK == WSAGetLastError())    continue;   else    return nRead;  }    nLeft -= nRead;  retlen += nRead;  pBuf += nRead; } return nRead;}/////////////////////////////////////////////////////////////////////////// 函数名       : SendFix// 功能描述     : 发送指定长度的数据,考虑非阻塞socket的情况// 参数         : SOCKET socket// 参数         : char *data// 参数         : DWORD len// 参数         : DWORD *retlen// 返回值       : bool /////////////////////////////////////////////////////////////////////////int SendFix(SOCKET socket, char *data, DWORD len){ int retlen = 0; int nLeft = len; int nWritten = 0; const char *pBuf = data; while(nLeft > 0) {  nWritten = send(socket, data, nLeft, 0);  if(nWritten == SOCKET_ERROR || nWritten == 0)  {   if(WSAEWOULDBLOCK == WSAGetLastError())    continue;   else    return nWritten;  }    nLeft -= nWritten;  retlen += nWritten;  pBuf += nWritten; } return nWritten;}/*/////////////////////////////////////////////////////////////////////////// 函数名       : SelectSend// 功能描述     : 使用select模型来发送数据,没完成,所以注释掉了// 参数         : SOCKET sock// 参数         : FD_SET *wfds// 参数         : char *data// 参数         : DWORD len// 返回值       : bool /////////////////////////////////////////////////////////////////////////bool SelectSend(SOCKET sock, FD_SET *wfds, char *data, DWORD len){ FD_ZERO(wfds); FD_SET(sock, wfds); if(select(0, NULL, wfds, NULL, NULL) == SOCKET_ERROR) {  OutErr("select() Failed!");  return false; } // 如果是可以写的SOCKET,就一直写,直到返回WSAEWOULDBLOCK if( FD_ISSET(sock, wfds) ) {  int nLeft = len;  while(nLeft > 0)  {   int nRet = send(sock, data, len, 0);   if(nRet == SOCKET_ERROR)    return false;   nLeft -= nRet;  } } return true;}/////////////////////////////////////////////////////////////////////////// 函数名       : SelectRecv// 功能描述     : 使用select模型来接收数据,没完成,所以注释掉了// 参数         : SOCKET sock// 参数         : FD_SET *rfds// 参数         : char *data// 参数         : DWORD len// 返回值       : bool /////////////////////////////////////////////////////////////////////////bool SelectRecv(SOCKET sock, FD_SET *rfds, char *data, DWORD len){ FD_ZERO(rfds); FD_SET(sock, rfds); if(select(0, rfds, NULL, NULL, NULL) == SOCKET_ERROR) {  OutErr("select() Failed!");  return false; } if( FD_ISSET(sock, rfds) ) {  int nLeft = len;  while(nLeft > 0)  {   int nRet = recv(sock, data, len, 0);   if(nRet == SOCKET_ERROR)    return false;   nLeft -= nRet;  } } return true;}*/#endif //__COMMONSOCKET_H__

第四个文件,公用的

/*/
文件: CommonCmd.h
说明:
实现了服务端和客户端一些公用的数据结构,所以服务端和客户端都要包含。
其中有命令、SOCKET的当前状态等的定义。
/*/

#ifndef __COMMONCMD_H__#define __COMMONCMD_H__#define PORT 5050// 命令定义#define CMD_AUTHEN 1 // 登录认证#define CMD_GETFILE 2 // 获取文件#define CMD_REGISTER 3 // 注册用户typedef struct tagCommand{int CommandID; // 命令IDDWORD DataSize; // 后接数据的大小}SCommand;// 标志目前的SOCKET该做什么enum ECur
原创粉丝点击