天天看点

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;

}

}

代码很麻烦也很乱,但基本功能是实现了。还剩下用户正常退出或异常退出时处理没有写,这个目前还没想到该怎么弄才好,先留着以后学了别的知识或许就能轻易解决了。

不怕自己笨,就怕自己不努力。

继续阅读