客户端只发,服务端只收。
服务端代码进行了优化:
1.fd_set进行了备份,如果客户端没有发生接入,离开。则,每次select的时候,使用备份的fd_set。
2.使用std::map<SOCKET, ClientSocket*> _clients;替代vector加快了客户端的查找操作。
客户端:
DataHeader.hpp
#ifndef _MessageHeader_hpp_ #define _MessageHeader_hpp_ enum CMD { CMD_LOGIN, CMD_LOGIN_RET, CMD_LOGOUT, CMD_LOGOUT_RET, CMD_NEW_USER_JOIN, CMD_ERROR }; struct DataHeader { DataHeader() { dataLength = sizeof(DataHeader); cmd = CMD_ERROR; } short dataLength; short cmd; }; struct Login :public DataHeader { Login() { dataLength = sizeof(Login); cmd = CMD_LOGIN; } char userName[32]; char passWord[32]; //char data[932]; char data[32]; }; struct LoginResult :public DataHeader { LoginResult() { dataLength = sizeof(LoginResult); cmd = CMD_LOGIN_RET; ret = 1; } int ret; //char data[992]; char data[92]; }; struct LogOut :public DataHeader { LogOut() { dataLength = sizeof(LogOut); cmd = CMD_LOGOUT; } char userName[32]; }; struct LogOutResult :public DataHeader { LogOutResult() { dataLength = sizeof(LogOutResult); cmd = CMD_LOGOUT_RET; ret = 1; } int ret; }; struct NewUserJoin :public DataHeader { NewUserJoin() { dataLength = sizeof(NewUserJoin); cmd = CMD_NEW_USER_JOIN; sock = -1; } int sock; }; #endif
EasyTcpClient.hpp
#ifndef _EasyTcpClient_hpp_ #define _EasyTcpClient_hpp_ #ifdef _WIN32 #include <windows.h> #include <WinSock2.h> #else #include<unistd.h> #include<arpa/inet.h> #include<string.h> #define SOCKET int #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #endif #include <stdio.h> #include "DataHeader.hpp" #define RECV_BUFF_SIZE 10240 class EasyTcpClient { public: EasyTcpClient() { _sock = INVALID_SOCKET; _isConnect = false; } //虚析构函数 virtual ~EasyTcpClient() { Close(); } //初始化socket void InitSocket() { #ifdef _WIN32 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); #endif // _WIN32 if (_sock != INVALID_SOCKET) { Close(); } _sock = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == _sock) { printf("错误,建立Socket失败...\n"); } else { printf("建立Socket成功...\n"); } } int Connect(const char* ip, unsigned short port) { if (INVALID_SOCKET == _sock) { InitSocket(); } sockaddr_in _sin = {}; _sin.sin_family = AF_INET; _sin.sin_port = htons(port); #ifdef _WIN32 _sin.sin_addr.S_un.S_addr = inet_addr(ip); #else _sin.sin_addr.s_addr = inet_addr(ip); #endif int ret = connect(_sock, (const sockaddr*)&_sin, sizeof(sockaddr_in)); if (SOCKET_ERROR == ret) { printf("错误,连接服务端失败...\n"); } else { _isConnect = true; printf("连接服务端成功...\n"); } return ret; } bool isRun() { return (_sock != INVALID_SOCKET) && _isConnect; } bool onRun() { if (isRun()) { fd_set fdRead; FD_ZERO(&fdRead); FD_SET(_sock, &fdRead); timeval t = { 0, 0}; int ret = select(_sock + 1, &fdRead, 0, 0, &t); if (ret < 0) { Close(); printf("select任务结束1。\n"); return false; } if (FD_ISSET(_sock, &fdRead)) { FD_CLR(_sock, &fdRead);//从集合移除了_sock if (-1 == RecvData()) { Close(); printf("select任务结束2。\n"); return false; } } } return false; } //接收数据 处理粘包 拆分包 int RecvData() { int nLen = recv(_sock, _szRecv, RECV_BUFF_SIZE, 0); //printf("nLen=%d\n", nLen); if (nLen <= 0) { printf("服务端%d已退出。\n", _sock); return -1; } //将收到的数据拷贝到消息缓冲区 memcpy(_szMsgBuf + _lastPos, _szRecv, nLen); //消息缓冲区的数据尾部位置后移 _lastPos += nLen; //判断消息缓冲区的数据长度大于消息头DataHeader长度 while (_lastPos >= sizeof(DataHeader)) { //这时就可以知道当前消息的长度 DataHeader* header = (DataHeader*)_szMsgBuf; //判断是否可以获取一个完整消息 if (_lastPos >= header->dataLength) { //剩余的未处理消息缓冲区数据的长度 int nSize = _lastPos - header->dataLength; //处理网络消息 onNetMsg(header); //将消息缓冲区剩余未处理数据前移 memcpy(_szMsgBuf, _szMsgBuf + header->dataLength, nSize); //将消息缓冲区的数据尾部位置前移 _lastPos = nSize; } else { //消息缓冲区剩余数据不够一条完整消息 break; } } return 0; } void onNetMsg(DataHeader* pData) { switch (pData->cmd) { case CMD_LOGIN_RET: { LoginResult *pLogin = (LoginResult*)pData; //printf("login result:%d\n", pLogin->ret); } break; case CMD_LOGOUT_RET: { LogOutResult *pLogOut = (LogOutResult*)pData; printf("log out result:%d\n", pLogOut->ret); } break; case CMD_NEW_USER_JOIN: { NewUserJoin *pMsg = (NewUserJoin*)pData; printf("new user:%d join.\n", pMsg->sock); } break; case CMD_ERROR: { printf("收到服务端消息:CMD_ERROR,数据长度:%d\n", pData->dataLength); } break; default: { printf("收到未定义消息,数据长度:%d\n", pData->dataLength); } } } //发送数据 int SendData(DataHeader pHeader[]) { int ret = SOCKET_ERROR; if (isRun() && pHeader) { ret = send(_sock, (const char*)pHeader, pHeader->dataLength, 0); if (SOCKET_ERROR == ret) { Close(); } } return ret; } //发送数据 int SendData(DataHeader* header, int nLen) { int ret = SOCKET_ERROR; if (isRun() && header) { ret = send(_sock, (const char*)header, nLen, 0); if (SOCKET_ERROR == ret) { Close(); } } return ret; } void Close() { if (_sock != INVALID_SOCKET) { #ifdef _WIN32 closesocket(_sock); //清除Windows socket环境 WSACleanup(); #else close(_sock); #endif _sock = INVALID_SOCKET; } _isConnect = false; } private: SOCKET _sock; //接收缓冲区 char _szRecv[RECV_BUFF_SIZE] = {}; //第二缓冲区 消息缓冲区 char _szMsgBuf[RECV_BUFF_SIZE * 5] = {}; //消息缓冲区的数据尾部位置 int _lastPos = 0; bool _isConnect; }; #endif
main.cpp
#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS //也可以放到工程属性,预处理中 #define _CRT_SECURE_NO_WARNINGS #pragma comment(lib, "ws2_32.lib") #endif #include <stdio.h> #include <thread> #include "EasyTcpClient.hpp" //开启4个线程,每个线程250个客户端 const int cCount = 10000; const int tCount = 4; EasyTcpClient* client[cCount]; bool g_bRun = true; void cmdThread() { while (true) { //3输入请求命令 char cmdBuf[128] = {}; scanf("%s", cmdBuf); getchar(); //4处理请求 if (0 == strcmp(cmdBuf, "exit")) { printf("退出cmdThread线程\n"); g_bRun = false; return; } else { printf("wrong cmd, please input cmd again.\n"); } } } void sendThread(int id) { int c = cCount / tCount; int begin = (id - 1)*c; int end = id*c; for (int n = begin; n<end; n++) { if (!g_bRun) { return; } client[n] = new EasyTcpClient(); } for (int n = begin; n<end; n++) { if (!g_bRun) { return; } client[n]->Connect("127.0.0.1", 4567); printf("connect=%d\n", n); } std::chrono::milliseconds t(3000); std::this_thread::sleep_for(t); //Login login = {}; //strcpy(login.userName, "jj"); //strcpy(login.passWord, "ww"); Login login[10]; for (int n = 0; n < 10; n++) { strcpy(login[n].userName, "lyd"); strcpy(login[n].passWord, "lydmm"); } const int nLen = sizeof(login); while (g_bRun) { for (int n = begin; n < end; n++) { client[n]->SendData(login, nLen); //client[n]->SendData(&login); //client[n]->onRun(); } } for (int n = begin; n < end; n++) { client[n]->Close(); } for (int n = begin; n < end; n++) { delete client[n]; client[n] = nullptr; } } int main() { std::thread t(cmdThread); t.detach(); std::thread client_th[tCount]; for (int n=0;n<tCount;n++) { client_th[n] = std::thread(sendThread, n+1); } for (int n = 0; n<tCount; n++) { client_th[n].join(); } printf("已退出"); getchar(); return 0; }
服务端:
DataHeader.hpp 同客户端
CELLTimestamp.hpp
#ifndef _CELLTimestamp_hpp_ #define _CELLTimestamp_hpp_ #include <chrono> using namespace std::chrono; class CELLTimestamp { public: CELLTimestamp() { update(); } ~CELLTimestamp() { } void update() { _begin = high_resolution_clock::now(); } //获取当前秒 double getElapsedTimeInSec() { return getElapsedTimeInMicroSec()*0.000001; } //获取当前毫秒 double getElapsedTimeInMilliSec() { return getElapsedTimeInMicroSec()*0.001; } //获取当前微妙 long long getElapsedTimeInMicroSec() { return duration_cast<microseconds>(high_resolution_clock::now() - _begin).count(); } private: time_point<high_resolution_clock> _begin; }; #endif // !
EasyTcpServer.hpp
#ifndef _EasyTcpServer_hpp_ #define _EasyTcpServer_hpp_ #ifdef _WIN32 #define FD_SETSIZE 2506 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS //也可以放到工程属性,预处理中 #define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") #else #include<unistd.h> #include<arpa/inet.h> #include<string.h> #define SOCKET int #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #endif #include <stdio.h> #include <vector> #include <map> #include <thread> #include <mutex> #include <atomic> #include "DataHeader.hpp" #include "CELLTimestamp.hpp" #define _CELLSERVERS_THREAD_COUNT 4 #define RECV_BUFF_SIZE 10240 //客户端数据类型 class ClientSocket { public: ClientSocket(SOCKET sockfd = INVALID_SOCKET) { _sockfd = sockfd; //memset(_szMsgBuf, 0, sizeof(_szMsgBuf)); } SOCKET sockfd() { return _sockfd; } char* msgBuf() { return _szMsgBuf; } int getlastPos() { return _lastPos; } void setlastPos(int pos) { _lastPos = pos; } //指定socket发送数据 int SendData(DataHeader* header) { if (header) { return send(_sockfd, (const char*)header, header->dataLength, 0); } return SOCKET_ERROR; } private: SOCKET _sockfd = INVALID_SOCKET; char _szMsgBuf[RECV_BUFF_SIZE * 5] = {}; int _lastPos = 0; }; //网络事件接口 class INetEvent { public: //客户端加入事件 virtual void OnNetJoin(ClientSocket* pClient) = 0; //客户端离开事件 virtual void OnNetLeave(ClientSocket* pClient) = 0; //客户端消息事件 virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header) = 0; }; class CellServer { public: CellServer(SOCKET sock = INVALID_SOCKET) { _sock = sock; _pNetEvent = nullptr; } ~CellServer() { Close(); //_sock = INVALID_SOCKET; } //关闭socket void Close() { #ifdef _WIN32 // 关闭套接字 for (auto iter : _clients) { closesocket(iter.second->sockfd()); delete iter.second; } #else for (auto iter : _clients) { close(iter.second->sockfd()); delete iter.second; } #endif _clients.clear(); } //是否工作中 bool isRun() { return _sock != INVALID_SOCKET; } void Start() { //std::thread t(&CellServer::onRun, this); _pThread = std::thread(std::mem_fn(&CellServer::onRun), this); } //接收数据 处理粘包 拆分包 int RecvData(ClientSocket* pClient) { // 接收客户端数据 int nLen = (int)recv(pClient->sockfd(), _szRecv, RECV_BUFF_SIZE, 0); //printf("nLen=%d\n", nLen); if (nLen <= 0) { //printf("客户端%d已退出。\n", pClient->sockfd()); return -1; } memcpy(pClient->msgBuf() + pClient->getlastPos(), _szRecv, nLen); //消息缓冲区的数据尾部位置后移 pClient->setlastPos(pClient->getlastPos() + nLen); //判断消息缓冲区的数据长度大于消息头DataHeader长度 while (pClient->getlastPos() >= sizeof(DataHeader)) { //这时就可以知道当前消息的长度 DataHeader* header = (DataHeader*)pClient->msgBuf(); //判断是否可以获取一个完整消息 if (pClient->getlastPos() >= header->dataLength) { //剩余的未处理消息缓冲区数据的长度 int nSize = pClient->getlastPos() - header->dataLength; //处理网络消息 OnNetMsg(pClient, header); //将消息缓冲区剩余未处理数据前移 memcpy(pClient->msgBuf(), pClient->msgBuf() + header->dataLength, nSize); //将消息缓冲区的数据尾部位置前移 pClient->setlastPos(nSize); } else { //消息缓冲区剩余数据不够一条完整消息 break; } } return 0; } //响应网络消息 //响应网络消息 virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header) { _pNetEvent->OnNetMsg(pClient, header); } FD_SET _fdRead_bak;//备份 bool _clients_change; SOCKET _maxSock; bool onRun() { _clients_change = true; while ( isRun() ) { if (_clientsBuff.size() > 0) { std::lock_guard<std::mutex> lock(_mutex); for (auto pClient : _clientsBuff) { _clients[pClient->sockfd()] = pClient; } _clientsBuff.clear(); _clients_change = true; } //如果没有需要处理的客户端,就跳过 if (_clients.empty()) { std::chrono::milliseconds t(1); std::this_thread::sleep_for(t); continue; } fd_set fdRead; //fd_set fdWrite; //fd_set fdExp; FD_ZERO(&fdRead); if (_clients_change)//如果有客户端,加入,退出 { _maxSock = _clients.begin()->second->sockfd(); for (auto iter : _clients) { FD_SET(iter.second->sockfd(), &fdRead); if (_maxSock < iter.second->sockfd()) { _maxSock = iter.second->sockfd(); } } memcpy(&_fdRead_bak, &fdRead, sizeof(fd_set)); _clients_change = false; } else { memcpy(&fdRead, &_fdRead_bak, sizeof(fd_set)); } //timeval t = { 0, 0}; //int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t); int ret = select(_maxSock + 1, &fdRead, nullptr, nullptr, nullptr); if (ret < 0) { printf("select任务结束。\n"); Close(); return false; } else if (ret == 0) { continue; } #ifdef _WIN32 for (int n = 0; n < fdRead.fd_count; n++) { auto iter = _clients.find(fdRead.fd_array[n]); if (iter != _clients.end()) { if (-1 == RecvData(iter->second)) { if (_pNetEvent) _pNetEvent->OnNetLeave(iter->second); _clients_change = true; delete iter->second; _clients.erase(iter->first); } } else { printf("error. if (iter != _clients.end())\n"); } } #else std::vector<ClientSocket*> temp; for (auto iter : _clients) { if (FD_ISSET(iter.second->sockfd(), &fdRead)) { if (-1 == RecvData(iter.second)) { if (_pNetEvent) _pNetEvent->OnNetLeave(iter.second); _clients_change = true; temp.push_back(iter.second); } } } for (auto pClient : temp) { _clients.erase(pClient->sockfd()); delete pClient; } #endif } return true; } void addClient(ClientSocket* pClient) { _mutex.lock(); _clientsBuff.push_back(pClient); _mutex.unlock(); } size_t getClientCount() { return _clients.size() + _clientsBuff.size(); } void setEventObj(INetEvent* event) { _pNetEvent = event; } private: SOCKET _sock; //正式客户队列 //std::vector<ClientSocket*> _clients; std::map<SOCKET, ClientSocket*> _clients; //缓冲客户队列 std::vector<ClientSocket*> _clientsBuff; //缓冲客户端队列的锁 std::mutex _mutex; std::thread _pThread; char _szRecv[RECV_BUFF_SIZE] = { }; private: //网络事件对象 INetEvent* _pNetEvent; }; class EasyTcpServer:public INetEvent { public: EasyTcpServer() { _sock = INVALID_SOCKET; _clientCount = 0; _recvCount = 0; } virtual ~EasyTcpServer() { Close(); } //初始化socket SOCKET InitSocket() { #ifdef _WIN32 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); #endif // _WIN32 if (_sock != INVALID_SOCKET) { Close(); } _sock = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == _sock) { printf("错误,建立Socket失败...\n"); } else { printf("建立Socket成功...\n"); } return _sock; } //绑定端口号 int Bind(const char* ip, unsigned short port) { sockaddr_in _sin = {}; _sin.sin_family = AF_INET; _sin.sin_port = htons(4567);//host to net unsigned short //使用127.0.0.1可以防止外网访问 //启用本机全部的ip地址可以使用,INADDR_ANY #ifdef _WIN32 if (ip) { _sin.sin_addr.S_un.S_addr = inet_addr(ip); } else { _sin.sin_addr.S_un.S_addr = INADDR_ANY; } #else if (ip) { _sin.sin_addr.s_addr = inet_addr(ip); } else { _sin.sin_addr.s_addr = INADDR_ANY; } #endif int ret = bind(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in)); if (SOCKET_ERROR == ret) { printf("错误,绑定网络端口失败...\n"); } else { printf("绑定网络端口成功...\n"); } return ret; } //监听端口号 int Listen(int cnt) { int ret = listen(_sock, cnt); if (SOCKET_ERROR == ret) { printf("错误,监听网络端口失败...\n"); } else { printf("监听网络端口成功...\n"); } return ret; } //接收客户端连接 int Accept() { sockaddr_in clientAddr = {}; int nAddrLen = sizeof(clientAddr); SOCKET _cSock = INVALID_SOCKET; #ifdef _WIN32 _cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen); #else _cSock = accept(_sock, (sockaddr*)&clientAddr, (socklen_t*)&nAddrLen); #endif if (INVALID_SOCKET == _cSock) { printf("错误,接受到无效客户端socket...\n"); } else { //NewUserJoin msg; //msg.sock = _cSock; //Send2All(&msg); //_clients.push_back(new ClientSocket(_cSock)); //将新客户端分配给客户端数量最少的cellServer addClientToCellServer(new ClientSocket(_cSock)); //printf("new user join in:socket=%d,IP=%s,count=%d\n", _cSock, inet_ntoa(clientAddr.sin_addr), _clients.size()); } return _cSock; } void Start(int nCellServer) { for (int n=0; n<nCellServer; n++) { auto ser = new CellServer(_sock); _cellServers.push_back(ser); //注册网络事件接收对象 ser->setEventObj(this); //启动消息处理线程 ser->Start(); } } //关闭socket void Close() { if (_sock != INVALID_SOCKET) { #ifdef _WIN32 closesocket(_sock); //清除Windows socket环境 WSACleanup(); #else close(_sock); #endif _sock = INVALID_SOCKET; } } void addClientToCellServer(ClientSocket* pClient) { OnNetJoin(pClient); //查找客户数量最少的CellServer消息处理对象 auto pMinServer = _cellServers[0]; for (auto pCellServer : _cellServers) { if (pCellServer->getClientCount() < pMinServer->getClientCount()) { pMinServer = pCellServer; } } pMinServer->addClient(pClient); } //处理网络消息 bool OnRun() { if (!isRun()) { return false; } time4msg(); fd_set fdRead; //fd_set fdWrite; //fd_set fdExp; FD_ZERO(&fdRead); //FD_ZERO(&fdWrite); //FD_ZERO(&fdExp); FD_SET(_sock, &fdRead); //FD_SET(_sock, &fdWrite); //FD_SET(_sock, &fdExp); SOCKET maxSock = _sock; timeval t = {0, 10}; //int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t); int ret = select(maxSock + 1, &fdRead, 0, 0, &t); if (ret < 0) { printf("select任务结束。\n"); Close(); return false; } if (FD_ISSET(_sock, &fdRead)) { FD_CLR(_sock, &fdRead);//从集合移除了_sock Accept(); } return true; } //是否工作中 bool isRun() { return _sock != INVALID_SOCKET; } //计算每秒钟收到了多少网络消息 void time4msg() { auto t1 = _tTime.getElapsedTimeInSec(); if (t1 > 1.0) { printf("time:%lf,sock:%d,clients:%d,recv count:%d\n", t1, _sock, _clientCount, (int)(_recvCount/t1)); _recvCount = 0; _tTime.update(); } } //void Send2All(DataHeader* header) //{ // if (isRun() && header) // { // for (int n = (int)_clients.size() - 1; n >= 0; n--) // { // SendData(_clients[n]->sockfd(), header); // } // } //} virtual void OnNetJoin(ClientSocket* pClient) { _clientCount++; } virtual void OnNetLeave(ClientSocket* pClient) { _clientCount--; } virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header) { _recvCount++; } private: SOCKET _sock; //消息处理对象,内部会创建线程 std::vector<CellServer*> _cellServers; CELLTimestamp _tTime; protected: std::atomic<int> _clientCount; //收到消息计数 std::atomic<int> _recvCount; }; #endif
main.cpp
#include "EasyTcpServer.hpp" #include <thread> bool g_bRun = true; void cmdThread() { while (true) { //3输入请求命令 char cmdBuf[128] = {}; scanf("%s", cmdBuf); getchar(); //4处理请求 if (0 == strcmp(cmdBuf, "exit")) { printf("退出cmdThread线程\n"); g_bRun = false; return; } else { printf("wrong cmd, please input cmd again.\n"); } } } class MyServer : public EasyTcpServer { public: //只会被一个线程触发 安全 virtual void OnNetJoin(ClientSocket* pClient) { _clientCount++; //printf("client<%d> join\n", pClient->sockfd()); } //cellServer 4 多个线程触发 不安全 //如果只开启1个cellServer就是安全的 virtual void OnNetLeave(ClientSocket* pClient) { _clientCount--; //printf("client<%d> leave\n", pClient->sockfd()); } //cellServer 4 多个线程触发 不安全 //如果只开启1个cellServer就是安全的 virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header) { _recvCount++; switch (header->cmd) { case CMD_LOGIN: { Login* login = (Login*)header; //printf("收到客户端<Socket=%d>请求:CMD_LOGIN,数据长度:%d,userName=%s PassWord=%s\n", cSock, login->dataLength, login->userName, login->PassWord); //忽略判断用户密码是否正确的过程 //LoginResult ret; //pClient->SendData(&ret); } break; case CMD_LOGOUT: { LogOut* logout = (LogOut*)header; //printf("收到客户端<Socket=%d>请求:CMD_LOGOUT,数据长度:%d,userName=%s \n", cSock, logout->dataLength, logout->userName); //忽略判断用户密码是否正确的过程 //LogoutResult ret; //SendData(cSock, &ret); } break; default: { printf("<socket=%d>收到未定义消息,数据长度:%d\n", pClient->sockfd(), header->dataLength); //DataHeader ret; //SendData(cSock, &ret); } break; } } private: }; int main() { MyServer server; server.InitSocket(); server.Bind(nullptr, 4567); server.Listen(5); //创建4个线程,收发客户端消息 server.Start(4); std::thread t(cmdThread); t.detach(); while (g_bRun) { server.OnRun(); } server.Close(); printf("已退出。\n"); getchar(); return 0; }