如何申请一个网站 新网,mip织梦手机网站模板,网站开发注册个体工商,上海设计网站公司tcp_socket 一、tcp_server与udp_server一样的部分二、listen接口#xff08;监听#xff09;三、accept接收套接字1、为什么还要多一个套接字#xff08;明明已经有了个socket套接字文件了#xff0c;为什么要多一个accept套接字文件#xff1f;#xff09;2、底层拿到新… tcp_socket 一、tcp_server与udp_server一样的部分二、listen接口监听三、accept接收套接字1、为什么还要多一个套接字明明已经有了个socket套接字文件了为什么要多一个accept套接字文件2、底层拿到新连接并根据连接进行通信3、类比理解监听套接字和连接套接字的区别 四、服务端提供服务向客户端回消息五、tcp_client客户端编写1、框架2、客户端卡退了服务端怎么处理(read返回值为0)3、一个有趣的现象--两个一样的客户端去连接客户端单进程服务4、方法1子进程关listensock父进程关sockfd5、处理waitpid问题孙子进程处理机制或者signal忽略信号6、方法2多线程版本7、方法3线程池版本1线程池代码ThreadPool.hpp2任务代码Task.hpp3代码改进4结果 六、服务端翻译小程序七、进化版出现错误的细节问题1、向一个已经关闭的文件描述符的文件中进行写入读端已经关掉了写端继续写OS会把客户端进程杀掉2、重连 八、在线翻译服务重连九、地址复用十、守护进程介绍十一、tcp的通信原理 一、tcp_server与udp_server一样的部分
#pragma once#include iostream
#include cstdlib
#include cstring
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include sys/types.h
#include Log.hppLog lg;const int defaultfd -1;
const std::string defaultip 0.0.0.0;
const uint16_t defaultport 8080;enum
{SocketERR2,BINDERR3
};class TcpServer
{
public:TcpServer(const uint16_t port defaultport, const std::string ip defaultip): _socketfd(defaultfd), _port(port), _ip(ip){}void InitServer(){_socketfd socket(AF_INET, SOCK_STREAM, 0);if (_socketfd 0){lg(Fatal, create socket err, errno: %d, errst: %s, errno, strerror(errno));exit(SocketERR);}lg(Info, create socket successful, sockfd:%d, _socketfd);struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(_port);inet_aton(_ip.c_str(), (local.sin_addr));int n bind(_socketfd, (struct sockaddr*)local, sizeof(local));if (n 0){lg(Fatal, bind socket err, errno: %d, errst: %s, errno, strerror(errno));exit(BINDERR);}lg(Info, bind sucessful);// 监听套接字 -- 因为tcp是要等待别人来连接的所以要有个监听套接字进行监听等待别人来连接}void RunServer(){}~TcpServer(){}
private:int _socketfd;uint16_t _port;std::string _ip;
};这里我们用inet_aton将本地序列转化成为网络序列。
二、listen接口监听 启动服务器状态是listen
三、accept接收套接字 1、为什么还要多一个套接字明明已经有了个socket套接字文件了为什么要多一个accept套接字文件 各司其职呗~~_socketfd是接客用的面对随时来的新连接先接客到底层去而accept的返回值才真正是服务的套接字也就是I/O端口进行服务的从底层拿出来进行服务所以_socketfd只有一个而accept返回值却有多个一个接客多个服务员
修改一下socket套接字为listen套接字
2、底层拿到新连接并根据连接进行通信 3、类比理解监听套接字和连接套接字的区别
相当于我们去一家饭店监听套接字是外面迎客的人把人都迎进来里面肯定有服务员吧服务员就是连接套接字服务员去服务迎客的人去迎客。我们目前实现的是迎客连接一条龙也就是来一群人一个个迎客再进来一个个服务太慢了所以我们的目标是实现迎客和服务两条线来了人和迎客互不耽误两者并发式的运行就需要我们用多线程版本但是会出现很多问题我们在下面一一进行讲解。
四、服务端提供服务向客户端回消息
那我们就写一个Server函数进行封装来将服务端进行提供服务我们传参传accept从底层拿到的套接字和拿到的套接字的ip地址和port我们找到ip地址用的是inet_ntop函数接口。
小问题我上来通信的字符串和数字等难道到网络中不考虑大小端问题我地址需要转大端难道通信的字符串不用转吗答案是肯定要转的但是它网络里面自动帮忙转了。 我们用简单的Server函数中的代码为接收到消息拼接一下再返回给服务器
五、tcp_client客户端编写
1、框架
#include iostream
#include unistd.h
#include cstring
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.hvoid Usage(const std::string proc)
{std::cout \n\rUsages: proc serverip serverport\n std::endl;
}// ./tcpclient serverip serverport
int main(int argc, char* argv[])
{if (argc ! 3){Usage(argv[0]);exit(1);}std::string serverip argv[1];uint16_t serverport std::stoi(argv[2]);int socketfd socket(AF_INET, SOCK_STREAM, 0);if (socketfd 0){std::cerr socket create error std::endl; return 1;}struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverport);inet_pton(AF_INET, serverip.c_str(), (server.sin_addr));// tcp客户端要绑定不需要显示绑定os随机分配// 向网络服务器请求连接客户端发起connect的时候进行自动随机绑定int n connect(socketfd, (struct sockaddr*)server, sizeof(server));if (n 0){std::cerr connect err std::endl;return 2;}// 连接成功std::string message;while (true){std::cout Please Enter ;std::getline(std::cin, message);char inbuffer[4096];write(socketfd, message.c_str(), message.size()); // 写进socketfdint n read(socketfd, inbuffer, sizeof(inbuffer)); // 从socketfd读入inbufferif (n 0){std::cerr read err std::endl;return 3;}inbuffer[n] 0;std::cout inbuffer std::endl;}close(socketfd);return 0;
}2、客户端卡退了服务端怎么处理(read返回值为0)
客户端卡退了服务端怎么办 服务端保存好消息其余不管也就是我们的服务器read的返回值为0也就是从底层拿到的连接的文件描述符值为0的时候表示客户端退出。
3、一个有趣的现象–两个一样的客户端去连接客户端单进程服务 理由是单进程版得等一个进程搞好退出后才能实现另一个进程的使用。
4、方法1子进程关listensock父进程关sockfd
因为子进程会有很多没必要的listensock套接字父进程会有很多没必要的sockfd套接字子进程是进行监听父进程是进行连接其套接字本质不一样子进程负责监听父进程负责连接所以把这些没必要的套接字都关了。 但这种情况父进程用waitpid还是有很大问题因为父进程得等子进程退出所以跟单进程没什么区别了下面我们介绍怎么解决这个问题
5、处理waitpid问题孙子进程处理机制或者signal忽略信号 因为父进程等待子进程是阻塞的方式导致的使父进程要一直等待子进程退出子进程退出需要一定的时间并且连的子进程多了子进程就会一直运行等待一个运行后再等下一个运行时间太久了所以我们使用一下子进程创建孙子进程的方法子进程创建完立马退出告诉父进程我退出了父进程就能够执行下一步操作而孙子进程去跑服务并且孙子进程给操作系统进行托孤孙子进程不受爷爷进程控制并发的去跑进程。
但上面这个方法还是有很大的问题的因为子进程的创建代价太大了要有进程地址空间等很多需要创建的东西很麻烦所以我们用下面的这种方法
6、方法2多线程版本 上面的做法仍然有不合理之处就是假如说是几亿个用户连接那岂不是要几亿个线程所以我们用线程池版本来解决
7、方法3线程池版本
1线程池代码ThreadPool.hpp
#pragma once#include iostream
#include vector
#include string
#include queue
#include pthread.h
#include unistd.hstruct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum 5;template class T
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(mutex_);}void Unlock(){pthread_mutex_unlock(mutex_);}void Wakeup(){pthread_cond_signal(cond_);}void ThreadSleep(){pthread_cond_wait(cond_, mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto ti : threads_){if (ti.tid tid)return ti.name;}return None;}public:static void *HandlerTask(void *args){ThreadPoolT *tp static_castThreadPoolT *(args);std::string name tp-GetThreadName(pthread_self());while (true){tp-Lock();while (tp-IsQueueEmpty()){tp-ThreadSleep();}T t tp-Pop();tp-Unlock();t();}}void Start(){int num threads_.size();for (int i 0; i num; i){threads_[i].name thread- std::to_string(i 1);pthread_create((threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t tasks_.front();tasks_.pop();return t;}void Push(const T t){Lock();tasks_.push(t);Wakeup();Unlock();}static ThreadPoolT *GetInstance(){if (nullptr tp_) // ???{pthread_mutex_lock(lock_);if (nullptr tp_){std::cout log: singleton create done first! std::endl;tp_ new ThreadPoolT();}pthread_mutex_unlock(lock_);}return tp_;}private:ThreadPool(int num defalutnum) : threads_(num){pthread_mutex_init(mutex_, nullptr);pthread_cond_init(cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(mutex_);pthread_cond_destroy(cond_);}ThreadPool(const ThreadPoolT ) delete;const ThreadPoolT operator(const ThreadPoolT ) delete; // abc
private:std::vectorThreadInfo threads_;std::queueT tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPoolT *tp_;static pthread_mutex_t lock_;
};template class T
ThreadPoolT *ThreadPoolT::tp_ nullptr;template class T
pthread_mutex_t ThreadPoolT::lock_ PTHREAD_MUTEX_INITIALIZER;2任务代码Task.hpp
#pragma once
#include iostream
#include string
#include Log.hpp
extern Log lg;class Task
{
public:Task(int sockfd, const std::string ipstr, const uint16_t clientport): _sockfd(sockfd), _clientip(ipstr), _clientport(clientport){}void run(){char buffer[4096];// 因为是面向字节流的所以读网络跟读文件一样简单// 先读到bufferssize_t n read(_sockfd, buffer, sizeof(buffer)); // 从套接字信息中读取消息存到buffer中if (n 0){lg(Warning, read err, readip:%s, readport:%d\n, _clientip.c_str(), _clientport);}else if (n 0) // 客户端退出{lg(Info, %s:%d quit, server close fd:%d, _clientip.c_str(), _clientport, _sockfd);}else{buffer[n] 0;std::cout client say# buffer std::endl;std::string echo_string tcpserver echo ;echo_string buffer;// 再写入write(_sockfd, echo_string.c_str(), echo_string.size()); // 处理完的消息写回sockfd文件}close(_sockfd);}void operator()(){run();}private:int _sockfd;std::string _clientip;uint16_t _clientport;
};3代码改进
void RunServer(){ThreadPoolTask::GetInstance()-Start(); // 开启线程池的单例模式// signal(SIGCHLD, SIG_IGN); // 信号忽略lg(Info, tcp_server is running...);while (true){struct sockaddr_in client;socklen_t len sizeof(client);// 1.获取新连接int sockfd accept(_listensocketfd, (struct sockaddr*)client, len);if (sockfd 0){lg(Warning, accept err, errno: %d, errst: %s, errno, strerror(errno));continue;}uint16_t clientport ntohs(client.sin_port);char ipstr[32]; // 自定义的缓冲区inet_ntop(AF_INET, (client.sin_addr), ipstr, sizeof(ipstr));// 2.根据新连接来通信lg(Info, get a new link, sockfd:%d, clentip:%s, clientport:%d, sockfd, ipstr, clientport);// 3.4 线程池版本Task t(sockfd, ipstr, clientport);ThreadPoolTask::GetInstance()-Push(t);}}4结果
发送一则消息则退出线程。
六、服务端翻译小程序
Init.hpp:
#pragma once
#include iostream
#include fstream
#include string
#include unordered_map
#include Log.hppextern Log lg;const std::string dicname ./dict.txt;
const std::string sep :;static bool Split(std::string line, std::string* part1, std::string* part2) // line输入性参数 part1/2都是输出型参数
{auto pos line.find(sep);if (pos std::string::npos){return false;}*part1 line.substr(0, pos);*part2 line.substr(pos 1);return true;
}class Init
{
public:Init(){std::ifstream in(dicname);if (!in.is_open()){lg(Fatal, ifstream open %s error, dicname.c_str());exit(1);}std::string line;while (std::getline(in, line)){std::string part1, part2;Split(line, part1, part2);dict.insert({part1, part2});}in.close();}std::string Translation(const std::string key){auto it dict.find(key);if (it dict.end()) return Unkonw;else return it-second;}
private:std::unordered_mapstd::string, std::string dict;
};Task.hpp:
#pragma once
#include iostream
#include string
#include Log.hpp
#include Init.hpp
extern Log lg;
Init init;
class Task
{
public:Task(int sockfd, const std::string ipstr, const uint16_t clientport): _sockfd(sockfd), _clientip(ipstr), _clientport(clientport){}void run(){char buffer[4096];// 因为是面向字节流的所以读网络跟读文件一样简单// 先读到bufferssize_t n read(_sockfd, buffer, sizeof(buffer)); // 从套接字信息中读取消息存到buffer中if (n 0){lg(Warning, read err, readip:%s, readport:%d\n, _clientip.c_str(), _clientport);}else if (n 0) // 客户端退出{lg(Info, %s:%d quit, server close fd:%d, _clientip.c_str(), _clientport, _sockfd);}else{buffer[n - 2] 0;std::cout client key# buffer std::endl;std::string echo_string init.Translation(buffer);// 再写入write(_sockfd, echo_string.c_str(), echo_string.size()); // 处理完的消息写回sockfd文件}close(_sockfd);}void operator()(){run();}private:int _sockfd;std::string _clientip;uint16_t _clientport;
};七、进化版出现错误的细节问题
1、向一个已经关闭的文件描述符的文件中进行写入读端已经关掉了写端继续写OS会把客户端进程杀掉 所以我们在write的时候都需要用返回值做一层判断防止向已经关闭掉的文件描述符中写信息。要么就加信号忽略
2、重连
tcpclient.cc: 八、在线翻译服务重连
我们先来这些词汇
九、地址复用 十、守护进程介绍
守护进程 守护进程的启动bash
带上日志文件日志信息打印到当前路径下 接口默认00
十一、tcp的通信原理 tcp是全双工的两个人吵架。