最近看了几个聊天室和发送文件的代码,觉得比较有意思。于是我就想自己也试着弄一个这样的程序出来,下面开始介绍我乱搞的这个程序。界面是模仿飞鸽的界面,但功能比那个菜多了,比山寨还山寨。只有传消息和传文件的功能。
首先定义两个结构体:
//这个是用户信息结构体,有主机名和IP地址。呆会要添加到列表狂里
typedef struct userInfo{
char myhost[256];
char ip[256];
}USERINFO,*PUSERINFO;
//这个是包含窗口句柄和套接字句柄的结构体。在向进程传递指针时用到这个
struct RECVPARAM{
HWND hWnd;
SOCKET sock;
};
BOOL CMsgDlg::OnInitDialog() { WSADATA wsadata; WSAStartup(MAKEWORD(2,2),&wsadata); /*下面部分是控件的初始化工作*/ //初始化列表控件 m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_HEADERDRAGDROP); m_list.InsertColumn(0,"IP地址",LVCFMT_LEFT,100); m_list.InsertColumn(1,"群组",LVCFMT_LEFT,100); m_list.InsertColumn(2,"主机名",LVCFMT_LEFT,100); //用户登陆后执行的操作 //获得主机名 PUSERINFO puser=(PUSERINFO)::GlobalAlloc(GPTR,sizeof(userInfo));//申请一个指针对象 gethostname(puser->myhost,256); //获得IP地址 hostent*phost=gethostbyname(puser->myhost); char *p=phost->h_addr_list[0]; in_addr sin; memcpy(&sin.S_un.S_addr,p,phost->h_length); strcpy(puser->ip,inet_ntoa(sin)); AddMyInfo(puser);//先添加自己的信息 PostAndRecvInfo(puser);//再发送给所有其他用户自己的信息 GlobalFree(p);//记得要释放,不然结束程序时会提示出错 //显示在线人数 CString struser; struser.Format("在线%d人",user); SetDlgItemText(IDC_EDIT1,struser); /*发送文件部分的操作,专门建立一个套接字在5000端口上收发文件*/ m_socket=socket(AF_INET,SOCK_DGRAM,0); if(INVALID_SOCKET==m_socket) { MessageBox("套接字创建失败!"); return FALSE; } SOCKADDR_IN addrSock; addrSock.sin_family=AF_INET; addrSock.sin_port=htons(5000); addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //绑定套接字 BOOL resue; setsockopt(m_socket,SOL_SOCKET,SO_REUSEADDR,(char*)&resue,sizeof(BOOL)); int retval; retval=bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR)); if(SOCKET_ERROR==retval) { closesocket(m_socket); MessageBox("绑定失败了!"); return FALSE; } //套接字设置成阻塞的 u_long ul=0; ioctlsocket(m_socket,FIONBIO,(u_long*)&ul); //产生一个用于接收数据的线程 struct RECVPARAM *pRecvParam=new RECVPARAM; pRecvParam->sock=m_socket; pRecvParam->hWnd=m_hWnd; HANDLE hThread=CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParam, 0, NULL); CloseHandle(hThread); return TRUE; // return TRUE unless you set the focus to a control } void CMsgDlg::AddMyInfo(userInfo * p) { //现在自己的界面上添加自己的信息 user = m_list.InsertItem(0, "1");//插入一行 m_list.SetItemText(user,0,p->ip); m_list.SetItemText(user,2,p->myhost);//对应行的某列加数据 user++;//在线人数 } void CMsgDlg::PostAndRecvInfo(userInfo *p) { //先发送广播给所有用户 SOCKET broadsocket=socket(AF_INET,SOCK_DGRAM,0); BOOL bBroad=TRUE; setsockopt(broadsocket,SOL_SOCKET,SO_BROADCAST,(char*)&bBroad,sizeof(BOOL)); //设置广播地址 SOCKADDR_IN m_cast; m_cast.sin_addr.S_un.S_addr=INADDR_BROADCAST; m_cast.sin_family=AF_INET; m_cast.sin_port=htons(6000); //发送广播 char *sendbuf=new char[256]; CString use; int lenhost=strlen(p->myhost); use+=char(lenhost);//第一位为主机名长度 use+=p->myhost;//后面几位是主机名 int lenip=strlen(p->ip); use+=char(lenip);//下一位是ip地址的长度 use+=p->ip;//最后是ip地址 int len=use.GetLength(); sendbuf=use.GetBuffer(len); sendbuf[len]='/0'; if(sendto(broadsocket,sendbuf,strlen(sendbuf),0,(SOCKADDR*)&m_cast,sizeof(m_cast))==SOCKET_ERROR) { CString str; str.Format("%d",WSAGetLastError()); MessageBox(str); MessageBox("发送广播数据失败!"); return; } //广播发送完,开始准备读取 s=socket(AF_INET,SOCK_DGRAM,0); SOCKADDR_IN m_addr; m_addr.sin_addr.S_un.S_addr=INADDR_ANY; m_addr.sin_family=AF_INET; m_addr.sin_port=htons(6000); BOOL reuse=TRUE; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char*)&reuse,sizeof(reuse)); if(bind(s,(SOCKADDR*)&m_addr,sizeof(SOCKADDR))==SOCKET_ERROR) { //CString str; //str.Format("%d",WSAGetLastError()); //MessageBox(str); MessageBox("绑定失败!"); return ; } if(WSAAsyncSelect(s,m_hWnd,WM_SOCK,FD_READ)==SOCKET_ERROR) { MessageBox("注册网络读取事件失败!"); return ; } } void CMsgDlg::OnSock(WPARAM wparam,LPARAM lparam) { switch(LOWORD(lparam)) { case FD_READ: static int flag=0; WSABUF wsabuf; wsabuf.buf=new char[200]; wsabuf.len=200; DWORD dwRead; DWORD dwFlag=0; SOCKADDR_IN addrFrom; int len=sizeof(SOCKADDR); CString strTemp; CString str; if(SOCKET_ERROR==WSARecvFrom(s,&wsabuf,1,&dwRead,&dwFlag, (SOCKADDR*)&addrFrom,&len,NULL,NULL)) { //MessageBox("接收数据失败!"); return ; } //发送过来的是广播用户信息 if(wsabuf.buf[0]>0&&wsabuf.buf[0]<65) { flag++; if(flag%2==0) { char *host; int hostlen=(int)wsabuf.buf[0]; host=(char*)malloc(hostlen+1); for(int i=0;i<hostlen;i++) host[i]=wsabuf.buf[i+1]; host[hostlen]='/0'; char *ip; int iplen=(int)wsabuf.buf[hostlen+1]; ip=(char*)malloc(iplen+1); for(i=0;i<iplen;i++) ip[i]=wsabuf.buf[hostlen+2+i]; ip[iplen]='/0'; int row=m_list.InsertItem(user,"12"); m_list.SetItemText(row,0,ip); m_list.SetItemText(row,2,host); user++; //SetTimer(1,1000,NULL); } } //发送来的是聊天消息 if(wsabuf.buf[0]=='M') { CString strrecv; int len=(int)wsabuf.buf[1]; char *recv=new char[len+1]; for(int i=0;i<len;i++) recv[i]=wsabuf.buf[i+2]; recv[len]='/0'; strrecv.Format("用户%s发来消息:%s",inet_ntoa(addrFrom.sin_addr),recv); SetDlgItemText(IDC_SEND,strrecv); } break; } } //定时器,每隔一段时间刷新一下在线人数 void CMsgDlg::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default CString struser; struser.Format("在线%d人",user); SetDlgItemText(IDC_EDIT1,struser); CDialog::OnTimer(nIDEvent); } //下面是发送消息的函数 void CMsgDlg::OnBtnsend() { // TODO: Add your control notification handler code here //获得选中行的某一列内容(这里为第一列) CString strsend; int nItem = m_list.GetItemCount(); for (int i=0; i<nItem; i++) { if (m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED) // 该行选中的话 { sItem1 = m_list.GetItemText(i, 0);// i为该行索引, 0表示第1列 break; } } GetDlgItemText(IDC_SEND,strsend); char *sendbuf=new char[256]; sendbuf[0]='M'; int len=strsend.GetLength(); sendbuf[1]=char(len); strcpy(&sendbuf[2],strsend.GetBuffer(len)); SOCKADDR_IN m_addrto; m_addrto.sin_addr.S_un.S_addr=inet_addr(sItem1); m_addrto.sin_family=AF_INET; m_addrto.sin_port=htons(6000); //套接字s在前面已经创建并绑定过了,这里直接发送就可以了 int ret=sendto(s,sendbuf,strlen(sendbuf),0,(SOCKADDR*)&m_addrto,sizeof(SOCKADDR)); if(ret==SOCKET_ERROR) { MessageBox("发送数据失败!"); return; } SetDlgItemText(IDC_SEND,""); } /*下面开始文件传输部分的操作*/ //这个是向导里添加的一个右键的命令相应 void CMsgDlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult) { // TODO: Add your control notification handler code here NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; //为-1时就表示右击了列表框上的某一行 if(pNMListView->iItem != -1) { DWORD dwPos = GetMessagePos(); CPoint point( LOWORD(dwPos), HIWORD(dwPos) ); CMenu menu; VERIFY( menu.LoadMenu( IDR_MENU1 ) ); CMenu* popup = menu.GetSubMenu(0); ASSERT( popup != NULL ); popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this ); } *pResult = 0; } //发送文件的函数,这里发送文件和接收文件用的都是网上现成的代码,不是自己的成果 void CMsgDlg::OnSendfile() { // TODO: Add your command handler code here CFileDialog dlg(TRUE); if(IDOK==dlg.DoModal()) { m_filePath=dlg.GetPathName(); if (m_posting) //bool isPosting 表示程序是否正在发送文件 { MessageBox("数据发送中,请稍候再试。"); return; } WIN32_FIND_DATA FindFileData; if (INVALID_HANDLE_VALUE == FindFirstFile(m_filePath, &FindFileData)) { MessageBox("文件路径错误或文件不存在!/n请重新指定文件路径。"); return; } SOCKADDR_IN addrTo; addrTo.sin_family=AF_INET; addrTo.sin_port=htons(5000); addrTo.sin_addr.S_un.S_addr=inet_addr(sItem1); //构建文件信息数据块 char sendBuf[274]; int i; //消息头 sendBuf[0] = 'H'; //文件名 for (i = 1; i <= 256 && FindFileData.cFileName[i-1] != '/0'; i++) sendBuf[i] = FindFileData.cFileName[i-1]; sendBuf[i] = '/0'; //文件大小 _itoa(FindFileData.nFileSizeLow, &sendBuf[257], 10); sendBuf[273] = '/0'; //发送数据块 sendto(s, sendBuf, 274, 0, (SOCKADDR*)&addrTo, sizeof(SOCKADDR)); //打开文件,等待读取 if (!(m_file = fopen(m_filePath, "rb"))) { MessageBox("读取文件失败!"); } m_nSend=0;//文件块数 m_nFileSize_s = FindFileData.nFileSizeLow; //文件大小 // m_progress.SetRange(0, m_nFileSize_s/256+1);//设置发送进度条 m_posting = true; //标明发送正进行 MessageBox("发送文件成功"); } } DWORD WINAPI CMsgDlg::RecvProc(LPVOID lpParameter) { SOCKET sock=((RECVPARAM*)lpParameter)->sock; HWND hWnd=((RECVPARAM*)lpParameter)->hWnd; delete lpParameter; SOCKADDR_IN addrFrom; addrFrom.sin_port=htons(5000); addrFrom.sin_family=AF_INET; addrFrom.sin_addr.S_un.S_addr=INADDR_ANY; int len=sizeof(SOCKADDR); char recvBuf[274]; //256+17字节的接受缓冲数组 char fileName[256]; //256字节的文件名存储区 int retval, i; FILE* file = NULL; while(1) { //接收UDP数据 retval=recvfrom(sock,recvBuf,274,0, (SOCKADDR*)&addrFrom,&len); if(SOCKET_ERROR==retval) break; //收到消息头为'R',即对方同意让你继续发送数据 if (recvBuf[0] == 'R') { char wParam = 'R'; ::PostMessage(hWnd, WM_READY_TO_RECEIVE, (WPARAM)&wParam, 0); } //收到消息头为'D',即对方拒绝让你继续发送数据 else if (recvBuf[0] == 'D') { char wParam = 'D'; ::PostMessage(hWnd, WM_READY_TO_RECEIVE, (WPARAM)&wParam, 0); } //收到消息头为'H',即对方申请给你发送信息,并送来文件的信息 else if (recvBuf[0] == 'H') { //从收到的数据中提取文件名信息 for (i = 1; i <= 256 && recvBuf[i] != '/0'; i++) fileName[i-1] = recvBuf[i]; //recvBuf[1]到recvBuf[256]为文件名 fileName[i-1] = '/0'; //从收到的数据中提取文件大小信息 CString recvMsg; nFileSize = atoi(&recvBuf[257]); //recvBuf[257]开始是文件大小信息,把字符串变整数 recvMsg.Format("收到来自于(%s)的文件:%s/n文件大小:%i字节/n是否接收?", inet_ntoa(addrFrom.sin_addr), fileName, nFileSize); //用消息框提示用户有人要发送文件 if (IDOK == AfxMessageBox(recvMsg, MB_OKCANCEL)) { //若用户同意接收,提供一个文件保存对话框用于设定保存的路径 CFileDialog saveDlg(false, NULL, fileName); if (IDOK == saveDlg.DoModal()) { //创建一个文件用于复制接收的文件数据 if (!(file = fopen(saveDlg.GetPathName(), "wb"))) { AfxMessageBox("创建本地文件失败!"); continue; } char wParam = 'H'; ::PostMessage(hWnd, WM_READY_TO_RECEIVE, (WPARAM)&wParam, (LPARAM)&addrFrom); } else { char wParam = 'C'; ::PostMessage(hWnd, WM_READY_TO_RECEIVE, (WPARAM)&wParam, (LPARAM)&addrFrom); } } else //用户拒绝接收 { char wParam = 'C'; ::PostMessage(hWnd, WM_READY_TO_RECEIVE, (WPARAM)&wParam, (LPARAM)&addrFrom); } } //收到的消息头为'F',即对方发来的是文件数据 else if (recvBuf[0] == 'F') { //将文件数据写入本地文件中 fwrite(&recvBuf[18], 1, 256, file); //recvBuf[18]开始是文件的数据块了 char wParam = 'F'; ::PostMessage(hWnd, WM_READY_TO_RECEIVE, (WPARAM)&wParam, (LPARAM)&addrFrom); } //收到的消息头为'E',即对方发来最后一个数据块 else if (recvBuf[0] == 'E') { //获取数据块的大小 int bufSize = atoi(&recvBuf[1]); //将数据块写入本地文件,并关闭文件 fwrite(&recvBuf[0x12], 1, bufSize, file); fclose(file); char wParam = 'E'; ::PostMessage(hWnd, WM_READY_TO_RECEIVE, (WPARAM)&wParam, (LPARAM)&addrFrom); } else AfxMessageBox("传送数据过程中出现错误!"); } return 0; } void CMsgDlg::OnReadyToRecv(WPARAM wParam,LPARAM lParam) { char sendBuf[0x112]; SOCKADDR_IN addrTo; addrTo.sin_family=AF_INET; addrTo.sin_port=htons(5000); addrTo.sin_addr.S_un.S_addr=inet_addr(sItem1); int nRead; CString str; switch (*(char*)wParam) { //对方拒绝接收文件,关闭已打开的文件 case 'D': MessageBox("对方拒绝接受你发送的文件!"); fclose(m_file); m_posting = false; break; //对方同意接收文件 case 'R': nRead = fread(&sendBuf[18], 1, 256, m_file); //读取的文件小于256字节,则读到文件尾 if (nRead < 0x100) { sendBuf[0] = 'E'; _itoa(nRead, &sendBuf[1], 10); sendto(m_socket, sendBuf, nRead+0x12, 0, (SOCKADDR*)&addrTo, sizeof(SOCKADDR)); fclose(m_file); m_posting = false; MessageBox("发送完毕!"); } //读到文件等于256字节,则文件还未读完 else { sendBuf[0] = 'F'; sendto(m_socket, sendBuf, 0x112, 0, (SOCKADDR*)&addrTo, sizeof(SOCKADDR)); m_nSend++; m_send.Format("发送进度:(%.1f%%)", (float)m_nSend/(m_nFileSize_s/0x100+1)*100); } break; //同意接收对方文件 case 'H': MessageBox("同意接收了"); m_nRecv = 0; case 'F': sendto(m_socket, "R", 2, 0, (SOCKADDR*)&addrTo, sizeof(SOCKADDR)); m_nRecv++; //str.Format("%d",m_nRecv); //MessageBox(str); m_recv.Format("接收进度:(%.1f%%)", (float)m_nRecv/(nFileSize/0x100+1)*100); break; //接受完毕,提示用户 case 'E': MessageBox("接收完毕!"); break; //拒绝接收,通知对方 case 'C': sendto(m_socket, "D", 2, 0, (SOCKADDR*)&addrTo, sizeof(SOCKADDR)); break; } }
代码很麻烦也很乱,但基本功能是实现了。还剩下用户正常退出或异常退出时处理没有写,这个目前还没想到该怎么弄才好,先留着以后学了别的知识或许就能轻易解决了。
不怕自己笨,就怕自己不努力。