Linux_Socket_TCP
✨✨ 欢迎大家来到小伞的大讲堂✨✨
🎈🎈养成好习惯,先赞后看哦~🎈🎈
所属专栏:LInux
小伞的主页:xiaosan_bloggitee:许星让 (xu-xingrang) - Gitee.com
制作不易!点个赞吧!!谢谢喵!!
0.本节目录gitee链接
完整代码:
lesson40 · 许星让/linux gcc - 码云 - 开源中国
1.TCP socket API 详解
如图:客户端与服务端通过类似于文件描述符的进行读写
- socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
- 应用程序可以像读写文件一样用read/write在网络上收发数据;
- 如果 socket()调用出错则返回-1
- 对于 IPv4,family 参数指定为 AF_INET;(即将接收地址设置为0,任何连接客户端,都接收)
- 对于 TCP 协议,type 参数指定为 SOCK_STREAM,表示面向流的传输协议
- protocol参数的介绍从略,指定为0即可
1.2 bind

- 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;服务器需要调用bind绑定一个固定的网络地址和端口号;
- bind()成功返回 0,失败返回-1。
- bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号;
- 前面讲过,structsockaddr*是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;
我们的程序中对myaddr参数是这样初始化的:
- 将整个结构体清零;
- 设置地址类型为AF_INET;
- 网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址,这样设置可以在所有的 IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
- 端口号为SERV_PORT,我们定义为9999;
1.4 listen

- listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略,这里设置不会太大(一般是5)
- listen()成功返回 0,失败返回-1;
1.5 accept

- 三次握手完成后,服务器调用accept()接受连接
- 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
- addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
- 如果给 addr 参数传 NULL,表示不关心客户端的地址;
- addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的,缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

1.6 connect
- 客户端需要调用connect()连接服务器;
- connect 和 bind 的参数形式一致,区别在于 bind 的参数是自己的地址,而connect 的参数是对方的地址;
- connect()成功返回0,出错返回-1;
2. Tcpserver
2.1 TcpServer.cc
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include <sys/wait.h>
#include <signal.h>
#include "ThreadPool.hpp"// 服务器往往是禁止拷贝的
using namespace LogModule;
using namespace ThreadPoolModule;// using task_t = std::function<void()>;
using func_t = std::function<std::string(const std::string &, InetAddr &word)>;const static int defaultsockfd = -1;
const static int backlog = 10;class TcpServer : public Nocopy
{
public:TcpServer(uint16_t port, func_t func): _port(port),_func(func),_listensockfd(defaultsockfd),_isrunning(false){}void Init(){signal(SIGCHLD, SIG_IGN); // 子进程退出发送SIGCHLD信号,用SIG_IGN信号忽略子进程信号// 1. 创建套接字文件_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::FATAL) << "socket error!";exit(SOCKET_ERR);}LOG(LogLevel::INFO) << "socket success: " << _listensockfd;// 2. bind 绑定端口号InetAddr local(_port);int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(BIND_ERR);}LOG(LogLevel::INFO) << "bind success: " << _listensockfd;// 设置_sockfd为监听状态n = listen(_listensockfd, backlog);if (n < 0){LOG(LogLevel::FATAL) << "listen error";exit(LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success: " << _listensockfd;}void Service(int sockfd, InetAddr &peer){char buffer[1024];while (true){ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0; // 设置为C风格字符串, n<= sizeof(buffer)-1LOG(LogLevel::DEBUG) << peer.StringAddr() << " say#" << buffer;std::string echo_string = _func(buffer, peer);// std::string echo_string = "echo# ";// echo_string += buffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0){LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";close(sockfd);break;}else{LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";close(sockfd);break;}}}class ThreadData{public:ThreadData(int sockfd, InetAddr addr, TcpServer *tsvr): _sockfd(sockfd),_addr(addr),_tsvr(tsvr){}public:int _sockfd;InetAddr _addr;TcpServer *_tsvr;};static void *Routine(void *args){pthread_detach(pthread_self()); // 分离线程ThreadData *td = static_cast<ThreadData *>(args);td->_tsvr->Service(td->_sockfd, td->_addr);delete td;return nullptr;}void Run(){_isrunning = true;while (_isrunning){// a.获取状态struct sockaddr_in peer;socklen_t len = sizeof(sockaddr_in);// sockfd 提供服务,_listensockfd 提供链接int sockfd = accept(_listensockfd, CONV(peer), &len);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error";continue;}InetAddr addr(peer);LOG(LogLevel::INFO) << "accept sucess, peer addr : " << addr.StringAddr();// 多线程适合长服务ThreadData *td = new ThreadData(sockfd, addr, this);pthread_t tid;pthread_create(&tid, nullptr, Routine, td);// 0.单进程版本// Service(sockfd, addr);// 1.多进程版本// pid_t id = fork();// if (id < 0)// {// LOG(LogLevel::FATAL) << "fork error";// exit(FORK_ERR);// }// else if (id == 0)// {// // 子进程// if (fork() > 0)// exit(OK);// //孙子进程// //当子进程返回,孙子进程为孤儿进程,父进程变为系统bash,系统自动回收进程// close(_listensockfd); // 关闭不相关的管道// Service(sockfd, addr);// exit(OK);// }// else// {// // 父进程// close(sockfd);// pid_t rid = waitpid(id, nullptr, 0); // 阻塞等待// }// 2.多线程// ThreadData *td = new ThreadData(sockfd, addr, this);// pthread_t tid;// pthread_create(&tid, nullptr, Routine, td);// //pthread_join()线程等待,也会导致线程串行,使用pthread_detach线程分离,无需等待;// version3:线程池版本,线程池一般比较适合处理短服务// 将新链接和客户端构建一个新的任务,push线程池中// ThreadPool<task_t>::GetInstance()->Enqueue([this,sockfd,&addr](){// this->Service(sockfd,addr);// });}_isrunning = false;}~TcpServer() {}private:int _port;int _listensockfd; // 监听socketbool _isrunning;func_t _func; // 设置回调处理
};
2.2 TcpServer.hpp
#include "TcpServer.hpp"
#include "Dict.hpp"
#include "Command.hpp"
void Usage(std::string proc)
{std::cerr << "Usage" << proc << " port" << std::endl;
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strategy();// Dict d;// d.LoadDict();Command cmd;//命令行xshellfunc_t ret = std::bind(&Command::Execute,&cmd,std::placeholders::_1,std::placeholders::_2);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port,ret);// std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, [&cmd](const std::string &command, InetAddr &addr)// { return cmd.Execute(command, addr); });//翻译// std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);// std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, [&d](const std::string &word, InetAddr &addr)// { return d.Translate(word, addr); });tsvr->Init();tsvr->Run();return 0;
}

