多工的socket
之前的socket是一對一連線,也就是一個socket對一個client。
此篇是一個Server,可以讓多個Client連線。
每次接受Client連線後,就fork一個子程序去做傳送資料與接收資料的事。
父程序流程
監聽listen socket()函式 -> 轉換port變成big-endian -> bind()綁定ip與port -> listen()開始監聽 -> accept()接受client連線,傳回client socket
對於父程序來說,只要管理listen socket,關閉listen socket。
子程序流程
send()傳送資料 -> recv()接收資料
對於子程序而言,只要管理client socket,關閉client socket。
多工Server Socket程式碼
- TCPServer tcpServer;設為全域變數
- include signal.h
- 複製kill的FatherExit()與ChildExit()函式
- 加上while
- fork複製出子程序
- fork複製出子程序後,父程序就不需要管理client socket,關掉client socket
- 子程序不用管理監聽的socket,所以關掉它
- 子程序執行完,要關閉子程序,return 0;在無限迴圈內關閉子程序,while(true){… return 0;}
class TCPServer { public: // 建構子 // 監聽listen_fd,-1代表未初始化 // client_fd,-1代表未連線 TCPServer() : listen_fd_(-1), client_fd_(-1) {} // 初始化監聽的socket bool initListen(const unsigned short port) { listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listen_fd_ == -1) return false; port_ = port; // sockaddr_in結構 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; // server port server_addr.sin_port = htons(port_); // server有多個ip,所有ip都會監聽port server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // socket綁定ip與port if (::bind(listen_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { // 關閉監聽器 close(listen_fd_); listen_fd_ = -1; return false; } // 把socket設為監聽的狀態 // 最多只讓3個client同時連server if (listen(listen_fd_, 3)) { close(listen_fd_); listen_fd_ = -1; return false; } return true; } bool accept() { // client的ip結構 struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); // 接受client的連線 // 若要得到client的ip,第二個參數填sockaddr_in結構,第3個參數填結構大小 // 若不想得到client ip,第2與第3參數填0就好 client_fd_ = ::accept(listen_fd_, (struct sockaddr*)&client_addr, &addr_len); if (client_fd_ == -1) return false; // 取得client的ip // 透過inet_ntoa把big-endian轉成little-endian client_ip_ = inet_ntoa(client_addr.sin_addr); return true; } const string& getClientIp() const { return client_ip_; } bool send(const string &buffer) { // 如果client fd是-1就沒必要繼續後面的動作,直接返回 if (client_fd_ == -1) return false; // send()第2個參數填記憶體位址,第3個參數填大小 if ((::send(client_fd_, buffer.data(), buffer.size(), 0)) <= 0) return false; return true; } // 第一個參數要修改buffer的內容,所以不加const bool recv(string &buffer, const size_t maxlen) { // 清空string buffer.clear(); // 分配記憶體大小 buffer.resize(maxlen); // 收到的資料放在buffer的記憶體空間 // 操作到string的記憶體位址,有對string物件進行讀取與寫入 // 第2個參數填不是const的記憶體位址,第3個參數填大小 // &buffer[0]取得位址,傳回值不是const // buffer.data()傳回值是const,所以不用 // recv()傳回值是收到的資料大小 int iret = ::recv(client_fd_, &buffer[0], buffer.size(), 0); // iret = -1 失敗 // iret = 0 socket斷線 // iret > 0 收到資料 if (iret <= 0) { cout << "iret = " << iret << endl; // 失敗時把buffer清空,否則buffer大小一直維持1024 buffer.clear(); return false; } // 如果有操作到string的記憶體位址,string的自動擴展空間會失效 // 需要自己重設string大小 // 從1024大小,設為真正收到資料的大小 buffer.resize(iret); return true; } // 關閉監聽socket bool closeListen() { // 若未初始化,直接返回 if (listen_fd_ == -1) return false; ::close(listen_fd_); // 設為未初始化-1 listen_fd_ = -1; return true; } // 關閉client socket bool closeClient() { // client_fd若已斷線,直接返回 if (client_fd_ == -1) return false; ::close(client_fd_); // 設成未連線-1 client_fd_ = -1; return true; } ~TCPServer() { // 解構子把二個socket都關掉 closeListen(); closeClient(); } private: int listen_fd_; int client_fd_; string client_ip_; unsigned short port_; }; // 設成全域變數 // 建立監聽fd listen_fd TCPServer tcpServer; // 父程序離開 void FatherExit(int sig) { // 怱略二次收到相同訊號 // 使用kill(0, 訊號)會發給子程序也會再次發給發送訊號的父程序自己 signal(SIGINT, SIG_IGN); // 怱略ctrl+c signal(SIGTERM, SIG_IGN); // 怱略kill -15 cout << "父程序退出, signal = " << sig << endl; kill(0, SIGTERM); // 向全部的子程序發送15訊號,終止子程序 // 父程序管理listen socket,父程序要離開要釋放記憶體 // 關閉listen socket tcpServer.closeListen(); exit(0); // 父程序離開 } // 子程序離開 void ChildExit(int sig) { // 防止再次收到相同訊號 signal(SIGINT, SIG_IGN); // 怱略ctrl+c signal(SIGTERM, SIG_IGN); // 怱略kill -15 cout << "子程序 pid = " << getpid() << " 退出, signal = " << sig << endl; // 子程序管理client socket,子程序要離開要釋放記憶體 // 關閉client socket tcpServer.closeClient(); exit(0); // 子程序離開 } int main(int argc, char *argv[]) { if (argc != 2) { cout << "請輸入 ./server_test port" << endl; return -1; } // 略過全部訊號 for (int i = 0; i <= 64; i++) signal(i, SIG_IGN); // 父程序要補捉的訊號 ctrl + c signal(SIGTERM, FatherExit); // 父程序要補捉kill -15 signal(SIGINT, FatherExit); // 初始化監聽 if (tcpServer.initListen(atoi(argv[1])) == false) { perror("initListen"); return -1; } // 無限循環,一直為Client提供服務 while (true) { // 接受client連接 if (tcpServer.accept() == false) { perror("accept"); return -1; } // fork後,會把client socket與listen socket複製一份給子程序 int pid = fork(); if (pid == -1) { perror("fork()"); return -1; } // 大於0是父程序,直接跳到while()條件句,等待Client來連線 if (pid > 0) { // fork複製出子程序後,父程序就不需要管理client socket,這個是子程序管理 // 關掉client socket tcpServer.closeClient(); continue; } // 以下是子程序要跑的程式碼 // 子程序不用管理監聽的socket,所以關掉它 tcpServer.closeListen(); // 子程序要補捉的訊號 ctrl + c signal(SIGTERM, ChildExit); // 子程序要補捉kill -15 signal(SIGINT, ChildExit); cout << "client已連上 = " << tcpServer.getClientIp() << endl; string buffer; while (true) { // 如果client沒有send data,recv()會等待 // 收到0,代表斷線 if (tcpServer.recv(buffer, 1024) == false) { perror("recv()"); break; } cout << "收到client data = " << buffer << endl; // 建立回應的資料給client buffer = "ok"; // 傳送回應的資料 if (tcpServer.send(buffer) == false) { perror("send"); break; } cout << "send = " << buffer << endl; } // 子程序處理用戶端傳輸,傳輸接收完畢後,要用return 0關閉子程序 // 若不關閉又會回到accept()函式 return 0; } }