网站模板带后台,如何把网站转网站,网站建设步骤 优帮云,在线建站软件#x1f525; 个人主页#xff1a;大耳朵土土垚 #x1f525; 所属专栏#xff1a;Linux系统编程 这里将会不定期更新有关Linux的内容#xff0c;欢迎大家点赞#xff0c;收藏#xff0c;评论#x1f973;#x1f973;#x1f389;#x1f389;#x1f389; 文章目… 个人主页大耳朵土土垚 所属专栏Linux系统编程 这里将会不定期更新有关Linux的内容欢迎大家点赞收藏评论 文章目录 1. TCP socket API 详解socket()bind()listen()accept()connect() 2. Echo ServerTCP服务器多进程版本多线程版本线程池版本 TCP客户端 1. TCP socket API 详解 下面介绍程序中用到的 socket API,这些函数都在 sys/socket.h 中。
socket() 作用打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符; 应用程序可以像读写文件一样用 read/write 在网络上收发数据;返回值如果 socket()调用出错则返回-1;参数对于 IPv4, family 参数指定为 AF_INET; 对于 TCP 协议,type 参数指定为SOCK_STREAM, 表示面向流的传输协议 protocol 参数的介绍从略,指定为 0 即可。
bind() 介绍服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一个固定的网络地址和端口号; 返回值 bind()成功返回 0,失败返回-1。 作用将参数 sockfd 和 myaddr 绑定在一起, 使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号; 参数 前面讲过,struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen指定结构体的长度;我们的程序中对 myaddr 参数是这样初始化的: 1. 将整个结构体清零; 2. 设置地址类型为 AF_INET; 3. 网络地址为 INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有 多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的 IP 地址上监听, 直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址; 4. 端口号为 SERV_PORT, 我们定义为 8080;
listen() 介绍listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接 等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5);返回值listen()成功返回 0,失败返回-1;
accept() 介绍三次握手完成后, 服务器调用 accept()接受连接; 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;参数addr 是一个传出参数,accept()返回时传出客户端的地址和端口号; 如果给 addr 参数传 NULL,表示不关心客户端的地址; addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
我们的服务器程序结构是这样的:
返回值sockfd用来进行通信
connect() 介绍客户端需要调用 connect()连接服务器;参数connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而 connect 的参数是对方的地址;返回值 connect()成功返回 0,出错返回-1;
2. Echo Server 有了上面的接口我们就可以实现以TCP为基础的简单消息回显服务器了运行结果应该如下图所示 代码如下
TCP服务器
#pragma once#include iostream
#include string.h
#include cstdlib
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include unistd.h
#include sys/wait.h
#include signal.h
#include pthread.h
#include functional
#include InetAddr.hpp
#include Log.hpp
#include Common.hpp#define BACKLOG 8
using namespace InetAddrModule;
using namespace LogModule;
static const uint16_t defaultport 8888;class TcpServer
{
public:TcpServer(uint16_t port defaultport) : _port(port), _listensockfd(-1), _isruning(false){}void InitServer(){// 1.创建Tcp套接字_listensockfd ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd 0){LOG(LogLevel::ERROR) InitServer socket fail ...;Die(SOCKET_ERR);}// 填充信息struct sockaddr_in serveraddr;memset(serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family AF_INET;serveraddr.sin_port ::htons(_port);//aaa注意要转网络serveraddr.sin_addr.s_addr INADDR_ANY; // 表示可以接收任意地址的信息// 2. bind;int n ::bind(_listensockfd, CONV(serveraddr), sizeof(serveraddr));if (n 0){LOG(LogLevel::ERROR) InitServer bind fail ...;Die(BIND_ERR);}// 3.监听int m ::listen(_listensockfd, BACKLOG);if (m 0){LOG(LogLevel::ERROR) InitServer listen fail ...;Die(LISTEN_ERR);}LOG(LogLevel::INFO) ServerInit success...;}void handler(int sockfd){char buffer[4096];while (true){ssize_t n ::read(sockfd, buffer, sizeof(buffer) - 1);if (n 0){buffer[n] 0;LOG(LogLevel::INFO) buffer;std::string echo_string server echo# ;echo_string buffer;::write(sockfd, echo_string.c_str(),echo_string.size());}else if (n 0) // client 退出{LOG(LogLevel::INFO) client quit: sockfd;break;}else{// 读取失败break;}}::close(sockfd); // fd泄漏问题}void Start(){_isruning true;while(_isruning){struct sockaddr_in peer;socklen_t peerlen sizeof(peer); // 这个地方一定要注意要不然会有问题LOG(LogLevel::DEBUG) accepting ...;// 我们要获取client的信息数据(sockfd)client socket信息(accept || recvfrom)int sockfd ::accept(_listensockfd, CONV(peer), peerlen);if (sockfd 0){LOG(LogLevel::ERROR) StartServer accept fail ...;continue; // 继续接收}LOG(LogLevel::INFO)ServerStart success...;// 连接成功后就可以通信handler(sockfd);}_isruning false;}~TcpServer(){}private:uint16_t _port;int _listensockfd;bool _isruning;
}; 与Udp服务器不同的是Tcp服务要求我们先调用listen接口监听然后在通过accept和客户端使用connet建立连接后才可以进行通信所以如果仅仅使用单进程是无法满足同时接收多个客户端的消息下面将会给出多进程、多线程以及基于线程池实现的Tcp服务。 多进程版本
//其他的不变
void Start(){_isruning true;while(_isruning){struct sockaddr_in peer;socklen_t peerlen sizeof(peer); // 这个地方一定要注意要不然会有问题LOG(LogLevel::DEBUG) accepting ...;// 我们要获取client的信息数据(sockfd)client socket信息(accept || recvfrom)int sockfd ::accept(_listensockfd, CONV(peer), peerlen);if (sockfd 0){LOG(LogLevel::ERROR) StartServer accept fail ...;continue; // 继续接收}LOG(LogLevel::INFO)ServerStart success...;// 连接成功后就可以通信//version1: 多进程pid_t id ::fork();if(id 0)//子进程{::close(_listensockfd);//要关掉不需要的文件描述符避免fd泄露问题if(fork())//子进程再创建孙子进程::exit(0);//让子进程退出孙子进程成为孤儿进程这样就不用父进程回收//孙子进程处理结束后由操作系统回收handler(sockfd);::exit(0);}::close(sockfd);//子进程退出后父进程就不会阻塞在这里,继续接收其他客户端连接int rid ::waitpid(id, nullptr, 0);if(rid 0)LOG(LogLevel::WARNING) ServerStart waitpid error...;}_isruning false;}对于多进程首先每个进程都有自己的文件描述符表所以父子进程都需要关闭自己不需要的文件描述符 其次父进程需要等待回收子进程此时父进程会阻塞直到子进程完成通信这样和之前单进程通信效果一样所以为了不让父进程阻塞子进程需要再创建子进程用它来完成通信此时父进程就可以直接回收子进程孙子进程就成为孤儿进程进行通信结束后由操作系统回收。 多线程版本 struct ThreadData{int sockfd;TcpServer *self;};static void *ThreadEntry(void *args){pthread_detach(pthread_self()); // 线程分离线程执行结束后自动被系统回收ThreadData *data (ThreadData *)args;data-self-handler(data-sockfd);return nullptr;}void Start(){_isruning true;while (_isruning){struct sockaddr_in peer;socklen_t peerlen sizeof(peer); // 这个地方一定要注意要不然会有问题LOG(LogLevel::DEBUG) accepting ...;// 我们要获取client的信息数据(sockfd)client socket信息(accept || recvfrom)int sockfd ::accept(_listensockfd, CONV(peer), peerlen);if (sockfd 0){LOG(LogLevel::ERROR) StartServer accept fail ...;continue; // 继续接收}LOG(LogLevel::INFO) ServerStart success...;// 连接成功后就可以通信// version2: 多线程// 主线程和新线程共享一张文件描述符表pthread_t tid;ThreadData *data new ThreadData;data-sockfd sockfd;data-self this;pthread_create(tid, nullptr, ThreadEntry, data);
}_isruning false;}设置线程分离这样线程执行完毕后就可以自动被系统回收 线程池版本
using task_t std::functionvoid();void Start(){_isruning true;while (_isruning){struct sockaddr_in peer;socklen_t peerlen sizeof(peer); // 这个地方一定要注意要不然会有问题LOG(LogLevel::DEBUG) accepting ...;// 我们要获取client的信息数据(sockfd)client socket信息(accept || recvfrom)int sockfd ::accept(_listensockfd, CONV(peer), peerlen);if (sockfd 0){LOG(LogLevel::ERROR) StartServer accept fail ...;continue; // 继续接收}LOG(LogLevel::INFO) ServerStart success...;// version-3线程池版本 比较适合处理短任务或者是用户量少的情况ThreadPooltask_t::GetInstance()-Enqueue([this, sockfd](){ this-handler(sockfd); });}_isruning false;}引入之前实现的线程池并使用单例模式 使用服务器代码如下
#include TcpServer.hppint main()
{std::unique_ptrTcpServer tcpserver std::make_uniqueTcpServer();tcpserver-InitServer();tcpserver-Start();return 0;
}TCP客户端
#include iostream
#include cstring
#include string
#include cstdlib
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h#include Common.hpp
#include Log.hpp
#include InetAddr.hppusing namespace LogModule;
using namespace InetAddrModule;int sockfd -1;//./udp_client server_ip server_port
int main(int argc, char *argv[])
{if (argc ! 3){LOG(LogLevel::ERROR) Usage: argv[0] serverip serverport;Die(ARGV_ERR);}// 1.创建sockfdsockfd ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd 0){LOG(LogLevel::WARNING) client sockfd fail...;Die(SOCKET_ERR);}// 2.填充服务器信息std::string serverip argv[1];uint16_t serverport std::stoi(argv[2]);// InetAddr serveraddr(serverip, serverport); struct sockaddr_in serveraddr;memset(serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family AF_INET;serveraddr.sin_port htons(serverport);serveraddr.sin_addr.s_addr inet_addr(serverip.c_str());// 3.与服务器建立连接int n ::connect(sockfd, CONV(serveraddr), sizeof(serveraddr));if (n 0){LOG(LogLevel::ERROR) ClientConnet fail...;Die(CONNET_ERR);}// 4. 发送请求给服务器while (true){// 4.1获取信息std::cout Please Enter# ;std::string message;std::getline(std::cin, message);// 4.2发送信息给服务器ssize_t n ::sendto(sockfd, message.c_str(), sizeof(message), 0, CONV(serveraddr), sizeof(serveraddr));if (n 0){LOG(LogLevel::ERROR) client sendto fail...;continue;}// 4.3从服务器接收信息char buffer[1024];struct sockaddr_in tmp;socklen_t len sizeof(tmp);ssize_t m ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(tmp), len);if (m 0){buffer[m] 0;std::cout buffer std::endl;}else{LOG(LogLevel::ERROR) client recvfrom fail...;}}::close(sockfd);return 0;
} 与UDP客户端相比TCP客户端需要与服务器通过connet连接后才能通信。