TCP通讯程序的编写
目錄
1.TCP和UDP區別
2.TCP通訊程序的編寫流程
3.實現一次循環通信
4.改進---多進程方式實現多次通信?
5.改進---多線程方式實現多次通信?
6.改進---多線程方式+字典方式實現多次通信?
?
1.TCP和UDP區別
TCP:面向連接、可靠傳輸、面向字節流? 應用場景:文件傳輸(安全性高于實時性)
UDP:無連接、不可靠、面向數據報? 應用場景:視頻、音頻(實時性高于安全性)
2.TCP通訊程序的編寫流程
服務器端:
客戶端:
3.實現一次循環通信
代碼如下:
tcp_socket.hpp:
#include<iostream> #include<string> #include<vector> #include<unistd.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/socket.h>#define MAX_LISTEN 5 #define CHECK_RES(q) if((q)==false) { return -1;} class TcpSocket{private:int _sockfd;public:TcpSocket():_sockfd(-1){}//創建套接字 bool Socket(){_sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(_sockfd < 0){perror("socket error");return false;}return true;}//綁定地址信息bool Bind(const std::string &ip,uint16_t port){struct sockaddr_in addr;//先定義一個ipv4的地址結構addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t len = sizeof(struct sockaddr_in);int ret = bind(_sockfd,(struct sockaddr*)&addr,len);if(ret<0){perror("bind error");return false;}return true;}//向服務器發起連接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(struct sockaddr_in);int ret = connect(_sockfd,(struct sockaddr*)&addr,len);if(ret < 0){perror("connect error");return false;}return true;}//服務器開始監聽bool Listen(int backlog = MAX_LISTEN){int ret = listen(_sockfd,backlog);if(ret < 0){perror("listen error");return false;}return true;}//獲取新建連接bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL){struct sockaddr_in addr;socklen_t len = sizeof(struct sockaddr_in);int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);if(newfd<0){perror("accept error");return false;}sock->_sockfd = newfd;if(ip != NULL){*ip = inet_ntoa(addr.sin_addr);}if(port != NULL){*port = ntohs(addr.sin_port);}return true;}//接受數據bool Recv(std::string *body){char tmp[4096] = {0};int ret = recv(_sockfd,tmp,4096,0);if(ret < 0){perror("recv error");return false;}else if(ret == 0){std::cout<<"peer shutdown!\n";return false;}body->assign(tmp,ret);//從tmp中截取ret長度大小的數據return true;}//發送數據bool Send(const std::string &body){int ret;ret = send(_sockfd,body.c_str(),body.size(),0);if(ret < 0){perror("send error");return false;}return true;}//關閉套接字bool Close(){if(_sockfd!= -1){close(_sockfd);}return true;}};tcp_srv.cpp:
#include "tcp_socket.hpp" int main() {TcpSocket lst_sock;//創建套接字CHECK_RES(lst_sock.Socket());//綁定地址信息CHECK_RES(lst_sock.Bind("192.168.164.128",20000));//開始監聽CHECK_RES(lst_sock.Listen());while(1){//獲取新建連接TcpSocket conn_sock;std::string cliip;uint16_t cliport;bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);if(ret < 0){continue;}std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;//使用新建連接與客戶端通信std::string buf;ret = conn_sock.Recv(&buf);if(ret == false){conn_sock.Close();continue;}std::cout<<"client say:"<<buf<<std::endl;std::cout<<"server say:";fflush(stdout);std::cin>>buf;ret = conn_sock.Send(buf);if(ret == false){conn_sock.Close();continue;}}// 關閉套接字lst_sock.Close();return 0;}tcp_cli.cpp:
#include "tcp_socket.hpp"int main(int argc,char* argv[]) {if(argc!= 3){std::cout<<"please input server address!\n";std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";return -1;}std::string srv_ip = argv[1];uint16_t srv_port = std::stoi(argv[2]);TcpSocket cli_sock;//創建套接字CHECK_RES(cli_sock.Socket());//綁定地址信息(客戶端不推薦)//向服務器發起連接CHECK_RES(cli_sock.Connect(srv_ip,srv_port));while(1){//與服務器通信std::string buf;std::cout<<"client say:";fflush(stdout);std::cin>>buf;bool ret = cli_sock.Send(buf);if(ret == false){cli_sock.Close();return -1;}buf.clear();ret = cli_sock.Recv(&buf);if(ret == false){cli_sock.Close();return -1;}std::cout<<"server say:"<<buf<<std::endl;}//關閉套接字cli_sock.Close();return 0;}makefile:
all:tcp_srv tcp_cli tcp_cli:tcp_cli.cppg++ -std=c++11 $^ -o $@ tcp_srv:tcp_srv.cppg++ -std=c++11 $^ -o $@測試結果如下:
先運行服務器端程序:
?再運行客戶端程序:
?客戶端發送數據:
服務器端收到客戶端發送的數據并進行回復:
客戶端收到了服務器端的回復:
?
4.改進---多進程方式實現多次通信?
?服務器端代碼改動 (cp tcp_srv.cpp process_srv.cpp):
process_srv.cpp代碼如下:
#include "tcp_socket.hpp" #include<signal.h>int new_worker(TcpSocket &conn_sock) {pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){while(1){std::string buf;bool ret = conn_sock.Recv(&buf);if(ret == false){conn_sock.Close();break;}std::cout<<"client say:"<<buf<<std::endl;std::cout<<"server say:";fflush(stdout);std::cin>>buf;ret = conn_sock.Send(buf);if(ret == false){conn_sock.Close();break;}}conn_sock.Close();exit(-1);} return 0; }int main() {//顯示忽略子進程退出信號,相當于告訴系統,子進程退出直接釋放資源signal(SIGCHLD,SIG_IGN); TcpSocket lst_sock;//創建套接字CHECK_RES(lst_sock.Socket());//綁定地址信息CHECK_RES(lst_sock.Bind("192.168.164.128",20000));//開始監聽CHECK_RES(lst_sock.Listen());while(1){//獲取新建連接TcpSocket conn_sock;std::string cliip;uint16_t cliport;bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);if(ret < 0){continue;}std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;//使用新建連接與客戶端通信new_worker(conn_sock);//父進程必須關閉套接字,否則連接的客戶端越多,創建的套接字越多//但父進程本質并不與客戶端通信,所以最后會資源耗盡//因此必須關閉,而父子進程數據獨有,因此父進程的關閉并不影響子進程conn_sock.Close();} // 關閉套接字lst_sock.Close();return 0;}makefile改動:
all:tcp_srv tcp_cli process_srv process_srv:process_srv.cppg++ -std=c++11 $^ -o $@ tcp_cli:tcp_cli.cppg++ -std=c++11 $^ -o $@ tcp_srv:tcp_srv.cppg++ -std=c++11 $^ -o $@測試結果(可實現多次通信):
?當前存在的問題:
若客戶端1和客戶端2都向服務器發送了數據,服務器在客戶端1發送了數據之后并沒有回復,在2發了之后才回復,此時回復的是客戶端1。
測試結果如下(1向服務器發送tianyijingheile,2向服務器發送ganjinxiakeba,服務器端二者都可收到,回復xiake是回復給了客戶端1):
客戶端1:
客戶端2:
?服務器端:
5.改進---多線程方式實現多次通信?
?cp tcp_srv.cpp thread_srv.cpp
thread_srv.cpp:
#include "tcp_socket.hpp" #include<pthread.h>void* entry(void* arg) {TcpSocket *conn_sock = (TcpSocket*)arg;while(1){ std::string buf;bool ret = conn_sock->Recv(&buf);if(ret == false){conn_sock->Close();break;}std::cout<<"client say:"<<buf<<std::endl;std::cout<<"server say:";fflush(stdout);std::cin>>buf;ret = conn_sock->Send(buf);if(ret == false){conn_sock->Close();break;}}conn_sock->Close();delete conn_sock;return NULL; } bool new_worker(TcpSocket *conn_sock) {pthread_t tid;int ret = pthread_create(&tid,NULL,entry,(void*)conn_sock);if(ret != 0){std::cout<<"thread create error\n";return false;}pthread_detach(tid);return true;}int main() {TcpSocket lst_sock;//創建套接字CHECK_RES(lst_sock.Socket());//綁定地址信息CHECK_RES(lst_sock.Bind("192.168.164.128",20000));//開始監聽CHECK_RES(lst_sock.Listen());while(1){//獲取新建連接,在堆上申請TcpSocket *conn_sock = new TcpSocket();std::string cliip;uint16_t cliport;bool ret = lst_sock.Accept(conn_sock,&cliip,&cliport);if(ret < 0){continue;}std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;//使用新建連接與客戶端通信new_worker(conn_sock);}// 關閉套接字lst_sock.Close();return 0;}makefile:
all:tcp_srv tcp_cli process_srv thread_srv thread_srv:thread_srv.cppg++ -std=c++11 $^ -o $@ -lpthread process_srv:process_srv.cppg++ -std=c++11 $^ -o $@ tcp_cli:tcp_cli.cppg++ -std=c++11 $^ -o $@ tcp_srv:tcp_srv.cppg++ -std=c++11 $^ -o $@測試結果和多進程方式一樣,存在的問題也相同。
6.改進---多線程方式+字典方式實現多次通信?
cp thread_srv.cpp robot_srv.cpp
robot_srv.cpp:
#include "tcp_socket.hpp"#include<pthread.h> #include<unordered_map>std::unordered_map<std::string,std::string>_dictionaries={{"nihao","你好"},{"leihou","雷猴"},{"hello","hi"}};std::string get_rsp(const std::string &key) { auto it = _dictionaries.find(key);if(it!= _dictionaries.end()){return it->second;}return "不要亂說話"; }void* entry(void* arg) {TcpSocket *conn_sock = (TcpSocket*)arg;while(1){ std::string buf;bool ret = conn_sock->Recv(&buf);if(ret == false){conn_sock->Close();break;}std::string data = get_rsp(buf); ret = conn_sock->Send(data);if(ret == false){conn_sock->Close();break;}}conn_sock->Close();delete conn_sock;return NULL; } bool new_worker(TcpSocket *conn_sock) {pthread_t tid;int ret = pthread_create(&tid,NULL,entry,(void*)conn_sock);if(ret != 0){std::cout<<"thread create error\n";return false;}pthread_detach(tid);return true;}int main() {TcpSocket lst_sock;//創建套接字CHECK_RES(lst_sock.Socket());//綁定地址信息CHECK_RES(lst_sock.Bind("192.168.164.128",20000));//開始監聽CHECK_RES(lst_sock.Listen());while(1){//獲取新建連接,在堆上申請TcpSocket *conn_sock = new TcpSocket();std::string cliip;uint16_t cliport;bool ret = lst_sock.Accept(conn_sock,&cliip,&cliport);if(ret < 0){continue;}std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;//使用新建連接與客戶端通信new_worker(conn_sock);}// 關閉套接字lst_sock.Close();return 0;}makefile:
all:tcp_srv tcp_cli process_srv thread_srv robot_srv robot_srv:robot_srv.cppg++ -std=c++11 $^ -o $@ -lpthread thread_srv:thread_srv.cppg++ -std=c++11 $^ -o $@ -lpthread process_srv:process_srv.cppg++ -std=c++11 $^ -o $@ tcp_cli:tcp_cli.cppg++ -std=c++11 $^ -o $@ tcp_srv:tcp_srv.cppg++ -std=c++11 $^ -o $@測試結果 客戶端:
?
總結
以上是生活随笔為你收集整理的TCP通讯程序的编写的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习日志day46(2021-09-10
- 下一篇: idea 切换分支时书签消失解决方案