深入解析:Socket编程TCP
目录
TCP ( Transmission Control Protocol 传输控制协议)
传输层协议
有连接(例如生活中双方打电话,此时首先双方确认对方是否听得见,此时就是在建立连接)
可靠传输(如果丢包,再次发送,维持可靠传输,需要做更多工作,复杂,占有资源多)
⾯向字节流(水流,连续的,如果要接水,自行分配如何接)
1.TCP⽹络编程
1.1 服务端初始化实现
这里TCP是有连接的,对服务端要进行监听,例如生活中去餐厅吃饭,此时就需要一个前台来持续等待新客户到来,此时前台就处于监听状态,而TCP是有连接的,因此此时需要把自身设为listen状态,等待别人连接,监听函数如下
说明:
使得TCP服务器能够监听来自客户端的连接请求,它为服务器端接收客户端连接做准备。
#include
int listen(int sockfd, int backlog);
参数:
sockfd 是通过socket函数创建的套接字文件描述符。
backlog 参数指定了队列中最多可以容纳多少等待处理的连接
#include"Common.hpp"//错误码类
#include"Log.hpp"//日志类
#include"inerAddr.hpp"//地址转换类
class TcpServer:public NoCopy//禁止对服务器拷贝
{
public:
TcpServer(uint16_t port,bool isRun=true)
:_port(port) ,
_isRun(isRun)
{}
void Init()
{
//创建套接字
int _sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::ERROR) << "create socket failed";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "create socket success";
//绑定地址
inerAddr lock(_port);
int n=bind(_sockfd, (struct sockaddr*)&lock.NetAddr(), sizeof(lock.NetAddr()));
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind socket failed";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind socket success";
//监听
int n=listen(_sockfd, 8);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen socket failed";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen socket success";
}
void Run()
{
}
~TcpServer()
{}
private:
uint16_t _port;
bool _isRun;
};
#include"inerAddr.hpp"
#pragma once
#include
#include
#include
#include
#include
#include
#include
//获取IP地址和端口号类
class inerAddr
{
public:
inerAddr(struct sockaddr_in &addr) : _addr(addr)
{
//网络转主机字节序
_port = ntohs(_addr.sin_port);
//_ip = inet_ntoa(_addr.sin_addr);
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
_ip = ipbuffer;
}
inerAddr(const std::string &ip, uint16_t port): _ip(ip), _port(port)
{
//主机字节序转网络字节序
_addr.sin_port = htons(_port);
//_addr.sin_addr.s_addr = inet_addr(_ip.c_str());
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
inerAddr(uint16_t port) :_port(port),_ip()
{
// 主机转网络
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_addr.s_addr = INADDR_ANY;
_addr.sin_port = htons(_port);
}
uint16_t getPort() const { return _port; }
std::string getIP() const { return _ip; }
const struct sockaddr_in &NetAddr() { return _addr; }
~inerAddr() {}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
1.2 服务端运行
对服务端初始化后,此时socket就可以进行建立连接,首先就要获取连接,函数如下
#include
#include
说明:accept()等待客户端连接,用来与s参数建立连接
int accept(int s, struct sockaddr * addr, int * addrlen);
参数:
s:需要建立连接的文件描述符
addr:输出型参数,保存发起连接请求的那个客户端的IP和端口
addrlen:输出型参数,表示第二个参数addr的大小
返回值:成功则返回新的socket 处理代码, 失败返回-1,。
注意:
accept获取的链接直接从内核获取的,建立连接的过程与accept无关
参数s只负责获取连接--》listensockfd,
accept返回的socket才是用来进行处理服务和数据的
对于TCP来说处理用户数据时,和文件读写的方式相同
void Service(int sockfd, inerAddr &client)
{
//处理客户端请求
char recvbuf[1024] = {0};
while(true)
{
//接收数据
ssize_t n=read(sockfd, recvbuf, sizeof(recvbuf)-1);
if (n > 0)
{
recvbuf[n] = '\0';
LOG(LogLevel::INFO) << "recv data from client: " << recvbuf;
//发送数据
std::string echo_string="echo#";
echo_string+=recvbuf;
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0)
{
//客户端关闭连接
LOG(LogLevel::INFO) << "client close connection";
close(sockfd);
break;
}
else
{
//出错
LOG(LogLevel::ERROR) << "recv data failed";
close(sockfd);
break;
}
}
}
void Run()
{
_isRun=true;
while (_isRun)
{
//获取连接,等待客户端连接
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
//如果没有客户端连接,则阻塞等待
int sockfd = accept(_listensockfd, (struct sockaddr*)&client_addr, &client_addr_len);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept socket failed";
continue;
}
inerAddr client(client_addr);
LOG(LogLevel::INFO) << "accept socket success, client ip: " << client.getIP() << ", port: " << client.getPort();
//处理客户端请求
Service(sockfd, client);//测试
}
_isRun=false;
}
1.3 客户端初始化
对于TCP的客户端,创建套接字和UDP一样,同样也不需要显示bind,同时客户端也不需要监听和获取连接,客户端应直接向服务端发送连接请求
发送连接函数如下
说明:
向服务端发起连接请求
int connect(int sockcd, const struct sockaddr *addr, int addrlen);
参数:
sockcd:套接字
addr:用于指定所要连接的服务器的地址
addrlen:为addr变量的大小
返回值:
成功返回0,失败返回-1。
int main(int argc,char* argv[])
{
if(argc!=3)
{
std::cout<<"Usage: "<
1.4 客户端运行
TCP客户端运行其实就和文件操作类似,调用系统的文件接口,进行使用
while (true)
{
std::string msg;
std::cout<<"Please input:#";
std::getline(std::cin,msg);
write(sockfd,msg.c_str(),msg.size());
char buf[1024];
int n=read(sockfd,buf,sizeof(buf)-1);
if(n>0)
{
buf[n]='\0';
std::cout<<"server echo#"<
2. 服务端多进程运行版本
对于服务端,不可能只接受一个客户端,因此需要创建多进程来进行多用户访问,实现如下
void Run()
{
_isRun=true;
while (_isRun)
{
//获取连接,等待客户端连接
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
//如果没有客户端连接,则阻塞等待
int sockfd = accept(_listensockfd, (struct sockaddr*)&client_addr, &client_addr_len);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept socket failed";
continue;
}
inerAddr client(client_addr);
LOG(LogLevel::INFO) << "accept socket success, client ip: " << client.getIP() << ", port: " << client.getPort();
//创建子进程
pid_t id=fork();
if (id < 0)
{
LOG(LogLevel::ERROR) << "fork process failed";
exit(FORK_ERR);
}
else if (id == 0)
{
//子进程处理客户端请求
//关闭监听套接字,这是由于子进程不需要监听套接字
//不关也可以但是会有风险
close(_listensockfd);
if(fork()>0)
{
exit(OK);//子进程退出
}
//此时孙子进程来处理,由于子进程退出,变成了孤儿进程,由系统回收
Service(sockfd, client);
exit(OK);
}
else
{
//父进程继续等待子进程结束
close(sockfd);//关闭连接套接字
waitpid(id, nullptr, 0);//此时由于子进程退出,父进程不用在这里阻塞,继续监听
}
}
_isRun=false;
}
3. 服务端多线程运行版本
class ThreadData//内部类,为了传参给任务函数
{
public:
ThreadData(TcpServer *server, int sockfd, inerAddr& client)
:server(server), sockfd(sockfd), client(client)
{}
public:
TcpServer *server;//能够访问类外函数
//下面是传参用的
int sockfd;
inerAddr client;
};
static void *Roetine(void*args)//任务函数
{
ThreadData *data=static_cast(args);
data->server->Service(data->sockfd, data->client);
delete data;
return nullptr;
}
void Run()
{
_isRun=true;
while (_isRun)
{
//获取连接,等待客户端连接
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
//如果没有客户端连接,则阻塞等待
int sockfd = accept(_listensockfd, (struct sockaddr*)&client_addr, &client_addr_len);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept socket failed";
continue;
}
inerAddr client(client_addr);
LOG(LogLevel::INFO) << "accept socket success, client ip: " << client.getIP() << ", port: " << client.getPort();
//创建线程
ThreadData *data=new ThreadData(this, sockfd, client);
pthread_t tid;
pthread_create(&tid, nullptr, Roetine, data);
}
_isRun=false;
}
4. 服务端进程池运行版本
对于用户要进行长服务: 多进程多线程比较合适,但是短服务:线程池就比较好了,首先如下是进程池封装代码及线程池相关的封装代码
4.1 线程池封装代码
#pragma once
#include
#include "Mutex.hpp" //互斥锁
#include "code.hpp" //线程
#include "Cond.hpp" //条件变量
#include "Log.hpp" //日志
#include
#include
#include
namespace ThreanPoolModuls
{
using namespace chuxin;
using namespace MutexMudule;
using namespace LogModuls;
static const int gnum = 5; // 线程池中线程数量
template
class ThreadPool
{
private:
void WakeUpAllThread()
{
// 加锁
Condition lock(_mutex);
// 通知所有线程
if (_waitnum)
_cond.Broadcast();
}
void WakeUpOne()
{
_cond.Cond_signal_wait();
}
void HandlerTask()
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name)); // 获取线程名
while (true)
{
T t; // 任务
{
Condition lock(_mutex); // 互斥锁
while (_task_.empty() && _isStarted) // 任务队列为空
{
_waitnum++; // 等待线程数量加1
_cond.Wait(_mutex); // 条件变量等待
_waitnum--; // 等待线程数量减1
}
if (!_isStarted && _task_.empty())
{
break;
}
t = _task_.front(); // 取出任务
_task_.pop();
}
t(); // 执行任务
}
}
ThreadPool(int num = gnum, bool isStart = false, int waitnum = 0)
: _num(num),
_isStarted(isStart),
_waitnum(waitnum)
{
for (int i = 0; i < num; i++)
{
_thread.emplace_back([this]()
{
HandlerTask(); // 处理任务
});
}
}
public:
void Start()
{
if (_isStarted)
{
return;
}
_isStarted = true;
for (auto &t : _thread)
{
t.Start();
}
}
void Join() // 等待所有线程退出
{
for (auto &t : _thread)
{
t.Join();
}
}
void Stop()
{
if (!_isStarted)
{
return;
}
_isStarted = false;
// 首先唤醒所有线程,让它们退出,如果有任务在队列中,则处理完后再退出
WakeUpAllThread();
}
bool Enqueue(const T &in) // 提供给外部线程调用,将任务放入队列
{
if (_isStarted)
{
Condition lock(_mutex);
_task_.push(in);
if (_waitnum == _thread.size())
WakeUpOne(); // 唤醒一个线程
return true;
}
return false;
}
private:
//对拷贝构造函数和赋值运算符进行删除
ThreadPool(const ThreadPool &) = delete;
ThreadPool &operator=(const ThreadPool &) = delete;
public:
static ThreadPool *GetInstance()
{
if (inc == nullptr)
{
Condition lockguard(_lock);
if (inc == nullptr)
{
inc = new ThreadPool();
inc->Start();
}
}
return inc;
}
private:
std::vector _thread; // 线程池
int _num; // 线程数量
std::queue _task_; // 任务队列
Mutex _mutex; // 互斥锁
Cond _cond; // 条件变量
bool _isStarted; // 线程池是否启动
int _waitnum; // 等待任务的线程数量
static ThreadPool *inc; // 单例指针
static Mutex _lock;
};
//静态变量类外初始化
template
ThreadPool *ThreadPool::inc = nullptr;// 线程池实例
template
Mutex ThreadPool::_lock;// 互斥锁
}
4.2 线程封装代码
#ifndef _THREAD_H_
#define _THREAD_H_
#pragma once
#include
#include
#include
#include
#include
#include
#include
namespace chuxin
{
static uint32_t num = 1;
class Thread
{
using func_t = std::function;
private:
void Running()
{
_running = true;
}
void Detached()
{
_detached = true;
}
static void *Routine(void *arg)
{
Thread *p = static_cast(arg);
p->Running();
if (p->_detached)
p->Detach();
pthread_setname_np(p->_tid, p->_name.c_str());
p->_func();
return nullptr;
}
public:
Thread(func_t func)
: _tid(0), _running(false), _detached(false), _func(func), _res(nullptr)
{
_name = "thread-" + std::to_string(num++);
}
~Thread()
{
}
void Detach()
{
if (_detached)
return;
if (_running)
pthread_detach(_tid);
Detached();
}
bool Start()
{
if (_running)
return false;
int n = pthread_create(&_tid, NULL, Routine, this); // 这里this
if (n != 0)
{
return false;
}
else
{
return true;
}
}
bool Stop()
{
if (_running)
{
int n = pthread_cancel(_tid);
if (n != 0)
{
return false;
}
else
{
_running = false;
return true;
}
}
return false;
}
void Join()
{
if (_detached)
{
return;
}
int n = pthread_join(_tid, &_res);
if (n != 0)
{
}
else
{
}
}
pthread_t Id()
{
return _tid;
}
private:
pthread_t _tid;
std::string _name;
bool _running;
bool _detached;
void *_res;
func_t _func;
};
}
#endif
4.3 互斥锁封装代码
#pragma once
#include
#include
namespace MutexMudule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&m_mutex, NULL);
}
void lock()
{
pthread_mutex_lock(&m_mutex);
}
void unlock()
{
pthread_mutex_unlock(&m_mutex);
}
pthread_mutex_t* getMutex()
{
return &m_mutex;
}
~Mutex()
{
pthread_mutex_destroy(&m_mutex);
}
private:
pthread_mutex_t m_mutex;
};
class Condition
{
public:
Condition(Mutex& mutex)
:m_mutex(mutex)
{
m_mutex.lock();
}
~Condition()
{
m_mutex.unlock();
}
private:
Mutex &m_mutex;
};
}
4.4 条件变量封装代码
#include
#include "Mutex.hpp" //互斥量的封装
namespace chuxin
{
using namespace MutexMudule; //使用互斥量模块
class Cond
{
public:
Cond()
{
pthread_cond_init(&_cond, NULL);
}
void Cond_signal_wait()
{
pthread_cond_signal(&_cond);
}
void Broadcast()
{
pthread_cond_broadcast(&_cond);
}
void Wait(Mutex &mutex)
{
pthread_cond_wait(&_cond, mutex.getMutex());
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
}
4.5 服务端进程池运行代码
void Run()
{
_isRun=true;
while (_isRun)
{
//获取连接,等待客户端连接
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
//如果没有客户端连接,则阻塞等待
int sockfd = accept(_listensockfd, (struct sockaddr*)&client_addr, &client_addr_len);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept socket failed";
continue;
}
inerAddr client(client_addr);
LOG(LogLevel::INFO) << "accept socket success, client ip: " << client.getIP() << ", port: " << client.getPort();
//创建线程池,并把任务放入任务队列中
// using task_t=std::function;
ThreadPool::GetInstance()->Enqueue([this, sockfd, &client]() {
this->Service(sockfd, client);
});
}
_isRun=false;
}
5. 基于TCP⽹络编程实现字典翻译的功能
5.1 引入字典文件,类
#include
#include
#include
#include"Log.hpp"//引入日志模块
#include"inerAddr.hpp"
using namespace LogModuls;
const std::string defaultdict = "./dict.txt";//默认字典路径
const std::string sep = ": ";//字典文件中每行的分隔符
class Dict {
public:
Dict(const std::string &path = defaultdict) : _dict_path(path)
{}
bool LoadDict()//加载字典
{
std::ifstream in(_dict_path);//打开字典文件
if(!in.is_open())
{
LOG(LogLevel::ERROR) << "Failed to open dict file: " << _dict_path;
return false;
}
std::string line;
while(std::getline(in,line))
{
auto pos = line.find(sep);
if(pos == std::string::npos)
{
LOG(LogLevel::WARN) << "解析 " << line<<" 失败";
continue;
}
std::string english = line.substr(0, pos);
std::string chinese = line.substr(pos + sep.size());
if(english.empty() || chinese.empty())
{
LOG(LogLevel::WARN) << "没有有效内容" << line;
continue;
}
_dict_map.insert(std::make_pair(english, chinese));
LOG(LogLevel::INFO) << "加载 " << english << " -> " << chinese;
}
in.close();
return true;
}
std::string Translate(const std::string &english,inerAddr &client) //翻译英文
{
auto it = _dict_map.find(english);
if(it == _dict_map.end())
{
LOG(LogLevel::DEBUG) <<"["<< client.getIP() << ":" << client.getPort() <<"]" << " 没有找到 " ;
return "None";
}
//顺便记录一下客户端的IP和端口
LOG(LogLevel::DEBUG) <<"["<< client.getIP() << ":" << client.getPort() <<"]" << " 请求翻译 " << english << " -> " << it->second;
return it->second;
}
~Dict()
{}
private:
std::string _dict_path;//字典的路径
std::unordered_map _dict_map;//字典的map,映射对应的翻译
};
5.2 代码实现
server.hpp
#include"Common.hpp"//错误码类
#include"Log.hpp"//日志类
#include"inerAddr.hpp"//地址转换类
#include
#include
using namespace LogModuls;
using func_t = std::function;
class TcpServer:public NoCopy//禁止对服务器拷贝
{
public:
TcpServer(uint16_t port,bool isRun=true)
:_port(port) ,
_isRun(isRun)
{}
void Init()
{
//创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::ERROR) << "create socket failed";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "create socket success";
//绑定地址
inerAddr lock(_port);
int n=bind(_listensockfd, (struct sockaddr*)&lock.NetAddr(), sizeof(lock.NetAddr()));
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind socket failed";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind socket success";
//监听
int n=listen(_listensockfd, 8);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen socket failed";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen socket success";
}
void Service(int sockfd, inerAddr &client)
{
//处理客户端请求
char recvbuf[1024] = {0};
while(true)
{
//接收数据
ssize_t n=read(sockfd, recvbuf, sizeof(recvbuf)-1);
if (n > 0)
{
recvbuf[n] = '\0';
LOG(LogLevel::INFO) << "recv data from client: " << recvbuf;
//发送数据
std::string echo_string = _func(recvbuf, client);
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0)
{
//客户端关闭连接
LOG(LogLevel::INFO) << "client close connection";
close(sockfd);
break;
}
else
{
//出错
LOG(LogLevel::ERROR) << "recv data failed";
close(sockfd);
break;
}
}
}
class ThreadData//为了传参个任务函数
{
public:
ThreadData(TcpServer *server, int sockfd, inerAddr& client)
:server(server), sockfd(sockfd), client(client)
{}
public:
TcpServer *server;//能够访问类外函数
//下面是传参用的
int sockfd;
inerAddr client;
};
static void *Roetine(void*args)//任务函数
{
ThreadData *data=static_cast(args);
data->server->Service(data->sockfd, data->client);
delete data;
return nullptr;
}
void Run()
{
_isRun=true;
while (_isRun)
{
//获取连接,等待客户端连接
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
//如果没有客户端连接,则阻塞等待
int sockfd = accept(_listensockfd, (struct sockaddr*)&client_addr, &client_addr_len);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "accept socket failed";
continue;
}
inerAddr client(client_addr);
LOG(LogLevel::INFO) << "accept socket success, client ip: " << client.getIP() << ", port: " << client.getPort();
//创建线程
ThreadData *data=new ThreadData(this, sockfd, client);
pthread_t tid;
pthread_create(&tid, nullptr, Roetine, data);
}
_isRun=false;
}
~TcpServer()
{}
private:
uint16_t _port;
int _listensockfd;//监听套接字
bool _isRun;
func_t _func; // 设置回调处理
};
server.cc
#include "TcpServer.hpp"
#include"Common.hpp"
#include"Dict.hpp"
void Usage(char* name)
{
std::cerr<<"Usage: "<server=std::make_unique(port,[&dict](const std::string& msg, inerAddr&addr){
//处理消息
dict.Translate(msg,addr);
});
server->Init();
server->Run();
return 0;
}
6. 基于TCP⽹络编程实现远程命令执⾏
6.1 命令类
#pragma once
#include
#include
#include
#include
#include "Command.hpp"
#include "inerAddr.hpp"
#include "Log.hpp"
using namespace LogModuls;
class Command
{
public:
Command()
{
//预设命令
//注意不要加入rm -rf这类命令,不然客户可能会误删本机文件
_Commands.insert("ls");
_Commands.insert("pwd");
_Commands.insert("ls -l");
_Commands.insert("touch haha.txt");
_Commands.insert("who");
_Commands.insert("whoami");
}
std::string Execute(const std::string& cmd, inerAddr& addr)
{
if(_Commands.find(cmd) == _Commands.end())
{
LOG(LogLevel::ERROR) << "Command not found: " << cmd;
return "Command not found: " + cmd;
}
std::string who=addr.getIP()+":"+std::to_string(addr.getPort());
//执行命令
FILE* fp=popen(cmd.c_str(),"r");
if(fp==NULL)
{
LOG(LogLevel::ERROR) << "Execute command failed: " << cmd;
return "Execute command failed: " + cmd;
}
char buffer[1024];
std::string res;
while(fgets(buffer,sizeof(buffer),fp)!=NULL)
{
res+=buffer;
}
pclose(fp);
std::string result = who + "execute done, result is: \n" + res;
LOG(LogLevel::DEBUG) << result;
return result;
}
~Command()
{}
private:
std::set _Commands; // 管理命令
};
6.2 代码实现
此处服务端的代码的实现和字典类似,只有把外部的执行任务换为执行命令的任务就行了
#include "TcpServer.hpp"
#include"Common.hpp"
#include"Command.hpp"
void Usage(char* name)
{
std::cerr<<"Usage: "< server = std::make_unique(port,
std::bind(&Command::Execute, &cmd, std::placeholders::_1, std::placeholders::_2));
// std::unique_ptr server = std::make_unique(port, [&cmd](const std::string &command, InetAddr &addr)
// { return cmd.Execute(command, addr); });
server->Init();
server->Run();
return 0;
}
浙公网安备 33010602011771号