TCP通讯程序的编写
目录
1.TCP和UDP区别
2.TCP通讯程序的编写流程
3.实现一次循环通信
4.改进---多进程方式实现多次通信
5.改进---多线程方式实现多次通信
6.改进---多线程方式+字典方式实现多次通信
1.TCP和UDP区别
TCP:面向连接、可靠传输、面向字节流 应用场景:文件传输(安全性高于实时性)
UDP:无连接、不可靠、面向数据报 应用场景:视频、音频(实时性高于安全性)
2.TCP通讯程序的编写流程
服务器端:
- 创建套接字
- 为套接字绑定地址信息
- 开始监听---将套接字状态置为LISTEN 告诉系统这个套接字可以开始处理连接,tcp服务器会为每个客户端创建一个新的套接字用于与指定客户端通信
- 获取新建连接socket的套接字描述符
- 收发数据
- 关闭套接字
客户端:
- 创建套接字
- 绑定地址信息(不推荐客户端主动绑定地址信息)
- 向服务器发起数据
- 收发数据
- 关闭套接字
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通讯程序的编写相关推荐
- 威纶tk6070ik与台达变频器vdf-s485通讯程序 自己编写的威纶触摸屏与台达变频器的通讯
威纶tk6070ik与台达变频器vdf-s485通讯程序 自己编写的威纶触摸屏与台达变频器的通讯程序,可以直接控制变频器正反转,启动停止,监视变频器的电流电压等及错误代码等. ID:981558944 ...
- 三菱fx3u通讯手册_三菱FX3U与变频器通讯程序如何编写
前面我们讲了如何用串口调试工具通讯变频器,也学习了Modbus的报文格式.今天老冯教你们怎么用PLC写通讯程序. 首先看我们要准备的硬件: 1.三菱FX3U 2.三菱FX3U-485BD板 3.台达V ...
- 在C++ Builder中用socket api来写网络通讯程序(同时支持TCP和UDP协议)
原标题:在C++ Builder中用socket api来写网络通讯程序(同时支持TCP和UDP协议) 原文: http://www.csdn.net/develop/read_article.as ...
- java tcp 编程实例_Java实现基于TCP的通讯程序实例解析
Java中的TCP通信程序 TCP可以实现两台计算机之间的数据交互通信的两端,要严格区分客户端与服务端 两端通信时的步骤: 1.服务端程序,需要事先启动,等待客户端连接 2.客户端主动连接服务器端,才 ...
- python socket recv超时_python使用多线程编写tcp客户端程序,你还没掌握吗?
这篇文章主要为大家详细介绍了python使用多线程编写tcp客户端程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 在网上浏览的时候发现很多关于此题目的程序都只能接收数据,所以随便找了个程序研究 ...
- VC++编写USB接口通讯程序
用VC++编写USB接口通讯程序 摘要:详细介绍Visual C++ 6.0环境下利用Windows API(Application Program Interface)函数来实现与符合HID设备类的 ...
- 《 Python程序设计项目案例》— 用Python开发的基于TCP通讯协议的私人聊天室 (期末大作业、结课作业、课程设计、毕业设计)
基于Python与TCP协议的私人聊天室(GUI交互界面,用户注册.用户登录.实时聊天,文件上传与下载) 用Python开发的基于TCP通讯协议的实时聊天通讯和文件共享应用 目录 基于Python与T ...
- S7-Smart200-和变频器Modbus通讯 程序由smart200 编写,东源变频器的modbus通讯
S7-Smart200-和变频器Modbus通讯 程序由smart200 编写,东源变频器的modbus通讯,控制启停由外部端子控制,通过通讯写入频率和读取运行电流的操作,软件版本smart V2.4 ...
- TCP/IP协议的编写《转载》
基于HHARM9-EDU的TCP/IP(UDP)协议的实现 原文网址:http://blog.csdn.net/lhj0503/article/details/3323788 摘 要:嵌入式技术的发展 ...
最新文章
- pm2-zabbix 安装与配置
- 一个项目搞定支付宝,微信支付!
- python 线程同步_Python并发编程-线程同步(线程安全)
- 前端学习(1668):前端系列实战课程之限制范围拖拽思路
- 理解CSS3 max/min-content及fit-content等width值
- C语言 memcpy和memcpy_s区别 - C语言零基础入门教程
- 一个前端UI资源共享网站
- BZOJ 1034: [ZJOI2008]泡泡堂BNB
- 利用SPI编写类似sockscap的代理工具
- 职称计算机考试题库word2003,全国职称计算机考试题库(Word2003模块)
- 百度地图android去除logo,百度地图 Android版 隐藏logo
- 互联网变迁-真实化信息的转移
- flask form表单
- 360P 480P 720P 1080P 1080i 说明
- html头像动画,用CSS3实现头像旋转效动画
- 高仿滴滴打车 android,仿滴滴出行页面Demo
- JavaScript实现弹出浏览器的三种提示框:提示信息框、确认框和输入文本框
- 大蕉毕业三周年了,有话对你说 No.103
- 块设备驱动、bio理解
- windows与Linux实时传输数据,I01 物理隔离条件下Windows与Linux服务器的文件传输脚本...