🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:计算机网络
🌹往期回顾🌹: 【计算机网络】基于UDP进行socket编程——实现服务端与客户端业务
🔖流水不争,争的是滔滔不息
一、TCPsocket编程
TCP Socket 编程是一种基于传输控制协议(TCP)的网络通信方式,用于在客户端和服务器之间建立可靠的双向连接。TCP 提供面向连接、可靠的数据传输,适用于需要确保数据完整性和顺序的应用场景,如网页浏览、文件传输等。
1.1基本接口
1.1.1通用接口
创建套接字
int socket(int domain, int type, int protocol);
参数,IPV4:AF_INET,TCP:SOCK_STREAM,标志位一般为0。失败返回-1。
关闭套接字
int close(int fd);
发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
返回值是返回发送的字节数,第一个参数套接字,第二个参数buffer,第三个参数是buffer大小,第四个参数是标志位。
接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
返回值是返回接收到的字节数,第一个参数套接字,第二个参数buffer,第三个参数是buffer大小,第四个参数是标志位。
TCP中发送和接收,用write和read也行。
1.1.2服务器主要流程
绑定端口号和ip
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
绑定本地ip和端口
监听
int listen(int sockfd, int backlog);
监听端口,backlog表示等待连接的队列的最大长度。
accept,返回新的socket描述符用于通信
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
接收客户端的请求,返回一个新的socket描述符,原来的socket描述符用于监听,新的用来和客户端通信。
1.1.3客户端主要流程
connect
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
主动连接服务器端的socket。
1.2 接口理解
服务端
- bind是绑定本地主机的ip和端口号。
- listen是开始监听,把绑定好的socket变成监听状态,等待客户端来连接。打个比喻也就是说listen相当于“开门营业”让内核知道这个socket是服务端的,准备接电话了。backlog表示等待连接的队列的最大长度,“最多有多少个客户拍队”。
- accept是接收连接,从监听 socket 中拿出一个连接请求,生成一个新的 socket,用于和客户端通信。你可以理解成服务员开门迎客,把客户带到一个新的包间聊天。返回的新 socket 是专用于与这个客户端通信的,原来的 socket 继续监听下一个连接。
客户端
connect是客户端主动给服务端发送连接请求,去找服务端,可以理解为客户端给客户端打电话。
二、TCP实现客户端到服务器远程网络命令行操作
Tcpserver.hpp
#include "Common.hpp"
using namespace std;
using namespace LogModule;
using func_t =function<string(const string&,InetAddr&)>;
const static int defaultsockfd=-1;
const static int backlog=8;
class Tcpserver : public NoCopy
{
public:
Tcpserver(uint16_t port,func_t func)
:_port(port)
,_listensockfd(defaultsockfd)
,_sockfd(defaultsockfd)
,_isrunning(false)
,_func(func)
{
}
void Init()
{
_listensockfd=socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(_listensockfd<0)
{
LOG(LogLevel::FATAL)<<"socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO)<<"socket success";
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)); //绑定
if(n<0)
{
LOG(LogLevel::FATAL)<<"bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO)<<"bind sucess";
n=listen(_listensockfd,backlog); //监听
if(n<0)
{
LOG(LogLevel::FATAL)<<"listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO)<<"listen sucess";
}
void Server(int _sockfd,InetAddr& peer)
{
while(true)
{
char buffer[1024];
ssize_t n=read(_sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n]=0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
string echo_string=_func(buffer,peer);
write(_sockfd,echo_string.c_str(),echo_string.size());
}
else if(n==0)
{
LOG(LogLevel::DEBUG)<<"退出";
close(_sockfd);
break;
}
else
{
LOG(LogLevel::FATAL)<<"异常";
close(_sockfd);
break;
}
}
}
class ThreadDate
{
public:
ThreadDate(int fd,InetAddr& ad,Tcpserver* th)
:sockfd(fd)
,addr(ad)
,tsvr(th)
{
}
~ThreadDate()
{
}
int sockfd;
InetAddr addr;
Tcpserver* tsvr;
};
static void* Routine(void* args) //线程处理任务
{
pthread_detach(pthread_self());
ThreadDate* td=static_cast<ThreadDate*>(args);
td->tsvr->Server(td->sockfd,td->addr);
delete td;
return nullptr;
}
void Run()
{
_isrunning=true;
while(_isrunning)
{
struct sockaddr_in peer; //客户端(网络)传来
socklen_t len=sizeof(peer);
_sockfd=accept(_listensockfd,(struct sockaddr*)&peer,&len); //接收连接
if(_sockfd<0)
{
LOG(LogLevel::WARNING)<<"accept error";
continue;
}
LOG(LogLevel::INFO)<<"accept sucess";
InetAddr addr(peer); //转为主机序列
pthread_t tid; //每当一个客户端连接,就创建线程去服务客户端想要干的事
ThreadDate* td=new ThreadDate(_sockfd,addr,this);
pthread_create(&tid,nullptr,Routine,td); //真正创建线程的,把td就是那些信息传进去到Routine
}
_isrunning=false;
}
~Tcpserver()
{
}
private:
bool _isrunning;
int _listensockfd;
int _sockfd;
uint16_t _port;
func_t _func;
};
服务器初始化
void Init()
{
_listensockfd=socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(_listensockfd<0)
{
LOG(LogLevel::FATAL)<<"socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO)<<"socket success";
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)); //绑定
if(n<0)
{
LOG(LogLevel::FATAL)<<"bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO)<<"bind sucess";
n=listen(_listensockfd,backlog); //监听
if(n<0)
{
LOG(LogLevel::FATAL)<<"listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO)<<"listen sucess";
}
创建套接字,绑定本地ip和端口号,把socket搞成监听状态。
有客户端connect给服务端发送连接请求,开始执行
void Run()
{
_isrunning=true;
while(_isrunning)
{
struct sockaddr_in peer; //客户端(网络)传来
socklen_t len=sizeof(peer);
_sockfd=accept(_listensockfd,(struct sockaddr*)&peer,&len); //接收连接
if(_sockfd<0)
{
LOG(LogLevel::WARNING)<<"accept error";
continue;
}
LOG(LogLevel::INFO)<<"accept sucess";
InetAddr addr(peer); //转为主机序列
pthread_t tid; //每当一个客户端连接,就创建线程去服务客户端想要干的事
ThreadDate* td=new ThreadDate(_sockfd,addr,this);
pthread_create(&tid,nullptr,Routine,td); //真正创建线程的,把td就是那些信息传进去到Routine
}
_isrunning=false;
}
accept,服务端接收客户端的连接,创建新的套接字。这里采用来一个客户端信息创建一个线程的方式。ThreadDate* td=new ThreadDate(_sockfd,addr,this);
把连接信息打包成 ThreadDate 对象(this是当前类的对象)。为啥封装成结构体?pthread_create 的第四个参数只能传 void*,所以你封装成 ThreadDate*,传给线程函数后可以解包用。下面是ThreadDate的构造的代码
class ThreadDate
{
public:
ThreadDate(int fd,InetAddr& ad,Tcpserver* th)
:sockfd(fd)
,addr(ad)
,tsvr(th)
{
}
~ThreadDate()
{
}
int sockfd;
InetAddr addr;
Tcpserver* tsvr;
};
static void* Routine(void* args) //线程处理任务
{
pthread_detach(pthread_self());
ThreadDate* td=static_cast<ThreadDate*>(args);
td->tsvr->Server(td->sockfd,td->addr);
delete td;
return nullptr;
}
Routine 是线程处理函数;传进去的参数是 ThreadDate* 类型的 td。在Routine中线程分离,解包然后通过td调用server进入回调函数。
void Server(int _sockfd,InetAddr& peer)
{
while(true)
{
char buffer[1024];
ssize_t n=read(_sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n]=0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
string echo_string=_func(buffer,peer);
write(_sockfd,echo_string.c_str(),echo_string.size());
}
else if(n==0)
{
LOG(LogLevel::DEBUG)<<"退出";
close(_sockfd);
break;
}
else
{
LOG(LogLevel::FATAL)<<"异常";
close(_sockfd);
break;
}
}
}
从缓冲区中读数据,然后回调函数对客户端传来的信息进行解析,然后在返回去。
Tcpclient.cc
#include "Common.hpp"
using namespace std;
using namespace LogModule;
int main(int argc, char *argv[])
{
string server_ip = argv[1];
uint16_t server_port = stoi(argv[2]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success";
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
int n=connect(sockfd,(struct sockaddr*)&server,sizeof(server));
if(n<0)
{
LOG(LogLevel::FATAL)<<"connect error";
exit(CONNECT_ERR);
}
LOG(LogLevel::INFO)<<"connect sucess";
while(true)
{
string line;
cout<<"请输入"<<endl;
getline(cin,line);
write(sockfd,line.c_str(),line.size());
char buffer[1024];
int n=read(sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n]=0;
cout<<buffer<<endl;
}
}
close(sockfd);
return 0;
}
创建套接字,connect去连接服务器,连接上服务器,给服务器发信息,读服务器的应答。
Command.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <set>
#include "Command.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
using namespace LogModule;
class Command
{
public:
std::string Execute(const std::string &cmd, InetAddr &addr)
{
std::string who = addr.StringAddr();
// 2. 执行命令
FILE *fp = popen(cmd.c_str(), "r");
if(nullptr == fp)
{
return std::string("你要执行的命令不存在: ") + cmd;
}
std::string res;
char line[1024];
while(fgets(line, sizeof(line), fp))
{
res += line;
}
pclose(fp);
std::string result = who + "execute done, result is: \n" + res;
LOG(LogLevel::DEBUG) << result;
return result;
}
~Command()
{}
private:
};
执行命令的方法
Tcpserver.cc
#include "Tcpserver.hpp"
using namespace std;
using namespace LogModule;
int main(int argc,char* argv[])
{
uint16_t port=stoi(argv[1]);
Enable_Console_Log_Strategy(); // 启用控制台输出
Command cmd;
unique_ptr<Tcpserver> u=make_unique<Tcpserver>(port,
[&cmd](const string& buffer,InetAddr& peer){
return cmd.Execute(buffer,peer);
});
u->Init();
u->Run();
return 0;
}
lambda表达式,对服务端的回调函数进行处理,让客户端发来去的信息去执行命令行操作。
客户端到服务端远程命令行操作:源码