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