多工的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;
}
}