2018-03-11

网络聊天室(MFC编程)

本应用是一款简单的模拟qq聊天应用.主要分为服务器端与客户端

服务器select端:

2018-03-11_第1张图片

客户端client:

2018-03-11_第2张图片

服务器端代码如下:



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 以便退出应用程序,

//  而不是启动应用程序的消息泵。

}


客户端登录界面:


2018-03-11_第3张图片
2018-03-11_第4张图片

在文字框输入文字,并发送出去代码如下:

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;

}

运行结果如下:


2018-03-11_第5张图片

你可能感兴趣的:(2018-03-11)