网络聊天室(MFC编程)
本应用是一款简单的模拟qq聊天应用.主要分为服务器端与客户端
服务器select端:
客户端client:
服务器端代码如下:
select.cpp:
// select.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include "dataHandle.h"#include#pragma comment(lib, "ws2_32")
void SockInit();
int _tmain(int argc, _TCHAR* argv[])
{
SockInit();
SOCKET lisSock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8090);
addr.sin_addr.S_un.S_addr = ADDR_ANY;
bind(lisSock, (sockaddr*)&addr, sizeof(sockaddr));
listen(lisSock, 5);
fd_set fds;
FD_ZERO(&fds);
FD_SET(lisSock, &fds); //将监听套接字加入数组中
timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
while (1)
{
fd_set tmSet = fds; //临时数组
//移除掉没有事件发生的套接字
// SELECT 在编程的过程中,经常会遇到许多阻塞的函数,
//好像read和网络编程时使用的recv, recvfrom函数都是阻塞的函数,
//当函数不能成功执行的时候,程序就会一直阻塞在这里,无法执行下面的代码。
//这是就需要用到非阻塞的编程方式,使用select函数就可以实现非阻塞编程。
select(0, &tmSet, 0, 0, &timeout);
SOCKET s[5];
int j = 0;
//剩下的都是有事件发生的套接字
for (int i = 0; i < tmSet.fd_count; i++)
{
if (tmSet.fd_array[i] == lisSock) //如果是监听监听套接字
{
SOCKET cliSock = accept(lisSock, 0, 0);
printf("新连接.\n");
FD_SET(cliSock, &fds); //将已连接的套接字加入数组中
}
else
{
char recvBuf[1024] = { 0 };
int recvLen = recv(tmSet.fd_array[i], recvBuf, 1024, 0);
if (recvLen > 0)
{
//处理数据
HandleData(recvBuf,recvLen);
printf("%s\n",recvBuf);
}
else //错误发生,或者客户端断开连接
{
printf("客户已断开\n");
FD_CLR(tmSet.fd_array[i], &fds); //从数组中移除已端口的客户端
}
}
}
}
return 0;
}
void SockInit()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
return;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return;
}
}
dataHandle.h 用于服务器接收到不同类型消息处理
#pragma once#include//处理收到的数据
bool HandleData(const char* recvData,int len);
dataHandle.cpp
#include "stdafx.h"
#include "dataHandle.h"
#include "netStruct.h"
//处理收到的数据
bool HandleData(const char* recvData,int len)
{
//printf("%s : %d", recvData, len);
short msgID = *(short*)recvData;
switch (msgID)
{
case TALKALL_MSGID:
{
//...给所有人的消息
const MSG_TALKALL* takAll = (MSG_TALKALL*)recvData;
printf("群聊消息: %s\n", takAll->Content);
}
break;
case TALKONE_MSGID:
{
//给某个人的消息
const MSG_TALKONE* takOne = (MSG_TALKONE*)recvData;
printf("私聊消息: %s : %s\n", takOne->userName, takOne->Content);
//takOne.useName
}
break;
case LOGIN_MSGID:
//登陆消息
break;
//.........
case FILEINFO_MSGID:
{
const MSG_SENDFILEINFO *fileInfo = (MSG_SENDFILEINFO*)recvData;
printf("文件路径:%s ,文件大小:%d\n", fileInfo->fileName, fileInfo->fileSize);
}
break;
case FILE_MSGID:
{
const MSG_SENDFILE *file = (MSG_SENDFILE*)recvData;
printf("文件内容:%s\n", file->fileBuf);
}
break;
default:
break;
}
return true;
}
netStruct.h 传输的信息结构体
#pragma once
#define TALKALL_MSGID 10001
#define TALKONE_MSGID 10002
#define LOGIN_MSGID 10003
#define SENDPIC_MSGID 10004
#define FILEINFO_MSGID 10005
#define FILE_MSGID 10006
//给所有人发的消息
struct MSG_TALKALL
{
short msgID;
char Content[1024];
};
//给某人发的消息
struct MSG_TALKONE
{
short msgID;
char userName[32];
char Content[1024];
};
struct MSG_SENDFILEINFO {
short msgID;
char fileName[256];
int fileSize;
};
struct MSG_SENDFILE {
short msgID;
int bufID;
short bufSize;
char fileBuf[1024];
};
客户端中
client.cpp
BOOL CclientApp::InitInstance(){
// 如果一个运行在 Windows XP 上的应用程序清单指定要
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
//则需要 InitCommonControlsEx()。 否则,将无法创建窗口。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
AfxEnableControlContainer();
// 创建 shell 管理器,以防对话框包含
// 任何 shell 树视图控件或 shell 列表视图控件。
CShellManager *pShellManager = new CShellManager;
// 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
// 标准初始化
// 如果未使用这些功能并希望减小
// 最终可执行文件的大小,则应移除下列
// 不需要的特定初始化例程
// 更改用于存储设置的注册表项
// TODO: 应适当修改该字符串,
// 例如修改为公司或组织名
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
AfxSocketInit();//网络初始化
clientSock sock;
sock.Create();
bool conRet = sock.Connect(_T("127.0.0.1"), 8090);
if (conRet == false) {
int errCode = GetLastError();
CString err;
err.Format(_T("连接服务器失败 : %d,请重试!"), errCode);
MessageBox(0, err, _T("错误"), MB_OK);
return FALSE;
}
Login log;//登录界面
INT_PTR res = log.DoModal();
if (res == IDOK) {
CclientDlg dlg(sock,log.userName);//发送信息界面,将socket传入对话框界面,以发信信息
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: 在此放置处理何时用
// “确定”来关闭对话框的代码
}
else if (nResponse == IDCANCEL)
{
// TODO: 在此放置处理何时用
// “取消”来关闭对话框的代码
}
else if (nResponse == -1)
{
TRACE(traceAppMsg, 0, "警告: 对话框创建失败,应用程序将意外终止。\n");
TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n");
}
// 删除上面创建的 shell 管理器。
if (pShellManager != NULL)
{
delete pShellManager;
}
#ifndef _AFXDLL
ControlBarCleanUp();
#endif
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
// 而不是启动应用程序的消息泵。
}
客户端登录界面:
在文字框输入文字,并发送出去代码如下:
void CclientDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
//CDialogEx::OnOK();
UpdateData();
if (content_send.IsEmpty()) {
MessageBox(_T("请填入消息!"), _T("提示"), MB_OK);
return;
}
if (isSendFile == FALSE) {
/*
char *buff;
buff = (char *)content_send.GetBuffer(0);
m_sock.Send(buff, strlen(buff),0);//用多字节来发送
content_send = _T("");//清空内容
*/
/*
MSG_TALKONE takone;
takone.msgID = TALKONE_MSGID;
strcpy_s(takone.userName, userName);
strcpy_s(takone.Content, content_send.GetBuffer(0));
m_sock.Send(&takone, sizeof(MSG_TALKONE));
*/
MSG_TALKALL takall;
takall.msgID = TALKALL_MSGID;
strcpy_s(takall.Content, content_send.GetBuffer(0));
m_sock.Send(&takall, sizeof(MSG_TALKALL));
content_send = _T("");//清空内容
UpdateData(0);
}
else//发送文件
{
CreateThread(0, 0,sendFileThread, this,0,0);//方法只能为全局方法,不能为类方法
}
}
发送文件代码如下:
DWORD WINAPI sendFileThread(LPVOID lpParameter)//开启多线程来传输文件
{
CclientDlg *dlg = (CclientDlg *)lpParameter;
CFileStatus fileStatus;
ULONGLONG size;
if (CFile::GetStatus(dlg->content_send.GetBuffer(0), fileStatus)) {
size = fileStatus.m_size;
}
MSG_SENDFILEINFO msgInfo;
msgInfo.msgID = FILEINFO_MSGID;
strcpy_s(msgInfo.fileName,dlg->content_send.GetBuffer(0));
msgInfo.fileSize = size;
dlg->m_sock.Send(&msgInfo, sizeof(MSG_SENDFILEINFO));
// 只读方式打开文件
CFile file;
BOOL b = file.Open("C:\\Users\\WJ\\Desktop\\wifi.txt", CFile::modeRead);
while (b)
{
// 读取文件数据
char ReadBuf[1016] = { 0 };
int ret = file.Read(ReadBuf, 100);
printf("%s\n", ReadBuf);
MessageBox(0, ReadBuf, 0, 0);
MSG_SENDFILE msg;
msgInfo.msgID = FILE_MSGID;
strcpy_s(msg.fileBuf, ReadBuf);
dlg->m_sock.Send(&msg, sizeof(MSG_SENDFILE));
if (ret < 100)// 如果到达文件结尾则中止循环
break;
}
// 关闭文件
file.Close();
dlg->isSendFile = FALSE;
return TRUE;
}
运行结果如下: