转自:http://blog.csdn.net/dylgsy/archive/2007/01/18/1487143.aspx

SOCKET模型的出现是为了解决“一个客户端一线程”的问题,为了WINDOWS的线程切换不要太频繁,我们可以使用WINDOWS的SOCKET模型。但在论坛里又看到了一些文章说现在的计算机硬件相当发达,成万的线程切换根本不是什么问题。无论怎么样,既然这些模型被MS发明了出来,就有它的道理,我们还是好好来学习一下吧。

对于select模型,大家都知道用select函数,然后判断读集、写集。但如何使用select,最终写好的Server又是一个怎么样的结构呢?

select可以这样用,写成两个函数,SelectSend和SelectRecv,这两个函数和一般的send/recv不同的地方在于它是有超时值的,这样就不会把程序完全阻塞了。这两个函数大概如下(参考《PC网络游戏编程》):

SelectRecv(...)

int 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);
}

SelectSend(...)

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[i].sock, &fdWrite)) >,
 即当Socket可写就可以发送信息给客户端了。

发送的根本协议是这样的:先发一个SCommand的结构体过去,这个结构体说明了指令和数据的长度。
 然后就根据这个长度接收数据。最后再给客户端做出相应的响应!

根据这种代码结构,可以很方便的添加新的功能。

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

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

输出:
 ../Bin/SelectServer.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 Include/CommonSocket.h"
#include "../Common Include/CommonCmd.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];
 // 为数据分配空间,分配多一位用来放最后的0
 sockInfo->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] = '/0';

*pBuf++;

nIdx = 0;
  while (*pBuf != '/0')
  {
   szPass[nIdx++] = *pBuf++;
  }
  szPass[nIdx] = '/0';

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;

// 执行完了就释放data
  delete []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;  // 命令ID
 DWORD DataSize;  // 后接数据的大小
}SCommand;

// 标志目前的SOCKET该做什么
enum ECurOp
{RecvCmd, RecvData, ExecCmd};

#endif //__COMMONCMD_H__

WinSocket模型的探讨——select模型相关推荐

  1. WinSocket模型的探讨——完成端口模型

    众所皆知,完成端口是在WINDOWS平台下效率最高,扩展性最好的IO模型,特别针对于WINSOCK的海量连接时,更能显示出其威力.其实建立一个完成端口的服务器也很简单,只要注意几个函数,了解一下关键的 ...

  2. 朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较

    经过之前四篇博文的介绍,可以大致清楚各种模型的编程步骤.现在我们来回顾下各种模型(转载请指明出于breaksoftware的csdn博客) 模型编程步骤对比 <朴素.Select.Poll和Ep ...

  3. linux下多路复用模型之Select模型

    Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模 ...

  4. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--朴素模型>中我们分析了朴素模型的一个缺陷--一次只能处理一个连接.本文介绍的Select模型则可以解决这个问题.(转载 ...

  5. winsock select模型实现

    select模型的原理是在指定的socket 的数组中轮询的采集是否可写.可读.有异常的信息,然后将可以操做的套接字 保存在指定的数组中. // selectsocket.cpp : 定义控制台应用程 ...

  6. R语言构建catboost模型:构建catboost模型并基于网格搜索获取最优模型参数(Select hyperparameters)、计算特征重要度

    R语言构建catboost模型:构建catboost模型并基于网格搜索获取最优模型参数(Select hyperparameters).计算特征重要度(feature importance) 目录

  7. java socket编程 select_windows socket编程select模型使用

    int select( int nfds,            //忽略 fd_ser* readfds,    //指向一个套接字集合,用来检测其可读性 fd_set* writefds,   / ...

  8. WinSock学习笔记3:Select模型

    WinSock学习笔记3:Select模型 unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, G ...

  9. socket select模型

    由于socket recv()方法是堵塞式的,当多个客户端连接服务器时,其中一个socket的recv调用时,会产生堵塞,使其他连接不能继续. 如果想改变这种一直等下去的焦急状态,可以多线程来实现(不 ...

  10. Select模型原理

    Select模型原理 利用select函数,推断套接字上是否存在数据,或者是否能向一个套接字写入数据.目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据, ...

最新文章

  1. 织梦html地图插件,织梦dede网站地图xml生成插件(图文教程)
  2. 怎样才能让一段代码每隔一段时间执行一次?
  3. DataGridView使用技巧十:单元格表示值的自定义
  4. 视频光端机选型及常见品牌
  5. jboss修改服务器端口,改了默认端口的jboss不能用shutdown.sh关闭,怎样解决
  6. Intellij IDEA 修改默认配置
  7. TFS2018环境搭建一硬件要求
  8. 信息集成项目管理工程师 学习资料_如何备考系统集成项目管理工程师?
  9. 空间权重矩阵构建(Stata代码)
  10. html5 flash播发器,什么时候HTML5视频应该回归到Flash播放器?
  11. 佳能6d2无线链接计算机操作,玩转EOS 6D无线WiFi功能三步骤
  12. Enjoying virus ⌒●ǒ●⌒
  13. adobe scout cc 2015中文版下载(附使用教程)
  14. leetcode 台阶_leetcode-爬楼梯(动态规划)
  15. Intel主板芯片组发展历史
  16. DCC-MGARCH:动态条件相关系数模型(R+Stata)
  17. vue中使用html5的drag实现任意位置拖动
  18. 什么是期权?如何从概念上理解看涨期权/看跌期权?
  19. 修复登录接口仿抽奖助手微信小程序源码下载
  20. 双线机房双IP linux设置路由

热门文章

  1. mysql怎么直接显示对象信息_详解 Navicat 查看方式之对象列表
  2. win7计算机相机,笔记本win7怎么拍照_win7电脑照相机如何打开
  3. python黑白方格画
  4. 数据结构手把手教学——单向循环链表
  5. 【计组笔记-1】ASCII码,区位码,国标码,汉字内码,以及万国码
  6. c语言开发 kdj,KDJ——随机指标之王
  7. 微信网页授权页面获取不到code
  8. 用手机写代码,在网页中写代码
  9. 5g和芯片有什么关系
  10. PS图层蒙版、参考线显示边距、盖印图层