目录
TCP:面向连接、可靠传输、面向字节流 应用场景:文件传输(安全性高于实时性)
UDP:无连接、不可靠、面向数据报 应用场景:视频、音频(实时性高于安全性)
服务器端:
客户端:
代码如下:
tcp_socket.hpp:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #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:"<
":"< - //使用新建连接与客户端通信
- std::string buf;
- ret = conn_sock.Recv(&buf);
- if(ret == false)
- {
- conn_sock.Close();
- continue;
- }
- std::cout<<"client say:"<
- 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:"<
- }
- //关闭套接字
- cli_sock.Close();
- return 0;
- }
makefile:
- all:tcp_srv tcp_cli
- tcp_cli:tcp_cli.cpp
- g++ -std=c++11 $^ -o $@
- tcp_srv:tcp_srv.cpp
- g++ -std=c++11 $^ -o $@
测试结果如下:
先运行服务器端程序:
再运行客户端程序:
客户端发送数据:
服务器端收到客户端发送的数据并进行回复:
客户端收到了服务器端的回复:
4.改进---多进程方式实现多次通信
服务器端代码改动 (cp tcp_srv.cpp process_srv.cpp):
process_srv.cpp代码如下:
- #include "tcp_socket.hpp"
- #include
-
- 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:"<
- 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:"<
":"< - //使用新建连接与客户端通信
- new_worker(conn_sock);
- //父进程必须关闭套接字,否则连接的客户端越多,创建的套接字越多
- //但父进程本质并不与客户端通信,所以最后会资源耗尽
- //因此必须关闭,而父子进程数据独有,因此父进程的关闭并不影响子进程
- conn_sock.Close();
-
- }
- // 关闭套接字
- lst_sock.Close();
- return 0;
- }
makefile改动:
- all:tcp_srv tcp_cli process_srv
- process_srv:process_srv.cpp
- g++ -std=c++11 $^ -o $@
- tcp_cli:tcp_cli.cpp
- g++ -std=c++11 $^ -o $@
- tcp_srv:tcp_srv.cpp
- g++ -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
-
- 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:"<
- 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:"<
":"< - //使用新建连接与客户端通信
- new_worker(conn_sock);
-
- }
-
- // 关闭套接字
- lst_sock.Close();
- return 0;
- }
makefile:
- all:tcp_srv tcp_cli process_srv thread_srv
- thread_srv:thread_srv.cpp
- g++ -std=c++11 $^ -o $@ -lpthread
- process_srv:process_srv.cpp
- g++ -std=c++11 $^ -o $@
- tcp_cli:tcp_cli.cpp
- g++ -std=c++11 $^ -o $@
- tcp_srv:tcp_srv.cpp
- g++ -std=c++11 $^ -o $@
测试结果和多进程方式一样,存在的问题也相同。
6.改进---多线程方式+字典方式实现多次通信
cp thread_srv.cpp robot_srv.cpp
robot_srv.cpp:
- #include "tcp_socket.hpp"
-
- #include
- #include
-
- std::unordered_map
_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:"<
":"< - //使用新建连接与客户端通信
- 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.cpp
- g++ -std=c++11 $^ -o $@ -lpthread
- thread_srv:thread_srv.cpp
- g++ -std=c++11 $^ -o $@ -lpthread
- process_srv:process_srv.cpp
- g++ -std=c++11 $^ -o $@
- tcp_cli:tcp_cli.cpp
- g++ -std=c++11 $^ -o $@
- tcp_srv:tcp_srv.cpp
- g++ -std=c++11 $^ -o $@
测试结果 客户端:
-
相关阅读:
Java的CompletableFuture,Java的多线程开发
Qt 框架 6.6版本添加响应式布局,并兼容AArch64 架构
Elasticsearch高级检索之使用单个字母数字进行分词N-gram tokenizer(不区分大小写)【实战篇】
一览8个 NFT 分析平台
MySQL8.0.26安装配置教程(windows 64位)
ROS-API函数介绍
第八篇 基于JSP 技术的网上购书系统——商品信息查看、我的购物车、结算中心功能实现(网上商城、仿淘宝、当当、亚马逊)
y137.第八章 Servless和Knative从入门到精通 -- Serverless概念和基础(一)
工具: MarkDown学习
harbor 搭建和部署
-
原文地址:https://blog.csdn.net/weixin_46153828/article/details/126475480