【摘要】WeTalk是一款基于Windows网络编程技术和MFC实现的聊天应用,该应用采用了Client/Server结构,可以实现多个客户端与服务器建立连接,用户可以通过客户端进行聊天,用户可以发群聊消息,也可以对指定的用户发消息,服务器进行转发和广播消息,还能够对指定的用户进行禁言操作。WeTalk界面美观,简单易用。
【关键词】Windows网络程序设计,事件选择模型,MFC,Client/Server。
目录
一、项目概述--------------------------------------------------------------------------------------------------------------------------------------
二、总体设计--------------------------------------------------------------------------------------------------------------------------------------
1 软件设计结构图---------------------------------------------------------------------------------------------------------------------------------
2功能要求-------------------------------------------------------------------------------------------------------------------------------------------
三、具体实现--------------------------------------------------------------------------------------------------------------------------------------
1 模型选择------------------------------------------------------------------------------------------------------------------------------------------
2 客户端---------------------------------------------------------------------------------------------------------------------------------------------
3 服务端---------------------------------------------------------------------------------------------------------------------------------------------
4 界面设计------------------------------------------------------------------------------------------------------------------------------------------
四、结果展示--------------------------------------------------------------------------------------------------------------------------------------
1 开发平台与编译运行要求---------------------------------------------------------------------------------------------------------------------
2 实验结果------------------------------------------------------------------------------------------------------------------------------------------
五、总结--------------------------------------------------------------------------------------------------------------------------------------------
六、部分代码--------------------------------------------------------------------------------------------------------------------------------------
1、客户端部分-------------------------------------------------------------------------------------------------------------------------------------
2、服务器部分-------------------------------------------------------------------------------------------------------------------------------------
图2 服务器界面
图3 客户端界面
(2)启动服务器,服务器连接客户端
图12 解除禁言所有用户
1、客户端部分
int CWeTalkClientDlg::start(char* ip, int port)
{
//线程句柄;
DWORD sendThreadId;
HANDLE hThreadReceive;
DWORD receiveThreadId;
// 初始化winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR)
{
return 1;
}
// 创建一个socket.
mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (mSocket == INVALID_SOCKET) {
WSACleanup();
return 1;
}
sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr(ip);
clientService.sin_port = htons(port);
// 连接服务器.
if (connect(mSocket, (SOCKADDR*)&clientService, sizeof(clientService)) == SOCKET_ERROR)
{
WSACleanup();
return 1;
}
hThreadReceive = CreateThread(NULL, 0, ClientThread, (LPVOID)this, 0, &receiveThreadId);
return 0;
}
DWORD WINAPI ClientThread(LPVOID lpParam)
{
CWeTalkClientDlg* client = (CWeTalkClientDlg*)lpParam;
char recvbuf[DEFAULT_BUFFER];
while (1)
{
//接收来自服务器的消息,成功执行时,返回接收到的字节数,失败返回-1。
int bytesRecv = recv(client->mSocket, recvbuf, DEFAULT_BUFFER, 0);
if (bytesRecv == 0 || bytesRecv == WSAECONNRESET)
{
return 1;
}
if (bytesRecv < 0)
return 1;
recvbuf[bytesRecv] = '\0';
client->actionRecvMsg(recvbuf);
memset(recvbuf, 0, DEFAULT_BUFFER);
}
}
//向服务器发送消息
int CWeTalkClientDlg::sendMsg(char * msg)
{
int bytesSent = send(mSocket, msg, strlen(msg), 0);
return bytesSent;
}
//判断服务器端发过来信息的种类做出相应的反应
void CWeTalkClientDlg::actionRecvMsg(char * msg)
{
char mark[ORD_SIZE], realmsg[DEFAULT_BUFFER];
memset(mark, 0, ORD_SIZE);
memset(realmsg, 0, DEFAULT_BUFFER);
int n = strlen(msg);
int i = 0;
for (; i < n; i++)
{
if (msg[i] == '#')
break;
mark[i] = msg[i];//获取标志
}
i++;
int j = 0;
for (; i < n; i++, j++)
realmsg[j] = msg[i];//获取真实信息
if (strcmp(mark, "msg") == 0)//msg#你好
{
print(realmsg);
}
else if (strcmp(mark, "off") == 0 || strcmp(mark, "max") == 0)//服务器已关闭
{
print(realmsg);
OnBnClickedButtonConnect();
}
else if (strcmp(mark, "users") == 0)
{
updateUserList(realmsg);
}
}
//更新用户列表
void CWeTalkClientDlg::updateUserList(char * msg)
{
removeAllUsers();
char s[NAME_SIZE];
int i = 0;
int j = 0;
int n = strlen(msg);
while (i < n)
{
memset(s, 0, NAME_SIZE);
j = 0;
while (i < n&&msg[i] != '#')
{
s[j] = msg[i];
i++;
j++;
}
CUser* u = new CUser;
u->sock = atoi(s);
i++;
memset(u->showname, 0, SHOW_NAME_SIZE);
j = 0;
while (i < n&&msg[i] != '#')
{
u->showname[j] = msg[i];
i++;
j++;
}
users.push_back(u);
i++;
}
showOnline();
}
//连接服务器
void CWeTalkClientDlg::OnBnClickedButtonConnect()
{
if (mIsStarted)
{
if (shutdown() == 0)
{
SetDlgItemText(IDC_BUTTON_CONNECT, CString("连接服务器"));
mPortInput.EnableWindow(true);
mIpInput.EnableWindow(true);
mSend.EnableWindow(false);
mIsStarted = false;
GetDlgItem(IDC_STATIC_ID)->SetWindowText(L" ");
}
else
{
MessageBox(CString("断开连接失败"));
}
return;
}
char ip[ADDR_SIZE];
DWORD dwIP;
mIpInput.GetAddress(dwIP);
unsigned char* pIP = (unsigned char*)&dwIP;
sprintf(ip, "%u.%u.%u.%u", *(pIP + 3), *(pIP + 2), *(pIP + 1), *pIP);
CString str;
GetDlgItemText(IDC_EDIT_SERVER_PORT, str);//获取端口输入框的内容
int j = _ttoi(str);//转为int
if (j <= 0 || j > 65535)
{
MessageBox(CString("非法端口号"));
return;
}
if (start(ip, j) == 0)
{
mIsStarted = true;
SetDlgItemText(IDC_BUTTON_CONNECT, CString("断开连接"));
mPortInput.EnableWindow(false);//不可编辑
mIpInput.EnableWindow(false);
mSend.EnableWindow(true);
}
else
{
MessageBox(CString("连接失败,请检查地址"));
}
}
//发送消息
void CWeTalkClientDlg::OnBnClickedButtonSend()
{
int to = mUserCombo.GetCurSel();
CString s;
GetDlgItemText(IDC_EDIT_INPUT, s);//获取端口输入框的内容
if (to == 0)
{
s = CString("*#") + s;
}
else
{
char buf[NAME_SIZE];
sprintf(buf, "%d#", users[to - 1]->sock);
s = CString(buf) + s;
}
char m[DEFAULT_BUFFER];
int len = WideCharToMultiByte(CP_ACP, 0, s, s.GetLength(), NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, s, s.GetLength() + 1, m, len + 1, NULL, NULL);
if (sendMsg(m) > 0)
SetDlgItemText(IDC_EDIT_INPUT, CString(""));
else
MessageBox(CString("发送失败"));
}
2、服务器部分
//线程函数,事件选择模型
DWORD WINAPI serverThread(LPVOID lpParam)
{
CWeTalkSeverDlg* server = (CWeTalkSeverDlg*)lpParam;
while (1) {
//只要指定事件对象中的一个或全部处于有信号状态,或者超时间隔到,则返回。如果函数成功,返回值指出造成函数返回的事件对象。
int index = WSAWaitForMultipleEvents(server->mNum, server->events, FALSE, WSA_INFINITE, NULL);
/*如果事件数组中有某一个事件被传信了,函数会返回这个事件的索引值,但是这个索引值需要减去预定义值 WSA_WAIT_EVENT_0
才是这个事件在事件数组中的位置。*/
index = index - WSA_WAIT_EVENT_0;
//对序号为index的客户端进行事件类型判断,做出相应动作。
for (int i = index; i < server->mNum; i++)
{
index = WSAWaitForMultipleEvents(1, &server->events[i], FALSE, 1000, NULL);
WSAResetEvent(server->events[i]);
if (index == WSA_WAIT_FAILED || index == WSA_WAIT_TIMEOUT)
continue;
else
{
WSANETWORKEVENTS event;
WSAEnumNetworkEvents(server->clients[i], server->events[i], &event);
if (event.lNetworkEvents & FD_ACCEPT)
{
if (event.iErrorCode[FD_ACCEPT_BIT] == 0)
{
sockaddr_in remoteAddr;
int iLenRemoteAddr = sizeof(remoteAddr);
SOCKET sNew = accept(server->clients[i], (sockaddr *)&remoteAddr, &iLenRemoteAddr);
WSAEVENT e = WSACreateEvent();
WSAEventSelect(sNew, e, FD_READ | FD_WRITE | FD_CLOSE);
//每收到accept请求就new一个user
server->addUser(sNew, remoteAddr, e);
}
else
cout << "FD_ACCEPT错误:" << event.iErrorCode[FD_ACCEPT_BIT] << endl;
}
else if (event.lNetworkEvents & FD_READ)
{
if (event.iErrorCode[FD_READ_BIT] == 0)//用于接收
{
char buffer[DEFAULT_BUFFER];
char backmsg[DEFAULT_BUFFER];
int ret = recv(server->clients[i], buffer, 1024, NULL);
if (ret > 0)
{
buffer[ret] = '\0';
while (ret >= 0)
{
if (buffer[ret - 1] == '\n' || buffer[ret - 1] == '\r')
ret--;
else
break;
}
buffer[ret] = '\0';
if (ret == 0)
continue;
char to[ADDR_SIZE];
server->getAddr(buffer, to);//to里面是标志,例如msg、exit等等
if (!(server->users[i]->banFlag))//判断是否被禁言
{
sprintf(backmsg, "系统:%s被禁言!\r\n", server->users[i]->showname);//\r\n\r\n
server->print(backmsg);
sprintf(backmsg, "msg#你被禁言了!请联系管理员!\r\n");//\r\n\r\n
server->sendMsg(backmsg, server->clients[i]);
continue;
}
else if (strlen(to) == 0 || strcmp(to, "*") == 0 || strcmp(to, "all") == 0)//对所有用户发消息
{
sprintf(backmsg, "%s对大家说:\r\n%s\r\n", server->users[i]->showname, buffer);
server->print(backmsg);
sprintf(backmsg, "msg#%s对大家说:\r\n%s\r\n", server->users[i]->showname, buffer);
server->sendMsg(backmsg);
}
else if (strcmp(to, "exit") == 0)//客户端退出
{
sprintf(backmsg, "%s离开了聊天室\r\n", server->users[i]->showname);
server->print(backmsg);
sprintf(backmsg, "msg#%s离开了聊天室\r\n", server->users[i]->showname);
server->removeUser(i);
server->sendMsg(backmsg);
server->notifyUsersInfo();
}
else//私聊
{
if (!(server->users[i]->banFlag))
{
sprintf(backmsg, "系统:%s被禁言!\r\n", server->users[i]->showname);
server->print(backmsg);
sprintf(backmsg, "msg#你被禁言了!请联系管理员!\r\n");
server->sendMsg(backmsg, server->clients[i]);
continue;
}
if (server->users[i]->sock == atoi(to))
{
server->sendMsg("msg#系统:与其自言自语,不如找朋友聊聊!\r\n", server->clients[i]);
continue;
}
sprintf(backmsg, "%s对用户%s说:\r\n%s\r\n", server->users[i]->showname, to, buffer);
server->print(backmsg);
sprintf(backmsg, "msg#%s对你说:\r\n%s\r\n", server->users[i]->showname, buffer);
server->sendMsg(backmsg, atoi(to));
sprintf(backmsg, "msg#你对用户%s说:\r\n%s\r\n", to, buffer);
server->sendMsg(backmsg, server->clients[i]);
}
memset(buffer, 0, DEFAULT_BUFFER);
memset(backmsg, 0, DEFAULT_BUFFER);
}
else
{
//???guanbi?
}
}
else if (event.lNetworkEvents & FD_WRITE) //写入
{
if (event.iErrorCode[FD_WRITE_BIT] == 0)
{
cout << "连接成功,开始通信!" << endl;
}
}
else if (event.lNetworkEvents & FD_CLOSE) //关闭socket
{
if (event.iErrorCode[FD_CLOSE_BIT] == 0)
{
server->removeUser(i);
}
else
{
}
}
}
}
}
}
closesocket(server->listenSocket);
WSACleanup();
return 0;
}
//在启动服务器时调用该函数
int CWeTalkSeverDlg::startup(char* ip, int port, int maxline)
{
mPort = port;
mMaxLine = maxline;
WSADATA wsaData;
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
return 1;
}
listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET)
{
return 1;
}
sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(mPort);
saddr.sin_addr.S_un.S_addr = inet_addr(ip);
if (bind(listenSocket, (LPSOCKADDR)&saddr, sizeof(saddr)) == SOCKET_ERROR)
{
return 1;
}
listen(listenSocket, 5);
memset(clients, 0, sizeof(clients));
memset(events, 0, sizeof(events));
WSAEVENT e = WSACreateEvent();
WSAEventSelect(listenSocket, e, FD_ACCEPT | FD_CLOSE);
clients[0] = listenSocket;
events[0] = e;
mNum = 1;
DWORD threadId;
HANDLE hThread = CreateThread(NULL, 0, serverThread, (LPVOID)this, 0, &threadId);
if (hThread == NULL)
{
MessageBox(CString("启动监听线程失败"));
}
CUser* me = new CUser(listenSocket, saddr);
users[0] = me;
return 0;
}
//私聊
void CWeTalkSeverDlg::sendMsg(char* msg, SOCKET s)
{
int left, ret, idx = 0;
for (int i = 1; i < mNum; i++)
{
if (users[i]->sock == s)
{
int ret = send(clients[i], msg, strlen(msg), 0);
if (ret == 0)
return;
else if (ret == SOCKET_ERROR)
{
return;
}
break;
}
}
}
//群聊发消息
void CWeTalkSeverDlg::sendMsg(char* msg)
{
for (int i = 1; i < mNum; i++)
{
int ret = send(clients[i], msg, strlen(msg), 0);
if (ret == 0)
return;
else if (ret == SOCKET_ERROR)
{
return;
}
}
}
//广播消息
void CWeTalkSeverDlg::OnBnClickedButtonSend()
{
// TODO: 在此添加控件通知处理程序代码
if (mNum <= 1)
{
MessageBox(CString("没有在线用户!"));
return;
}
CString str;
GetDlgItemText(IDC_EDIT_MSG, str);
str = CString("系统:") + str + CString("\r\n\r\n");
char m[DEFAULT_BUFFER];
int len = WideCharToMultiByte(CP_ACP, 0, str, str.GetLength(), NULL, 0, NULL, NULL);//映射unicode字符串到多字节字符串
/*果函数运行成功,并且cchMultiByte不为零,返回值是由 lpMultiByteStr指向的缓冲区中写入的字节数;如果函数运行成功,
并且cchMultiByte为零,返回值是接收到待转换字符串的缓冲区所必需的字节数。如果函数运行失败,返回值为零。若想获得更多错误信息,
请调用GetLastError函数。*/
WideCharToMultiByte(CP_ACP, 0, str, str.GetLength() + 1, m, len + 1, NULL, NULL);
print(m);
str = CString("msg#") + str;
len = WideCharToMultiByte(CP_ACP, 0, str, str.GetLength(), NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, str, str.GetLength() + 1, m, len + 1, NULL, NULL);
sendMsg(m);
SetDlgItemText(IDC_EDIT_MSG, CString(""));
}
//设置禁言
void CWeTalkSeverDlg::OnBnClickedButtonBan()
{
// TODO: 在此添加控件通知处理程序代码
int to = mUserCombo.GetCurSel();
if (mNum > 1)
{
if (to == 0)
{
for (int i = 1; i < mNum; i++)
{
users[i]->banFlag = false;
}
char msg[DEFAULT_BUFFER];
char msg2[DEFAULT_BUFFER];
sprintf(msg, "系统:已设置禁止全体发言!\r\n");
print(msg);
sprintf(msg, "msg#系统:全体禁言中!\r\n");
sendMsg(msg);
}
else
{
users[to]->banFlag = false;
char msg[DEFAULT_BUFFER];
sprintf(msg, "系统:已设置禁止用户%s发言!\r\n", users[to]->showname);
print(msg);
}
}
}
//解除禁言
void CWeTalkSeverDlg::OnBnClickedButtonFree()
{
// TODO: 在此添加控件通知处理程序代码
int to = mUserCombo.GetCurSel();
if (mNum > 1)
{
if (to == 0)
{
for (int i = 1; i < mNum; i++)
{
users[i]->banFlag = true;
}
char msg[DEFAULT_BUFFER];
char msg2[DEFAULT_BUFFER];
sprintf(msg, "系统:已设置允许全体用户发言!\r\n");
print(msg);
sprintf(msg, "msg#系统:解除全体禁言!\r\n");
sendMsg(msg);
}
else
{
users[to]->banFlag = true;
char msg[DEFAULT_BUFFER];
sprintf(msg, "系统:已设置允许用户%s发言!\r\n", users[to]->showname);
print(msg);
}
}
}