目录
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.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<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.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<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.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<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.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 $@
测试结果 客户端: