1.简单了解Tcp和Udp的区别
TCP是面向连接的,也就是需要连接的,使用Tcp协议的客户端进行通信的时候必须要先和服务器建立连接,这就要求服务器随时都要处于等待连接的状态,这也就是所谓的监听,才能进行通信,而Udp一旦绑定就自动建立连接,可以直接通信;

backlog是指服务器中全链接的个数,其实也就是排队个数;

还有就是Tcp是面向字节流进行通信的,Udp是面向数据报通信,两者的区别是面向数据报是不可能会发生读不完这种情况的,但是面向字节流可能会发生,所以我们使用recv和sendto来解决这个问题;
2.Tcp接收数据的方法


因为tcp接收数据前需要先建立连接,并且进行通信的socket和建立连接的监听套接字并不相同,所以在accept这里我们就可以直接设置多线程模式了,或者是直接引入线程池;
3.TCP服务器的实现
server.hpp
cpp
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/wait.h>
#include <pthread.h>
#include <functional>
#include "log.hpp"
#include "conmmen.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;
const int gloalsockfd = -1;
static const uint16_t gport = 8080;
#define BACKLOG 8
using handler_t =std::function<std::string (std::string)>;
class Tcpserver
{
using task_t =std::function<void()>;
struct ThreadData
{
int sockfd;
Tcpserver * self;
};
public:
Tcpserver(handler_t handler, uint16_t port = gport)
: _handler(handler),_listensockfd(gloalsockfd), _port(port), _isrunning(false)
{
}
void InitServer()
{
// 1.创建套接字
_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(loglevel::FATAL) << "socket:" << strerror(errno);
Die(SOCKET_ERR);
}
LOG(loglevel::INFO) << "create socket success";
// 2.绑定套接字到网络
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(gport);
local.sin_addr.s_addr=INADDR_ANY;
int n = ::bind(_listensockfd, cast(&local),sizeof(local));
if (n < 0)
{
LOG(loglevel::FATAL) << "bind:" << strerror(errno);
Die(BIND_ERR);
}
LOG(loglevel::INFO) << "bind success";
// 3.监听
n = ::listen(_listensockfd, BACKLOG);
if (n < 0)
{
LOG(loglevel::FATAL) << "listen:" << strerror(errno);
Die(LISTEN_ERR);
}
LOG(loglevel::INFO) << "listen success";
signal(SIGCHLD, SIG_IGN); //忽略子进程退出信号,os自动回收不再不要wait了
}
void HandlerRequest(int sockfd)
{
LOG(loglevel::INFO)<<"HandlerRequest ,sockfd is:"<<sockfd;
char inbuffer[4096];
while(true)
{
//ssize_t n = ::read(sockfd,inbuffer,sizeof(inbuffer)-1);//读取不完善
ssize_t n = ::recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);
if(n>0)
{
inbuffer[n]=0;
// std::string echo_str="server echo #";
// echo_str+=inbuffer;
std::string result_str = _handler(inbuffer); //回调方法出去调用完还是会回来的
// ::write(sockfd,echo_str.c_str(),echo_str.size()); 写入也是不完善的
::sendto(sockfd,result_str.c_str(),result_str.size(),0,nullptr,0 );
}
else if(n==0)
{
LOG(loglevel::INFO)<<"client close the connection"<< sockfd;
break;
}
else
{
break;
}
}
::close(sockfd);
}
static void * threadEntry(void * args)
{
pthread_detach(pthread_self());
ThreadData * data =(ThreadData*)args;
data->self->HandlerRequest(data->sockfd);
return nullptr;
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer); //对端地址的长度必须初始化好,不然会有随机值,参数就会出错
int sockfd = ::accept(_listensockfd, cast(&peer), &peerlen);
if (sockfd < 0)
{
LOG(loglevel::WARNING) << "accept:" << strerror(errno);
continue;
}
LOG(loglevel::INFO) << "accecpt success,sockfd:" << sockfd;
//version 1 多进程模式
// pid_t id=fork(); //创建子进程
// if(id == 0)
// {
// //关闭不需要的文件描述符,防止子进程误触发
// ::close(_listensockfd);
// if(fork() >0) exit(0); //子进程退出,孙子进程执行,因为子进程退出了,所以孙子进程成为了孤儿进程,会被系统收养,所以孙子进程也要退出,不然会造成僵尸进程
// //子进程处理请求
// HandlerRequest(sockfd);
// exit(0); //子进程退出,必须退出否则会造成内存泄露,因为子进程会拷贝父进程得文件描述符表
// }
// ::close(sockfd); //sockfd已经交给子进程了,父进程关闭自己不关心的文件描述符,防止误触发
// //子进程已经退出,父进程不再阻塞
// int rid =::waitpid(id,nullptr,0);
// if(id < 0)
// {
// LOG(loglevel::FATAL)<<"create fork error";
// }
//version 2 多线程模式
//新线程和主线程共享一张文件描述符表
// pthread_t tid ;
// ThreadData * data= new ThreadData; //sockfd是在栈上开辟的空间声明周期很短,int(sockfd)的意思是用sockfd初始化int类型的地址sockfdp;
// data->sockfd=sockfd;
// data->self=this;
// pthread_create(&tid,nullptr,threadEntry,data);
//version 3 线程池模式
task_t f=std::bind(&Tcpserver::HandlerRequest,this,sockfd);
ThreadPool<task_t>::getInstance()->Equeue([this,sockfd](){
this->HandlerRequest(sockfd);
});
}
}
void Stop()
{
_isrunning = false;
}
~Tcpserver()
{
}
private:
int _listensockfd;
bool _isrunning;
uint16_t _port;
handler_t _handler;
};
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char * argv[])
{
if(argc !=3)
{
std::cout<<"Usage:./client_tcp server_ip server_port"<<std::endl;
return 1;
}
std::string server_ip = argv[1];
int server_port = std::stoi(argv[2]);
int sockfd=::socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
std::cout<<"create sockfd error"<<std::endl;
return 2;
}
struct sockaddr_in server_addr;
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=::htons(server_port);
server_addr.sin_addr.s_addr=::inet_addr(server_ip.c_str());
int n = ::connect(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr)); //connect之后会自动bind
if(n<0)
{
std::cout<<"connect error"<<std::endl;
return 3;
}
std::string message;
while(true)
{
char inbuffer[1024];
std::cout<<"input message:";
std::getline(std::cin,message);
n= ::write(sockfd,message.c_str(),message.size());
if(n>0)
{
//读的是服务器发回来的数据
int m = ::read(sockfd,inbuffer,sizeof(inbuffer));
if(m>0)
{
inbuffer[m]=0;
std::cout<<"receive message from server:"<<inbuffer<<std::endl;
}
else
break;
}
else
break;
}
::close(sockfd);
return 0;
}
cpp
#include "server.hpp"
#include "commendExcu.hpp"
#include <memory>
int main()
{
ENABLE_CONSOLE_LOG();
commendExcu commend; //实例化命令执行类
std::unique_ptr<Tcpserver> tsvr=std::make_unique<Tcpserver>([&commend](std::string cmdstr){
return commend.execute(cmdstr);
}); //用智能指针初始化服务器
tsvr -> InitServer(); //初始化服务器
tsvr -> Start(); //启动服务器
return 0;
}
commentExct.hpp ->实现远程执行命令功能
cpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
class commendExcu
{
public:
std::string execute(std::string cmdstr)
{
FILE * fp = ::popen(cmdstr.c_str(), "r");
if(fp == nullptr)
{
return std::string("Failed to execute command");
}
char buffer[1024];
std::string result;
while(true)
{
char * ret = ::fgets(buffer, sizeof(buffer), fp);
if(!ret) break;
result += ret;
}
pclose(fp);
return result.empty()?std::string("done"):result;
}
};
本篇博客可以搭配"每日遗忘"专栏中的二三篇文章来看;