青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 45  文章 - 129  trackbacks - 0
<2007年1月>
31123456
78910111213
14151617181920
21222324252627
28293031123
45678910

專注于C++ P2P STL GP OpenSource等
Google

常用鏈接

留言簿(10)

隨筆分類

隨筆檔案

相冊

朋友

  • .NET

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

轉貼自:http://blog.csdn.net/dylgsy/

查找了很多資料都找不到select模型的詳細用法,《Windows網絡編程》這本書上也只是寫了一個簡單的回應服務器,就連writefds的用法都沒講,也不知道什么時候利用“可寫”來發文件。這些都是我的疑問,相信很多研究網絡編程的同路人也碰到了我的這些問題。這些疑問在這篇文章中都解決了!耗費了偶很多的精力去猜測去思考!?

感覺一些已經得道的高人都不肯把這些問題說透徹點,唉,只能靠自己去摸索了,希望這篇文章能對你有用,也希望一些高人能出來指點指點!

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,也就是可以掃描主機,看哪個主機開了服務(遠程控制軟件經常這樣干哦?。?/font>

?我們如何利用這種邏輯來設計我們的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__

?好了,上面四個文件都搞好了,有興趣的朋友自己去弄吧,希望對你們有用,好累?。▽懳恼率且患芾鄣氖虑椋?。有什么問題歡迎探討!

posted on 2007-01-19 16:44 CPP&&設計模式小屋 閱讀(2092) 評論(1)  編輯 收藏 引用 所屬分類: Network

FeedBack:
# re: WinSocket模型的探討——select模型(轉 收藏)[未登錄] 2007-02-20 11:54 walkspeed
有些意識。自己的想法和總結,還是不錯的。
要是能進一步設計下,寫成相互協作的類組就更有意識了。

ace中accept-connect框架完成了這類應用的,不過就是框架很大。研究起來還是很煩的。你這個思想明確,架構簡單,可以發展一下。  回復  更多評論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
      <noscript id="pjuwb"></noscript>
            <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
              <dd id="pjuwb"></dd>
              <abbr id="pjuwb"></abbr>
              亚洲小说欧美另类社区| 久久久精品动漫| 欧美亚州在线观看| 一本色道88久久加勒比精品| 亚洲欧美日韩在线播放| 国产亚洲欧美另类中文| 91久久视频| 欧美激情国产日韩| 亚洲国产精品va在线看黑人| 亚洲成色最大综合在线| 欧美精品一区二区三区在线播放| 亚洲欧洲一区二区在线播放 | 激情六月婷婷综合| 欧美v国产在线一区二区三区| 亚洲另类在线一区| 久久精品国产99国产精品| 91久久香蕉国产日韩欧美9色| 欧美日韩一区二区三区在线| 欧美在线观看网站| 亚洲黄色影院| 久久久av水蜜桃| 日韩亚洲欧美综合| 国内精品久久久久久| 欧美激情综合在线| 欧美一区成人| 99riav国产精品| 久久综合999| 亚洲一区二区三区精品在线观看| 国产一区日韩二区欧美三区| 欧美日本精品在线| 久久久夜精品| 亚洲一级特黄| 91久久综合| 美女啪啪无遮挡免费久久网站| 在线亚洲伦理| 最新亚洲电影| 国模精品娜娜一二三区| 欧美视频免费在线观看| 久久中文精品| 欧美一区二区成人6969| 日韩亚洲精品在线| 欧美激情aⅴ一区二区三区 | 欧美一区综合| 一本色道久久综合狠狠躁篇的优点 | 最新69国产成人精品视频免费| 久久久999国产| 亚洲一区二区免费视频| 亚洲另类在线视频| 亚洲成色www久久网站| 国产一区三区三区| 国产精品日韩欧美一区二区| 欧美全黄视频| 欧美激情精品久久久久久久变态| 午夜欧美电影在线观看| 亚洲视频第一页| 亚洲黄色性网站| 亚洲第一精品福利| 欧美国产激情二区三区| 老司机成人在线视频| 久久精品视频免费播放| 欧美一区二区黄| 亚洲一区精品电影| 亚洲影视九九影院在线观看| 9国产精品视频| 99国产精品99久久久久久粉嫩| 亚洲欧洲日韩综合二区| 亚洲欧洲在线看| 亚洲日本黄色| 亚洲免费观看视频| 一本综合久久| 亚洲午夜日本在线观看| 亚洲一区二区三区视频| 一区二区三区日韩欧美| 中文亚洲视频在线| 一区二区三区色| 亚洲愉拍自拍另类高清精品| 亚洲欧美国产高清| 午夜欧美大尺度福利影院在线看| 亚洲国产精品一区二区第一页| 欧美三级韩国三级日本三斤| 欧美三日本三级少妇三2023| 欧美午夜精品理论片a级大开眼界 欧美午夜精品理论片a级按摩 | 久久久久久网站| 久久综合九色综合久99| 欧美va亚洲va国产综合| 亚洲高清一区二区三区| 亚洲欧洲一级| 亚洲亚洲精品在线观看 | 欧美一区二区成人6969| 久久嫩草精品久久久精品| 欧美xart系列在线观看| 欧美精品在线网站| 国产精品区一区二区三区| 国产在线欧美日韩| 亚洲黄色性网站| 国产精品99久久久久久www| 午夜精品视频在线观看一区二区| 久久国产66| 亚洲电影在线| 亚洲小说欧美另类社区| 久久国产精品久久久久久久久久| 噜噜噜在线观看免费视频日韩| 欧美片在线观看| 国产午夜久久| 亚洲精品久久7777| 午夜欧美精品久久久久久久| 久久在线免费观看视频| 亚洲精品乱码久久久久久| 亚洲欧美日韩在线| 免费观看30秒视频久久| 国产精品国产福利国产秒拍| 国内精品久久久久久影视8 | 欧美性猛交xxxx乱大交蜜桃| 国产一区二区日韩| 99视频热这里只有精品免费| 欧美在线高清视频| 欧美福利在线| 亚洲欧美日韩综合| 欧美风情在线| 红桃视频成人| 亚洲一区二区三区乱码aⅴ| 猛男gaygay欧美视频| 在线一区欧美| 猫咪成人在线观看| 国产日韩欧美一区二区三区在线观看| 亚洲欧洲一区二区在线观看| 欧美一区二区三区视频免费播放| 亚洲国产美女精品久久久久∴| 午夜精品美女自拍福到在线| 欧美国产免费| 在线色欧美三级视频| 欧美一区二区网站| 99在线精品视频| 欧美成人官网二区| 永久91嫩草亚洲精品人人| 午夜精品久久久久久久99樱桃| 亚洲高清网站| 老巨人导航500精品| 国产一区二区三区四区三区四| 一区二区三区视频在线看| 欧美高清在线观看| 久久精品成人一区二区三区蜜臀 | 国产亚洲欧美激情| 午夜精品国产| 一片黄亚洲嫩模| 欧美人妖另类| 99国产麻豆精品| 亚洲国产导航| 美女图片一区二区| 伊人久久大香线蕉av超碰演员| 欧美在线观看网站| 亚洲自拍偷拍网址| 国产精品入口麻豆原神| 亚洲一区亚洲二区| 一区二区三区精密机械公司| 欧美日韩中文字幕在线视频| 91久久国产综合久久蜜月精品| 亚洲无毛电影| 9国产精品视频| 欧美日韩国产麻豆| 亚洲视频在线二区| 99成人精品| 欧美性色aⅴ视频一区日韩精品| 日韩一区二区福利| 日韩一区二区福利| 国产精品hd| 午夜一区二区三视频在线观看| 亚洲主播在线| 国产一区二区福利| 老司机一区二区| 老妇喷水一区二区三区| 亚洲精品日韩激情在线电影| 亚洲日韩第九十九页| 欧美日韩久久不卡| 亚洲午夜电影| 亚洲欧美国产制服动漫| 国产日韩欧美一区| 欧美成人中文字幕| 欧美激情乱人伦| 亚洲视频免费在线| 亚洲欧美日韩人成在线播放| 国产一区二区三区直播精品电影| 麻豆精品视频| 欧美日本国产在线| 性xx色xx综合久久久xx| 久久av资源网| 亚洲理论在线观看| 亚洲线精品一区二区三区八戒| 国产专区综合网| 亚洲第一搞黄网站| 国产精品美女久久久浪潮软件| 久久精品成人一区二区三区蜜臀 | 亚洲欧美色婷婷| 影音先锋久久| 99精品久久免费看蜜臀剧情介绍| 国产欧美日韩一区二区三区| 嫩草影视亚洲| 欧美午夜精彩| 男人的天堂亚洲在线| 国产精品久久久久毛片软件|