首先是服务器端,大致说下流程:服务器创建线程去处理应答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端数据的网络传输,有哪些不足的地方,请看到的前辈们不吝赐教!!!