c++ 使用socket實現(xiàn)C/S端文件的下載傳輸
c++ 使用socket實現(xiàn)C/S端文件的下載傳輸
首先是服務(wù)器端,大致說下流程:服務(wù)器創(chuàng)建線程去處理應(yīng)答accept(),當(dāng)接受到客戶端連接請求時,首先獲取要發(fā)送的指定的文件數(shù)據(jù)總大小給客戶端,接著就是循環(huán)讀取要發(fā)送的文件數(shù)據(jù)流向客戶端發(fā)送文件數(shù)據(jù),每次都判斷循環(huán)讀取到的數(shù)據(jù)實際大小,當(dāng)實際讀取到的數(shù)據(jù)總大小為0時,表示文件發(fā)送結(jié)束。下面是服務(wù)器server端實現(xiàn):
聲明部分:
public:
??? afx_msg void OnBnClickedButton1();
public:
??? BOOL InitSocket();??? //初始化并創(chuàng)建套接字
??? static DWORD WINAPI ThreadProc(LPVOID lpParameter);??? //創(chuàng)建線程去執(zhí)行服務(wù)器accept()
實現(xiàn)部分:
void CSendFileServerDlg::OnBnClickedButton1()
{
??? // TODO: 在此添加控件通知處理程序代碼
??? if (InitSocket())
??? {
??????? GetDlgItem(IDC_EDIT1)->SetWindowText(_T("服務(wù)器開啟監(jiān)聽。。。 rn"));
??????? //創(chuàng)建線程
??????? HANDLE hThread = CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
??????? //關(guān)閉該接收線程句柄,釋放引用計數(shù)
??????? CloseHandle(hThread);
??? }
}
BOOL CSendFileServerDlg::InitSocket()
{
??? //加載套接字庫
??? WORD wVersionRequested;
??? WSADATA wsaData;
??? int err;
??? wVersionRequested = MAKEWORD( 1, 1 );
??? err = WSAStartup( wVersionRequested, &wsaData );
??? if ( err != 0 )
??? {
??????? return FALSE;
??? }
??? if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 )
??? {
??????????? WSACleanup( );
??????????? return FALSE;
??? }
??? //創(chuàng)建套接字
??? //SOCKET m_socket=socket(AF_INET,SOCK_STREAM,0);
??? m_socket=socket(AF_INET,SOCK_STREAM,0);
??? if (m_socket == INVALID_SOCKET)
??? {
??????? AfxMessageBox(_T("套接字創(chuàng)建失敗!"));
??????? return FALSE;
??? }
??? SOCKADDR_IN addrSrv;
??? addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
??? addrSrv.sin_family=AF_INET;
??? addrSrv.sin_port=htons(8099);
??? err = bind(m_socket,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));??? //綁定本地端口
??? if (err==SOCKET_ERROR)
??? {
??????? closesocket(m_socket);
??????? AfxMessageBox(_T("綁定失敗!"));
??????? return FALSE;
??? }
??? listen(m_socket,5);//開啟監(jiān)聽
??? return TRUE;
}
DWORD WINAPI CSendFileServerDlg::ThreadProc(LPVOID lpParameter)
{
??? SOCKADDR_IN addrClient;
??? int len = sizeof(SOCKADDR);
??? while (true)
??? {
??????? SOCKET sockConn=accept(m_socket,(SOCKADDR*)&addrClient,&len);
??????? CString filename = _T("E:\test.zip");
??????? HANDLE hFile;
??????? unsigned long long file_size = 0;
??????? char Buffer[1024];
??????? DWORD dwNumberOfBytesRead;
??????? hFile = CreateFile(filename,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
??????? file_size = GetFileSize(hFile,NULL);
??????? send(sockConn,(char*)&file_size,sizeof(unsigned long long)+1,NULL);
??????? do
??????? {
??????????? ::ReadFile(hFile,Buffer,sizeof(Buffer),&dwNumberOfBytesRead,NULL);
??????????? ::send(sockConn,Buffer,dwNumberOfBytesRead,0);
??????? } while (dwNumberOfBytesRead);
??????? CloseHandle(hFile);
??? }
??? return 0;
}
如代碼所述? 每次發(fā)送單位是unsigned char[1024]大小(程序是char 應(yīng)該為unsigned char[1024])所以就不存在網(wǎng)絡(luò)字節(jié)序問題也不用考慮大端小端什么的。
服務(wù)器端暫時不支持多客戶端并發(fā)訪問,后續(xù)可能會加上。。。
-------------------------------------------
下面是客戶端,同樣也大致說下客戶端流程,客戶端增加手動填寫Ip地址和端口號功能(端口號暫為8099)。以及下載傳輸文件數(shù)據(jù)進(jìn)度條的顯示,和下面簡單的一些狀態(tài)顯示。客觀端由填寫的IP地址進(jìn)行連接服務(wù)器操作,如果客戶端連接服務(wù)器成功的話直接就會獲取服務(wù)器端發(fā)送的要發(fā)送的文件數(shù)據(jù)的總大小,如果獲取文件總大小>0 則會循環(huán)往指定的路徑寫數(shù)據(jù)啦。此處循環(huán)寫文件結(jié)束標(biāo)志,我是用每次實際寫的累加如果累計值等于從服務(wù)器端獲取的文件總大小的話表示下載文件數(shù)據(jù)成功,結(jié)束循環(huán)。大致是這樣一個過程。代碼實現(xiàn):
客戶端聲明部分:
?public:
??? afx_msg void OnBnClickedButton1();
?
??? BOOL InitSocket();
??? void ConnectServer();
??? void ConnectRecvFileData(DWORD ip,int port);
?
private:
??? CProgressCtrl *m_progress; //進(jìn)度條
?進(jìn)度條在OnInitDialog()里初始化:
按 Ctrl+C 復(fù)制代碼
BOOL CRecvFileClientDlg::OnInitDialog()
{
??? CDialog::OnInitDialog();
??? // 將“關(guān)于...”菜單項添加到系統(tǒng)菜單中。
??? // IDM_ABOUTBOX 必須在系統(tǒng)命令范圍內(nèi)。
??? ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
??? ASSERT(IDM_ABOUTBOX < 0xF000);
??? CMenu* pSysMenu = GetSystemMenu(FALSE);
??? if (pSysMenu != NULL)
??? {
??????? CString strAboutMenu;
??????? strAboutMenu.LoadString(IDS_ABOUTBOX);
??????? if (!strAboutMenu.IsEmpty())
??????? {
??????????? pSysMenu->AppendMenu(MF_SEPARATOR);
??????????? pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
??????? }
??? }
??? // 設(shè)置此對話框的圖標(biāo)。當(dāng)應(yīng)用程序主窗口不是對話框時,框架將自動
??? //? 執(zhí)行此操作
??? SetIcon(m_hIcon, TRUE);??????????? // 設(shè)置大圖標(biāo)
??? SetIcon(m_hIcon, FALSE);??????? // 設(shè)置小圖標(biāo)
??? // TODO: 在此添加額外的初始化代碼
??? m_progress = (CProgressCtrl*)GetDlgItem(IDC_PROGRESS1);
??? m_progress->SetPos(0);
??? return TRUE;? // 除非將焦點設(shè)置到控件,否則返回 TRUE
}
按 Ctrl+C 復(fù)制代碼
客戶端具體實現(xiàn)部分:
按 Ctrl+C 復(fù)制代碼
void CRecvFileClientDlg::OnBnClickedButton1()
{
??? // TODO: 在此添加控件通知處理程序代碼
??? ConnectServer();
???
}
BOOL CRecvFileClientDlg::InitSocket()
{
??? //加載套接字庫
??? WORD wVersionRequested;
??? WSADATA wsaData;
??? int err;
??? wVersionRequested = MAKEWORD( 1, 1 );
??? err = WSAStartup( wVersionRequested, &wsaData );
??? if ( err != 0 )
??? {
??????? return FALSE;
??? }
??? if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 )
??? {
??????? WSACleanup( );
??????? return FALSE;
??? }
??? return TRUE;
}
void CRecvFileClientDlg::ConnectRecvFileData(DWORD ip,int port)
{
???
??? unsigned long long file_size=0;
??? SOCKET sockClient = socket(AF_INET,SOCK_STREAM,0);
??? SOCKADDR_IN addrSrv;
??? addrSrv.sin_addr.S_un.S_addr=htonl(ip);
??? addrSrv.sin_port=ntohs(port);
??? addrSrv.sin_family = AF_INET;
??? //connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
??? //recv(sockClient,(char*)&file_size,sizeof(unsigned long long)+1,NULL);
??? if (!connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)))
??? {
??????? GetDlgItem(IDC_SHOWINFO)->SetWindowText(_T(""));
??????? GetDlgItem(IDC_SHOWINFO)->SetWindowText(_T("連接服務(wù)器成功!rn"));
???????
??????? recv(sockClient,(char*)&file_size,sizeof(unsigned long long)+1,NULL);
??????? unsigned short maxvalue = file_size;??? //此處不太穩(wěn)妥 當(dāng)數(shù)據(jù)很大時可能會出現(xiàn)異常
??????? m_progress->SetRange(0,maxvalue);
??????? if (file_size>0)
??????? {
??????????? GetDlgItem(IDC_SHOWINFO)->SetWindowText(_T(""));
??????????? GetDlgItem(IDC_SHOWINFO)->SetWindowText(_T("文件下載到本地 d:\test.zip rn"));
??????????? DWORD dwNumberOfBytesRecv=0;
??????????? DWORD dwCountOfBytesRecv=0;
??????????? char Buffer[1024];
??????????? CString filename = _T("d:\test.zip");
??????????? HANDLE hFile;
??????????? hFile = CreateFile(filename,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
??????????? do
??????????? {
??????????????? m_progress->SetPos(dwCountOfBytesRecv);//更新進(jìn)度條
??????????????? dwNumberOfBytesRecv = ::recv(sockClient,Buffer,sizeof(Buffer),0);
??????????????? ::WriteFile(hFile,Buffer,dwNumberOfBytesRecv,&dwNumberOfBytesRecv,NULL);
??????????????? dwCountOfBytesRecv += dwNumberOfBytesRecv;???????????????
??????????? } while (file_size - dwCountOfBytesRecv);
??????????? CloseHandle(hFile);
???????????
??????????? GetDlgItem(IDC_SHOWINFO)->SetWindowText(_T(""));
??????????? GetDlgItem(IDC_SHOWINFO)->SetWindowText(_T("文件接收完畢!rn"));
??????????? AfxMessageBox(_T("文件接收完畢!"));//醒目可以注釋
??????? }else
??????? {
??????????? AfxMessageBox(_T("獲取文件總大小失??!"));
??????? }
??? }else
??? {
??????? AfxMessageBox(_T("連接服務(wù)器失敗、請確認(rèn)IP地址或端口號!"));
??? }
???
???
??? closesocket(sockClient);//關(guān)閉套接字
}
void CRecvFileClientDlg::ConnectServer()
{
??? if (InitSocket())
??? {
??????? DWORD strIp =NULL;
??????? CString strPort = _T("");
??????? ((CIPAddressCtrl*)GetDlgItem(IDC_IP))->GetAddress(strIp);
??????? GetDlgItem(IDC_PORT)->GetWindowText(strPort);
??????? if (strIp==NULL||strPort=="")
??????? {
??????????? AfxMessageBox(_T("Ip地址或Port端口號不能為空!"));
??????? }else
??????? {
??????????? int port = atoi(strPort.GetBuffer(1));
??????????? ConnectRecvFileData(strIp,port);
???????????
??????? }
???????
??? }
???
}