首先是服務器端,大致說下流程:服務器創建線程去處理應答accept(),當接受到客戶端連接請求時,首先獲取要發送的指定的文件數據總大小給客戶端,接着就是循環讀取要發送的文件數據流向客戶端發送文件數據,每次都判斷循環讀取到的數據實際大小,當實際讀取到的數據總大小為0時,表示文件發送結束。下面是服務器server端實現:
聲明部分:
public: afx_msg void OnBnClickedButton1(); public: BOOL InitSocket(); //初始化並創建套接字 static DWORD WINAPI ThreadProc(LPVOID lpParameter); //創建線程去執行服務器accept()
實現部分:
void CSendFileServerDlg::OnBnClickedButton1() { // TODO: 在此添加控件通知處理程序代碼 if (InitSocket()) { GetDlgItem(IDC_EDIT1)->SetWindowText(_T("服務器開啟監聽。。。 \r\n")); //創建線程 HANDLE hThread = CreateThread(NULL,0,ThreadProc,NULL,0,NULL); //關閉該接收線程句柄,釋放引用計數 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; } //創建套接字 //SOCKET m_socket=socket(AF_INET,SOCK_STREAM,0); m_socket=socket(AF_INET,SOCK_STREAM,0); if (m_socket == INVALID_SOCKET) { AfxMessageBox(_T("套接字創建失敗!")); 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);//開啟監聽 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; }
如代碼所述 每次發送單位是unsigned char[1024]大小(程序是char 應該為unsigned char[1024])所以就不存在網絡字節序問題也不用考慮大端小端什么的。
服務器端暫時不支持多客戶端並發訪問,后續可能會加上。。。
-------------------------------------------
下面是客戶端,同樣也大致說下客戶端流程,客戶端增加手動填寫Ip地址和端口號功能(端口號暫為8099)。以及下載傳輸文件數據進度條的顯示,和下面簡單的一些狀態顯示。客觀端由填寫的IP地址進行連接服務器操作,如果客戶端連接服務器成功的話直接就會獲取服務器端發送的要發送的文件數據的總大小,如果獲取文件總大小>0 則會循環往指定的路徑寫數據啦。此處循環寫文件結束標志,我是用每次實際寫的累加如果累計值等於從服務器端獲取的文件總大小的話表示下載文件數據成功,結束循環。大致是這樣一個過程。代碼實現:
客戶端聲明部分:
public: afx_msg void OnBnClickedButton1(); BOOL InitSocket(); void ConnectServer(); void ConnectRecvFileData(DWORD ip,int port); private: CProgressCtrl *m_progress; //進度條
進度條在OnInitDialog()里初始化:
BOOL CRecvFileClientDlg::OnInitDialog() { CDialog::OnInitDialog(); // 將“關於...”菜單項添加到系統菜單中。 // IDM_ABOUTBOX 必須在系統命令范圍內。 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); } } // 設置此對話框的圖標。當應用程序主窗口不是對話框時,框架將自動 // 執行此操作 SetIcon(m_hIcon, TRUE); // 設置大圖標 SetIcon(m_hIcon, FALSE); // 設置小圖標 // TODO: 在此添加額外的初始化代碼 m_progress = (CProgressCtrl*)GetDlgItem(IDC_PROGRESS1); m_progress->SetPos(0); return TRUE; // 除非將焦點設置到控件,否則返回 TRUE }
客戶端具體實現部分:
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("連接服務器成功!\r\n")); recv(sockClient,(char*)&file_size,sizeof(unsigned long long)+1,NULL); unsigned short maxvalue = file_size; //此處不太穩妥 當數據很大時可能會出現異常 m_progress->SetRange(0,maxvalue); if (file_size>0) { GetDlgItem(IDC_SHOWINFO)->SetWindowText(_T("")); GetDlgItem(IDC_SHOWINFO)->SetWindowText(_T("文件下載到本地 d:\\test.zip \r\n")); 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);//更新進度條 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("文件接收完畢!\r\n")); AfxMessageBox(_T("文件接收完畢!"));//醒目可以注釋 }else { AfxMessageBox(_T("獲取文件總大小失敗!")); } }else { AfxMessageBox(_T("連接服務器失敗、請確認IP地址或端口號!")); } closesocket(sockClient);//關閉套接字 } 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); } } }
小結:最近用到些socket 網絡編程的東西,所以就試着寫了個基本的C/S端數據的網絡傳輸,有哪些不足的地方,請看到的前輩們不吝賜教!!!