网络程序设计-WeTalk聊天程序

来源:互联网 发布:淘宝店铺招牌代码 编辑:程序博客网 时间:2024/06/06 20:46

        【摘要】WeTalk是一款基于Windows网络编程技术和MFC实现的聊天应用,该应用采用了Client/Server结构,可以实现多个客户端与服务器建立连接,用户可以通过客户端进行聊天,用户可以发群聊消息,也可以对指定的用户发消息,服务器进行转发和广播消息,还能够对指定的用户进行禁言操作。WeTalk界面美观,简单易用。

        【关键词】Windows网络程序设计,事件选择模型,MFC,Client/Server。

目录

一、项目概述--------------------------------------------------------------------------------------------------------------------------------------

二、总体设计--------------------------------------------------------------------------------------------------------------------------------------

1 软件设计结构图---------------------------------------------------------------------------------------------------------------------------------

2功能要求-------------------------------------------------------------------------------------------------------------------------------------------

三、具体实现--------------------------------------------------------------------------------------------------------------------------------------

1 模型选择------------------------------------------------------------------------------------------------------------------------------------------

2 客户端---------------------------------------------------------------------------------------------------------------------------------------------

3 服务端---------------------------------------------------------------------------------------------------------------------------------------------

4 界面设计------------------------------------------------------------------------------------------------------------------------------------------

四、结果展示--------------------------------------------------------------------------------------------------------------------------------------

1 开发平台与编译运行要求---------------------------------------------------------------------------------------------------------------------

2 实验结果------------------------------------------------------------------------------------------------------------------------------------------

五、总结--------------------------------------------------------------------------------------------------------------------------------------------

六、部分代码--------------------------------------------------------------------------------------------------------------------------------------

1、客户端部分-------------------------------------------------------------------------------------------------------------------------------------

2、服务器部分-------------------------------------------------------------------------------------------------------------------------------------


一、项目概述

        现如今很多聊天社交软件,例如QQ、微信、钉钉等应用的实现必包含了网络程序设计相关的原理,为了能够综合运用在课程中所学到的知识,在实际过程中加深对相关知识的掌握和对网络编程技术的理解,模拟出一套网络聊天的应用程序,命名为WeTalk,该程序有以下功能:按照Client/Server结构分别设计服务端程序和客户端程序;服务端通过图形用户界面实现对服务器的控制,负责维护用户帐户和用户群,并维护用户信息、维持客户端之间的端对端通信和群聊通信、适时维护用户在线信息,并能够发送广播消息,还能够选择用户进行禁言和解除禁言操作;客户端能够连接服务端,能够向所有用户发送消息,并且能够向指定用户发送消息。

二、总体设计

        1 软件设计结构图


图1 软件设计结构图
        2功能要求
        2.1客户端部分


表1  客户端功能要求表
        2.2服务器部分


表2  服务器功能要求表

三、具体实现 

        1 模型选择
        本次实验选择的是事件选择模型,使用WSAEventSelect模型编程的基本步骤:
        (1).创建一个事件句柄表和一个对应的套接字句柄表。
        (2).每创建一个套接字,就创建一个事件对象,把它们的句柄分别放入上面的两个表中,并调用WSAEventSelect将二者关联起来。
        (3).调用WSAWaitForMultipleEvents在所有事件对象上等待(bWaitAll=FALSE),函数返回后,从第一个有信号的事件对象开始检查事件对象表中的事件对象是否有信号(再次调用WSAWaitForMultipleEvents)。
        (4).调用WSAEnumNetworkEvents(),获取套接字上相应的网络事件并处理,然后继续在事件对象上等待。
        2 客户端
        (1)初始化:
        创建套接字:
        创建一个socket.
mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        (2)连接服务器:
        先创建服务器的地址:
        sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr(ip);
clientService.sin_port = htons(port);
        使用connect()函数来连接服务器,连接成功之后创建一个线程来监听是否有收到消息。服务器的地址可以设置,本工程是基于MFC对话框开发的,所以,设置IP时使用IP输入控件,端口号输入用了Edit控件。
        (3)向服务器发消息,发消息的时候选择要发送的对象和消息内容。选择对象使用了ComboBox控件,根据当前选中控件内容来确定对象。使用send()函数发送消息给服务器。
        (4)判断消息类型
服务器发过来的消息有很多种,本实验定义了三种:根据消息缓冲区中的字符来区别,在一条消息中插入前缀,前缀的结束符为“#”,如果“#”前面的是“msg”直接显示到屏幕,如果“#”前面的是“off”或者“max”则显示服务器发送的消息,并且关闭与服务器的连接,如果“#”前面的是“users”则更新用户列表。
        (5)更新用户列表
服务器会实时发送消息,有新的用户状态变更,客户端也会作出相应变化,如果新用户上线,则用户列表增加其信息,如果某用户下线了,则删除该用户的信息。
        3 服务端
        (1)启动服务器
        创建套接字:
        listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        服务器端的地址也是可以在操作界面进行设置,原理跟客户端预先设置服务器地址一样,都是通过IP控件和Edit控件完成数据的输入。服务器端还可以设置最大用户数,这个数据设定以后,当连接用户数等于这个设定数据之后,新的客户端就不能与服务器进行连接了。服务器打开之后,屏幕会提示已启动服务器,并且创建一个线程用于接收客户端发过来的消息。
        (2)更新用户列表
服务器会实时更新用户状态,如果新用户上线,则用户列表增加其信息,如果某用户下线了,则删除该用户的信息。
        (3)广播消息、群聊
采用单播技术,服务端需要向该组内的所有客户端一一转发该消息。
        (4)私聊
私聊的实现方法是在客户端的操作界面选中对象,然后这个对象会被包含在send()函数发送的消息内,这个消息会先发送到服务器,服务器收到之后将这个消息中目的地址读取出来,然后再转发给目的客户端。
        (5)禁言和解除禁言
        该工程服务器和客户端中都有一个CUser类,专门用于保存用户名、用户地址、用户当前的禁言标志。在服务器的操作界面选择某个用户禁言后,该用户的禁言标志banFlag由true变为false,服务器在转发消息时会首先检测发消息的客户端当前是否被禁言,若为禁言状态,那么就不会转发,并且提示当前发消息的客户端已经被系统禁言,服务器界面也会显示该客户端被禁言。解除禁言就是将选中的用户的禁言标志banFlag置为true,这样就会为该客户端转发消息了。
        4 界面设计
        为了界面美观,看起来更像具有设计感的聊天软件,本程序基于MFC对话框开发,精心设计了MFC界面布局,加入了图标按钮,客户端和服务器均有特定的专属标志。

        服务器:客户端:

        广播图标:发送消息:禁言:解除禁言:

四、结果展示

        1 开发平台与编译运行要求


表3  编译运行环境
        2 实验结果: 
        (1)运行程序,打开客户端、服务器(设置IP、端口号、最大用户数)


图2  服务器界面


图3  客户端界面

        (2)启动服务器,服务器连接客户端


图4  客户端与服务器建立连接
        (3)客户端发群聊消息


图5  群聊
        (2)私聊,选择特定用户进行聊天


图6  私聊
        用户1044对用户1032发消息说:我们交朋友吧!因为没有对用户1108说,所有用户1108没有收到该消息。
        (4)服务器端发送广播消息


图7  广播
        服务器向客户端发送广播消息,所有用户都收到消息了。
        (5)服务器选择用户禁言


图8  禁言
        用户1044被禁言,系统会提示他被禁言了,改用户发的消息服务器不予转发。


图9  禁言后正常用户发言
        因为选择了用户1044禁言,所以其他用户可以正常发言。


图10  禁言所有用户
        这次所有用户都被禁言了,因为选中的是所有用户。
        (6)解除禁言


图11  解除禁言用户
        解除禁言可以选择所有用户,可以选择其中某个用户。
        (7)断开连接


图12  解除禁言所有用户

五、总结

        本项目用五个socket模型中的时间选择模型,该模型的核心是WSAEventSelect()函数,它可以为socket注册网络事件,并将指定的事件对象关联到指定的网络事件集合。WSAEnumNetworkEvents()获取套接字上相应的网络事件并处理,然后继续在事件对象上等待。根据判断网络事件类型 FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE来进行下一步操作。还工程实现了按照Client/Server结构分别设计服务端程序和客户端程序;服务端通过图形用户界面实现对服务器的控制,负责维护用户帐户和用户群,并维护用户信息、维持客户端之间的端对端通信和群聊通信、适时维护用户在线信息,并能够发送广播消息,还能够选择用户进行禁言和解除禁言操作;客户端能够连接服务端,能够向所有用户发送消息,并且能够向指定用户发送消息。实验中为了区别消息的类型,专门对消息做了加前缀的处理,通过识别“#”前面的内容来判断下一步的操作。该程序可以开启多个服务端和多个客户端,每个客户端与某个服务器断开连接之后还能与另外的服务器建立连接,可以形成多个用户群。打开多个客户端的过程中,会发现一个不好的现象:多个客户端不是很容易辨别当前客户端的ID,在开发过程中原计划是为每个客户端加一个ID用于辨识,但是程序会出现一些问题,于是删除了这部分功能。
        开发过程中,还加入了禁言功能。可以模拟QQ聊天中的禁言某个用户,也可以全员禁言。
界面中布局合理,加入了图标按钮,而且这些图标都是在图标网站下载的具有指示意义的图标。这块实现起来很容易,因为在之前,做过类似开发。
这个工程还有一些可以改进的地方:功能单一,不能发送语音消息和图片消息;以下情景:三台电脑,一台电脑上是服务器,另外两台是客户端,没有测试能否进行聊天;没有对程序进行测试。以上都是可以改进的地方。

六、部分代码

1、客户端部分

int CWeTalkClientDlg::start(char* ip, int port){//线程句柄;DWORD  sendThreadId;HANDLE hThreadReceive;DWORD  receiveThreadId;// 初始化winsockWSADATA 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);//转为intif (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(""));elseMessageBox(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一个userserver->addUser(sNew, remoteAddr, e);}elsecout << "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--;elsebreak;}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\nserver->print(backmsg);sprintf(backmsg, "msg#你被禁言了!请联系管理员!\r\n");//\r\n\r\nserver->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);}}}

         注:本博客源代码下载地址:http://download.csdn.net/detail/dmxexcalibur/9904529


原创粉丝点击