Linux网络编程 | Socket编程(二)TCPSocket的封装、TCP服务器多进程、多线程版本的实现
目錄
- TCP的通信流程
- TCPSocket的封裝
- TCP客戶(hù)端
- TCP服務(wù)器
- 多進(jìn)程版本
- 多線(xiàn)程版本
TCP的通信流程
計(jì)算機(jī)網(wǎng)絡(luò) (三) 傳輸層 :一文搞懂UDP與TCP協(xié)議
在這篇博客中,我描述了UDP與TCP的特性以及通信流程,下面就根據(jù)特性來(lái)規(guī)劃該如何通過(guò)Socket來(lái)實(shí)現(xiàn)UDP通信。
TCPSocket的封裝
為了使用更方便,先封裝一個(gè)TCPSocket
#include<iostream> #include<string> #include<unistd.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h>const int MAX_LISTEN = 5;inline void CheckSafe(bool ret) {if(ret == false){exit(0);} }class TcpSocket {public:TcpSocket() : _socket_fd(-1){}//創(chuàng)建套接字bool Socket(){_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if(_socket_fd < 0){std::cerr << "socket create error" << std::endl;return false;}return true;}//綁定地址信息bool Bind(const std::string& ip, uint16_t& port){struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t len = sizeof(sockaddr_in);int ret = bind(_socket_fd, (sockaddr*)&addr, len);if(ret < 0){std::cerr << "bind error" << std::endl;return false;}return true;}//監(jiān)聽(tīng)bool Listen(int backlog = MAX_LISTEN){//用初始的套接字開(kāi)始監(jiān)聽(tīng)int ret = listen(_socket_fd, backlog);if(ret < 0){std::cerr << "connect error" << std::endl;}return true;}//新建連接bool Accept(TcpSocket *new_sock, std::string* ip = NULL, uint16_t* port = NULL){struct sockaddr_in addr;socklen_t len = sizeof(sockaddr_in);//創(chuàng)建一個(gè)新的套接字與客戶(hù)端建立連接int new_fd = accept(_socket_fd, (sockaddr*)&addr, &len);if(new_fd < 0){std::cerr << "accept error" << std::endl;}new_sock->_socket_fd = new_fd;if(ip != NULL){*ip = inet_ntoa(addr.sin_addr);}if(port != NULL){*port = ntohs(addr.sin_port);}return true;}//發(fā)起連接請(qǐng)求bool Connect(const std::string& ip, uint16_t port){struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t len = sizeof(sockaddr_in);int ret = connect(_socket_fd, (sockaddr*)&addr, len);if(ret < 0){std::cerr << "connect error" << std::endl;}return true;}//發(fā)送數(shù)據(jù)bool Send(const std::string& data){int ret = send(_socket_fd, data.c_str(), data.size(), 0); if(ret < 0){std::cerr << "send error" << std::endl;}return true;}//接收數(shù)據(jù)bool Recv(std::string& data){char buff[4096] = { 0 };int ret = recv(_socket_fd, buff, 4096, 0);if(ret == 0){std::cerr << "connect error" << std::endl;return false;}else if(ret < 0){std::cerr << "recv error" << std::endl;return false;}data.assign(buff, ret);return true;}void Close(){if(_socket_fd > 0){close(_socket_fd);_socket_fd = -1;}}private:int _socket_fd; };TCP客戶(hù)端
按照這個(gè)流程,來(lái)實(shí)現(xiàn)TCP的客戶(hù)端
TCP服務(wù)器
因?yàn)樵谝欢螘r(shí)間內(nèi)可能會(huì)建立多個(gè)連接,所以多個(gè)執(zhí)行流分別去控制這些連接。每當(dāng)創(chuàng)建一個(gè)新連接,就分配一個(gè)新的執(zhí)行流去執(zhí)行它。
下面就分別實(shí)現(xiàn)多進(jìn)程版本和多線(xiàn)程版本的服務(wù)器
多進(jìn)程版本
注意:因?yàn)楦缸舆M(jìn)程數(shù)據(jù)獨(dú)有,子進(jìn)程會(huì)拷貝一份父進(jìn)程,所以對(duì)于父進(jìn)程來(lái)說(shuō)新建的套接字用不到,需要關(guān)閉。父進(jìn)程也不需要去阻塞等待子進(jìn)程,處理好信號(hào)即可
#include<iostream> #include<signal.h> #include<sys/wait.h> #include"TcpSocket.hpp"using namespace std;void sigcb(int no) {while(waitpid(-1, NULL, WNOHANG) > 0); }int main(int argc, char* argv[]) {if(argc != 3){ cerr << "正確輸入方式: ./tcp_srv_process.cc. ip port\n" << endl;return -1; } signal(SIGCHLD, sigcb);string srv_ip = argv[1];uint16_t srv_port = stoi(argv[2]);TcpSocket socket;//創(chuàng)建套接字CheckSafe(socket.Socket());//綁定地址信息CheckSafe(socket.Bind(srv_ip, srv_port));//開(kāi)始監(jiān)聽(tīng)CheckSafe(socket.Listen());while(1){TcpSocket new_sock;//通過(guò)監(jiān)聽(tīng)套接字建立連接CheckSafe(socket.Accept(&new_sock));//創(chuàng)建子進(jìn)程int pid = fork();if(pid == 0){while(1){string data;//接收數(shù)據(jù)CheckSafe(new_sock.Recv(data));cout << "cli send message: " << data << endl;data.clear();cout << "srv reply message: ";//發(fā)送數(shù)據(jù)getline(cin, data);CheckSafe(new_sock.Send(data));}//關(guān)閉子進(jìn)程連接的套接字new_sock.Close();exit(0);}//關(guān)閉父進(jìn)程套接字new_sock.Close();}//關(guān)閉監(jiān)聽(tīng)套接字socket.Close();return 0; }多線(xiàn)程版本
注意:因?yàn)榫€(xiàn)程之間共享描述符表,所以主線(xiàn)程創(chuàng)建線(xiàn)程之后千萬(wàn)不能關(guān)閉新建的套接字。
#include<iostream> #include"TcpSocket.hpp" #include<pthread.h>using namespace std;void* thr_work(void* arg) {//因?yàn)閰?shù)只能是void*,所以要強(qiáng)轉(zhuǎn)獲取操作句柄,所以讀取前四個(gè)字節(jié)即可long fd = (long)arg;TcpSocket new_sock;new_sock.SetFd(fd);while(1){string data;//接收數(shù)據(jù)new_sock.Recv(data);cout << "cli send message: " << data << endl;data.clear();cout << "srv reply message: ";//發(fā)送數(shù)據(jù)getline(cin, data);new_sock.Send(data);}new_sock.Close();return nullptr; }int main(int argc, char* argv[]) {if(argc != 3){ cerr << "正確輸入方式: ./tcp_srv_thread.cc. ip port\n" << endl;return -1; } string srv_ip = argv[1];uint16_t srv_port = stoi(argv[2]);TcpSocket lst_socket;//創(chuàng)建監(jiān)聽(tīng)套接字CheckSafe(lst_socket.Socket());//綁定地址信息CheckSafe(lst_socket.Bind(srv_ip, srv_port));//開(kāi)始監(jiān)聽(tīng)CheckSafe(lst_socket.Listen());while(1){TcpSocket* new_sock = new TcpSocket();//通過(guò)監(jiān)聽(tīng)套接字獲取連接bool ret = lst_socket.Accept(new_sock);//連接失敗則重新連接if(!ret){cerr << "連接失敗" << endl;continue;}//創(chuàng)建線(xiàn)程pthread_t tid;int res = pthread_create(&tid, NULL, thr_work, (void*)new_sock->GetFd());if(res != 0){cerr << "線(xiàn)程創(chuàng)建失敗" << endl;continue;}//不需要知道返回值,所以直接分離線(xiàn)程pthread_detach(tid);}//關(guān)閉監(jiān)聽(tīng)套接字lst_socket.Close();return 0; }總結(jié)
以上是生活随笔為你收集整理的Linux网络编程 | Socket编程(二)TCPSocket的封装、TCP服务器多进程、多线程版本的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux网络编程 | Socket编程
- 下一篇: C++ 面向对象(三)异常 :异常概念、