概述
文章目录
- 1 由来
- 2 TCP网络编程注意事项
- TCP_SERVER封装
- 3 TCP简单示例
- 3.1 discard
- 3.2 daytime
- 3.3 time
- 3.4 echo
- 3.5 chargen
- 3.6 总述
1 由来
socket编程时,使用并不难,但容易忽略问题,出现错误,因此封装后以此来降低开发难度;
2 TCP网络编程注意事项
【TCP网络编程最本质的是处理三个半事件】
- 【1】连接的建立,包括服务端accept新连接和客户端成功发起connect;TCP连接一旦建立,cs是平等的,可以各自收发数据;
- 【2】连接的断开,包括主动断开
close、shutdown
和被动断开read返回0
; - 【3】消息到达,文件描述符可读,是最为重要的一个事件;
- 【3.5】消息发送完毕,这算半个。低流量的服务,可不关心该事件;“发送完毕”是指将数据写入操作系统的缓冲区,将由TCP协议栈负责数据的发送与重传,不代表对方已经收到数据;
【3.5中有许多细节要注意】
- 【若要主动关闭连接,如何保证对方已收到全部数据?】
- 【若应用层有缓冲,如何保证先发送完缓冲区中的数据,在断开连接?】
- 【若主动发起连接,但对方主动拒绝,如何定期重试?】
- 【电平触发,需要什么时候关注EPOLLOUT事件?会不会造成busy-loop】
- 【边沿触发,如何防止漏读造成饥饿?】
- 【epoll一定比poll快吗】
- 【在非阻塞网络中,为什么要使用应用层发送缓冲区?若有40Kb数据要发送,但系统的TCP发送缓冲区只剩25K空间,剩下的15KB咋办?】
- 在等待OS buf可用,回阻塞当前线程,由于不清楚对方何时收到并读取数据,故网络库需要将15KB缓存,放到TCP链接的应用层发送缓冲区,等待socket可写立刻发送数据,该发送操作不会阻塞;若还有数据要发送,则追加到缓冲区;
- 【非阻塞网络中,为什么用应用层接收缓冲区?若一次读到的数据不够一个完成包,则已读到的包是否需要暂存,等数据都收到在一并处理?】
- 在非阻塞网络中,如何设计并使用缓冲区?需要考虑减少系统调用,数据一次读多点,且需要考虑内存占用;
TCP_SERVER封装
【主要数据成员】
- m_socks:存储已连接的套接字;
- m_worker:工作线程;
- m_acceptWorker:用于处理连接的线程;
- m_recvTimeout:接收超时时间;
- m_name:服务器类型;
- m_stop:服务是否停止;
【主要成员函数】
- 构造函数:默认初始化两个工作线程;
- 析构函数:将所有套接字都关闭,并清空套接字列表,避免引用计数无法释放;
- bind:提供单个地址或多个地址绑定、监听;
- start:启动TcpServer开始accept客户端;
- stop:取消套接字的所有事件、并关闭;
- handleClient:为虚函数,具体实现交给子类,当连接后,对如何响应客户端,如何处理客户端的请求;
- startAccept:为虚函数,处理连接客户端;
class TcpServer : public std::enable_shared_from_this<TcpServer> , Noncopyable{
public:
typedef std::shared_ptr<TcpServer> ptr;
TcpServer(sylar::IOManager* woker = sylar::IOManager::GetThis(),
sylar::IOManager* accept_woker = sylar::IOManager::GetThis());
virtual ~TcpServer();
virtual bool bind(sylar::Address::ptr addr);
virtual bool bind(const std::vector<Address::ptr>& addrs
,std::vector<Address::ptr>& fails);
virtual bool start();
virtual void stop();
uint64_t getReadTimeout() const { return m_recvTimeout; }
std::string getName() const { return m_name; }
void setReadTimeout(uint64_t v) { m_recvTimeout = v; }
void setName(const std::string& v) { m_name = v; }
bool isStop() const { return m_stop; }
protected:
virtual void handleClient(Socket::ptr client);
virtual void startAccept(Socket::ptr sock);
private:
std::vector<Socket::ptr> m_socks;
IOManager* m_worker;
IOManager* m_acceptWorker;
uint64_t m_recvTimeout;
std::string m_name;
bool m_stop;
};
3 TCP简单示例
3.1 discard
- 丢弃所有收到的数据,为最简单的TCP协议;
#ifndef __DISCARD_SERVER_H__
#define __DISCARD_SERVER_H__
#include "sylar/tcp_server.h"
namespace sylar {
namespace http {
class Discard: public TcpServer {
public:
typedef std::shared_ptr<Discard> ptr;
Discard(sylar::IOManager* worker = sylar::IOManager::GetThis()
,sylar::IOManager* accept_worker = sylar::IOManager::GetThis());
protected:
virtual void handleClient(Socket::ptr client) override;
};
}
}
#endif
=============================cpp=================================
static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");
Discard::Discard(sylar::IOManager* worker, sylar::IOManager* accept_worker)
: TcpServer(worker, accept_worker){
setName("discard");
}
void Discard::handleClient(Socket::ptr client) {
char buf[1024] = {0};
do {
int rt = client->recv(buf, 1024);
SYLAR_LOG_INFO(g_logger) << getName() << " recv size: " << rt;
if(!client->isConnected() || !strncmp(buf, "q", 1)) {
SYLAR_LOG_INFO(g_logger) << getName() << "close";
break;
}
}
3.2 daytime
- 短链接协议,发生完当前时间后,由服务端主动断开连接;
class DayTime: public TcpServer {
public:
typedef std::shared_ptr<DayTime> ptr;
DayTime(sylar::IOManager* worker = sylar::IOManager::GetThis()
,sylar::IOManager* accept_worker = sylar::IOManager::GetThis());
void getTime();
protected:
virtual void handleClient(Socket::ptr client) override;
private:
struct tm m_tm;
char m_time[128];
};
static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");
DayTime::DayTime(sylar::IOManager* worker, sylar::IOManager* accept_worker)
: TcpServer(worker, accept_worker){
setName("daytime Server");
}
void DayTime::getTime() {
time_t t = time(0);
localtime_r(&t, &m_tm);
strftime(m_time, sizeof(m_time), "%Y-%m-%d %H:%M:%S", &m_tm);
m_time[sizeof(m_time)] = 'n';
}
void DayTime::handleClient(Socket::ptr client) {
if(client->isConnected()) {
getTime();
client->send(m_time, sizeof(m_time)+1);
SYLAR_LOG_INFO(g_logger) << getName() << " send msg: " << m_time;
}
}
3.3 time
- 类似daytime,返回32bit整数,发送时间后,关闭连接;
time客户端
3.4 echo
- 双向协议,服务端把客户端的消息返回;
- 当读到数据即发送,避免客户端恶意发送,导致服务端内存暴涨;
void Echo::handleClient(Socket::ptr client) {
do {
char buf[1024] = {0};
int rt = client->recv(buf, sizeof(buf));
SYLAR_LOG_INFO(g_logger) << getName() << " recv size: " << rt
<< "content: " << buf;
if(!client->isConnected() || !strncmp(buf, "q", 1)) {
SYLAR_LOG_INFO(g_logger) << getName() << "close";
break;
}
client->send(buf, sizeof(buf));
} while(true);
3.5 chargen
- 该协议只发送数据,不接收数据,发送数据不能快于客户端接收速度,还需要用到流量统计功能,定时器功能 ;
void Chargen::handleClient(Socket::ptr client) {
char buf[1024] = "Hellon";
do {
client->send(buf, sizeof(buf));
SYLAR_LOG_INFO(g_logger) << getName() << " send msg: " << buf;
if(!client->isConnected() || !strncmp(buf, "q", 1)) {
SYLAR_LOG_INFO(g_logger) << getName() << "close";
break;
}
} while(true);
}
3.6 总述
- 以上程序都用到EventLoop,用于注册和分发IO事件;
- 上述使用Reactor模型的复用能力,让一个单线程同时具备多个网络服务功能;
具体源码
查看此处
最后
以上就是隐形流沙为你收集整理的【Linux多线程服务端编程】| 【06】TCP网络编程及五个应用示例的全部内容,希望文章能够帮你解决【Linux多线程服务端编程】| 【06】TCP网络编程及五个应用示例所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复