链接:http://blog.youkuaiyun.com/wanjingwei/archive/2009/07/02/4317036.aspx
UDP的传输功能
最近看了几个聊天室和发送文件的代码,觉得比较有意思。于是我就想自己也试着弄一个这样的程序出来,下面开始介绍我乱搞的这个程序。界面是模仿飞鸽的界面,但功能比那个菜多了,比山寨还山寨。只有传消息和传文件的功能。
首先定义两个结构体:
//这个是用户信息结构体,有主机名和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;
- }
- }
代码很麻烦也很乱,但基本功能是实现了。还剩下用户正常退出或异常退出时处理没有写,这个目前还没想到该怎么弄才好,先留着以后学了别的知识或许就能轻易解决了。
不怕自己笨,就怕自己不努力。