【Linux网络编程】网络编程套接字(TCP服务器)

news/2024/4/30 2:54:26/文章来源:https://blog.csdn.net/m0_74215144/article/details/137466046

【Linux网络编程】网络编程套接字(TCP服务器)

目录

  • 【Linux网络编程】网络编程套接字(TCP服务器)
    • 地址转换函数
      • 关于inet_ntoa
    • 简单的TCP网络程序
      • TCP sockot API详解
        • socket()
        • bind()
        • listen()
        • accept();
        • connect
    • 完整的TCP服务器代码(线程池版)

作者:爱写代码的刚子

时间:2024.4.4

前言:本篇博客主要介绍TCP及其服务器编码

地址转换函数

只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP地址 但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换

字符串转in_addr的函数

#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
int inet_pton(int family,const char *strptr,void *addrptr);

in_addr转字符串的函数:

char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);

其中inet_ptoninet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。

关于inet_ntoa

inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是否需要调用者手动释放呢?

在这里插入图片描述

inet_ntoa函数将这个返回结果放到了静态存储区,不需要我们进行手动释放,如果多次调用会出现问题吗?

  • 进行一段代码演示:

在这里插入图片描述

在这里插入图片描述

因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果

  • 在APUE中,明确提出inet_ntoa不是线程安全函数
  • 但在centos7上测试没有出现问题,可能内部的实现加了互斥锁
  • 在多线程环境下,推荐使用inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题
  • 可以用以下代码进行多线程的测试
// Created Time:    2024-04-04 10:48:05
// Modified Time:   2024-04-04 11:00:43#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int cnt =100;void* Func1(void* p) {struct sockaddr_in* addr = (struct sockaddr_in*)p;while (cnt>0) {char* ptr = inet_ntoa(addr->sin_addr);printf("addr1: %s,cnt: %d\n", ptr,cnt);cnt--;}return NULL;
}
void* Func2(void* p) {struct sockaddr_in* addr = (struct sockaddr_in*)p;while (cnt>0) {char* ptr = inet_ntoa(addr->sin_addr);printf("addr2: %s,cnt: %d\n", ptr,cnt);cnt--;}return NULL;
}
int main() {pthread_t tid1 = 0;pthread_t tid2 = 0;struct sockaddr_in addr1;struct sockaddr_in addr2;addr1.sin_addr.s_addr = 0;addr2.sin_addr.s_addr = 0xffffffff;pthread_create(&tid1, NULL, Func1, &addr1);pthread_create(&tid2, NULL, Func2, &addr2);pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

在这里插入图片描述

所以在我这个环境下他并不是线程安全的

简单的TCP网络程序

TCP sockot API详解

头文件:<sys/socket.h>

socket()

在这里插入图片描述

参数

  • domain(域):这个参数指定了套接字的地址簇,也称为协议簇
    • AF_INET:IPv4 地址族。
    • AF_INET6:IPv6 地址族。
    • AF_UNIX:本地通信(Unix 域套接字)。
    • AF_PACKET:低级网络接口。
  • type(类型)
    • SOCK_STREAM:面向连接的流套接字,提供可靠的、双向的、基于字节的数据传输。
    • SOCK_DGRAM:数据报套接字,提供不可靠的、无连接的、固定长度的数据传输。
    • SOCK_RAW:原始套接字,直接访问网络层。
  • protocol(协议)
    • 0:表示使用默认协议。
    • IPPROTO_TCP:TCP 协议。
    • IPPROTO_UDP:UDP 协议。
    • IPPROTO_ICMP:ICMP 协议。
  • 对于 AF_INET(IPv4 地址族),默认协议通常是 IPPROTO_TCP(TCP 协议)。
  • 对于 AF_INET6(IPv6 地址族),默认协议通常是 IPPROTO_TCP(TCP 协议)。
  • 对于 AF_UNIX(Unix 域套接字),默认协议是不适用的,因为它们在本地通信上工作,不涉及到传输层协议。
  • socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;

  • 应用程序可以像读写文件一样用read/write在网络上收发数据;

  • 如果socket()调用出错则返回-1;

  • 对于IPv4, family参数指定为AF_INET;

  • 对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议

  • protocol参数的介绍从略,指定为0即可。

bind()

在这里插入图片描述

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后 就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;

  • bind()成功返回0,失败返回-1。

  • bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听 myaddr所描述的地址和端口号;

  • struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结 构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;

我们的程序中对myaddr参数是这样初始化的:

bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
  1. 将整个结构体清零;
  2. 设置地址类型为AF_INET;
  3. 网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
  4. 端口号为SERV_PORT,我们定义为9999
listen()

在这里插入图片描述

  • listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多 的连接请求就忽略, 这里设置不会太大(一般是5), 具体细节同学们课后深入研究;
  • listen()成功返回0,失败返回-1;

在这里插入图片描述

accept();

在这里插入图片描述

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

在这里插入图片描述

  • 客户端需要调用connect()连接服务器;
  • connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
  • connect()成功返回0,出错返回-1;

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

注意,云服务器的公有ip无法直接绑定,但可以绑定本地的ip

local.sin_addr.s_addr = INADDR_ANY;//将套接字绑定到所有可用的网络接口,而不是绑定到特定的网络接口。这样就能绑定服务器的公有ip了

添加这个语句就能绑定服务器的公有ip了

  • 向网络发送数据,比如字符串等,使用的接口会自动将主机序列转换为网络序列

  • 获取客户端的ip以及端口号

在这里插入图片描述

  • 接收并发送消息

在这里插入图片描述

在这里插入图片描述

  • 添加重连功能,服务器处理客户端中途读时的错误

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码:

TcpClient.cc

#include <iostream>
//sock四件套
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>void Usage(const std::string &proc)
{std::cout << "\n\rUsage: " << 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]);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));while (true){int cnt = 5;int isreconnect = false;int sockfd = 0;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;return 1;}do{//tcp客户端要不要绑定,tcp客户端要绑定,只是不用显示地写出来(由操作系统根据需求进行随机选择)//UDP在首次发送数据时确定端口号//tcp是面向链接的,客户端发起connect的时候进行自动随机bindint n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){isreconnect = true;cnt--;std::cerr << "connect error..., reconnect: " << cnt << std::endl;sleep(2);}else{break;}} while (cnt && isreconnect);if (cnt == 0){std::cerr << "user offline..." << std::endl;break;}while (true){std::string message;std::cout << "Please Enter# ";std::getline(std::cin, message);int n = write(sockfd, message.c_str(), message.size());if (n < 0){std::cerr << "write error..." << std::endl;// break;}char inbuffer[4096];n = read(sockfd, inbuffer, sizeof(inbuffer));if (n > 0){inbuffer[n] = 0;std::cout << inbuffer << std::endl;}else{break;}}close(sockfd);}return 0;
}

TcpServer.cc

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h> //sockaddr_in类型头文件
#include <netinet/in.h>
#include "Log.hpp"
#include <sys/wait.h>
#include <signal.h>const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 一般不要设置太大Log lg;enum
{UsageError = 1,SocketError,BindError,ListenError
};class TcpServer
{
public:TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip){}void InitServer() // 尽量不要将有风险的事情放进构造函数中{listensock_ = socket(AF_INET, SOCK_STREAM, 0);if (listensock_ < 0){lg(Fatal, "create socket,errno: %d, errstring: %s", errno, strerror(errno));exit(SocketError);}lg(Info, "create socket success, sockfd: %d", listensock_);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));local.sin_addr.s_addr = INADDR_ANY; // 将套接字绑定到所有可用的网络接口,而不是绑定到特定的网络接口。这样就能绑定服务器的ip了if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));exit(BindError);}lg(Info, "Bind socket success, sockfd: %d", listensock_);if (listen(listensock_, backlog) < 0){lg(Fatal, "bind error: %d, errstring: %s", errno, strerror(errno));exit(ListenError);}lg(Info, "Listen socket success, sockfd: %d", listensock_);}void Start(){// signal(SIGCHLD,SIG_IGN);lg(Info, "tcpServer is running...");for (;;){// 获取新链接struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = accept(listensock_, (struct sockaddr *)&client, &len); // 为什么这里又多了一个fd,为什么不用之前的初始化服务器的sockfd_// 这里的listensock_只是将底层的链接获取上来,但是真正提供服务的是accept返回的sockfd// 一般listensock_为3,sockfd为4if (sockfd < 0){lg(Warning, "accept error: %d,errstring: %s", errno, strerror(errno));// 获取一个链接失败了不一定要退出,获取下一个链接即可continue;}uint16_t clientport = ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));// 根据新链接来进行通信lg(Info, "get a new link...,sockfd: %d,client ip: %s,client port: %d", sockfd, clientip, clientport);Service(sockfd, clientip, clientport);close(sockfd);}}void Service(int sockfd, const std::string &clientip, const uint16_t &clientport){while (true){// 读消息直接使用read函数即可char buffer[4096];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){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());}else if (n == 0){lg(Info, "%s:%d quit,server close sockfd: %d", clientip.c_str(), clientport, sockfd);break;}else{lg(Warning, "read error, sockfd: %d, client ip: %s,client port: %d", sockfd, clientip.c_str(), clientport);break;}}}~TcpServer() {}private:int listensock_;uint16_t port_;std::string ip_;
};

但是这种单进程服务器只能处理一个链接,明显无法满足我们的需求,所以我们进行改进:

  • 多线程版的服务器

在这里插入图片描述

父进程的sockfd关闭了不会对子进程产生影响,因为子进程和父进程各自有独立的文件描述符指针(文件描述符fd中也存在引用计数

优雅的让父进程不会阻塞等待(子进程已经退出了),同时让系统领养进程并自动回收

if(fork() > 0) exit(0);

还可以使用信号,让父进程不用等待

在这里插入图片描述

  • 还可以使用多线程(参考):

在这里插入图片描述

注意要将Routine变为static函数,再使用pthread_detach(pthread_self())进行线程分离

线程池:

在这里插入图片描述

完整的TCP服务器代码(线程池版)

TcpClient.cc

#include <iostream>
//sock四件套
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>void Usage(const std::string &proc)
{std::cout << "\n\rUsage: " << 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]);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));while (true){int cnt = 5;int isreconnect = false;int sockfd = 0;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;return 1;}do{//tcp客户端要不要绑定,tcp客户端要绑定,只是不用显示地写出来(由操作系统根据需求进行随机选择)//UDP在首次发送数据时确定端口号//tcp是面向链接的,客户端发起connect的时候进行自动随机bindint n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){isreconnect = true;cnt--;std::cerr << "connect error..., reconnect: " << cnt << std::endl;sleep(2);}else{break;}} while (cnt && isreconnect);if (cnt == 0){std::cerr << "user offline..." << std::endl;break;}while (true){std::string message;std::cout << "Please Enter# ";std::getline(std::cin, message);int n = write(sockfd, message.c_str(), message.size());if (n < 0){std::cerr << "write error..." << std::endl;// break;}char inbuffer[4096];n = read(sockfd, inbuffer, sizeof(inbuffer));if (n > 0){inbuffer[n] = 0;std::cout << inbuffer << std::endl;}else{break;}}close(sockfd);}return 0;
}

TcpServer.cc

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h> //sockaddr_in类型头文件
#include <netinet/in.h>
#include "Log.hpp"
#include <sys/wait.h>
#include <signal.h>
#include "Task.hpp"
#include "ThreadPool.hpp"
const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 一般不要设置太大Log lg;enum
{UsageError = 1,SocketError,BindError,ListenError
};
class TcpServer;class ThreadData
{
public:ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t) : sockfd(fd), clientip(ip), clientport(p), tsvr(t){}public:int sockfd;std::string clientip;uint16_t clientport;TcpServer *tsvr;
};class TcpServer
{
public:TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip){}void InitServer() // 尽量不要将有风险的事情放进构造函数中{listensock_ = socket(AF_INET, SOCK_STREAM, 0);if (listensock_ < 0){lg(Fatal, "create socket,errno: %d, errstring: %s", errno, strerror(errno));exit(SocketError);}lg(Info, "create socket success, sockfd: %d", listensock_);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));local.sin_addr.s_addr = INADDR_ANY; // 将套接字绑定到所有可用的网络接口,而不是绑定到特定的网络接口。这样就能绑定服务器的ip了if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));exit(BindError);}lg(Info, "Bind socket success, sockfd: %d", listensock_);if (listen(listensock_, backlog) < 0){lg(Fatal, "bind error: %d, errstring: %s", errno, strerror(errno));exit(ListenError);}lg(Info, "Listen socket success, sockfd: %d", listensock_);}static void *Routine(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);td->tsvr->Service(td->sockfd, td->clientip, td->clientport); //???delete td;return nullptr;}void Start(){ThreadPool<Task>::GetInstance()->Start();// signal(SIGCHLD,SIG_IGN);lg(Info, "tcpServer is running...");for (;;){// 获取新链接struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = accept(listensock_, (struct sockaddr *)&client, &len); // 为什么这里又多了一个fd,为什么不用之前的初始化服务器的sockfd_// 这里的listensock_只是将底层的链接获取上来,但是真正提供服务的是accept返回的sockfd// 一般listensock_为3,sockfd为4if (sockfd < 0){lg(Warning, "accept error: %d,errstring: %s", errno, strerror(errno));// 获取一个链接失败了不一定要退出,获取下一个链接即可continue;}uint16_t clientport = ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));// 根据新链接来进行通信lg(Info, "get a new link...,sockfd: %d,client ip: %s,client port: %d", sockfd, clientip, clientport);// Service(sockfd, clientip, clientport);// close(sockfd);//多进程版// pid_t id = fork();// if (id == 0)// {//     // child//     close(listensock_); // 由于子进程不需要父进程的listensock_,所以我们需要将它关闭//     if (fork() > 0)//         exit(0);//     Service(sockfd, clientip, clientport); // 孙子进程, system 领养//     close(sockfd);//     exit(0);// }// close(sockfd);// // father// pid_t rid = waitpid(id, nullptr, 0);// (void)rid;//多线程版本// ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);// pthread_t tid;// pthread_create(&tid, nullptr, Routine, td);//线程池版本Task t(sockfd, clientip, clientport);ThreadPool<Task>::GetInstance()->Push(t);}}void Service(int sockfd, const std::string &clientip, const uint16_t &clientport){while (true){// 读消息直接使用read函数即可char buffer[4096];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){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());}else if (n == 0){lg(Info, "%s:%d quit,server close sockfd: %d", clientip.c_str(), clientport, sockfd);break;}else{lg(Warning, "read error, sockfd: %d, client ip: %s,client port: %d", sockfd, clientip.c_str(), clientport);break;}}}~TcpServer() {}private:int listensock_;uint16_t port_;std::string ip_;
};

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 &clientip, const uint16_t &clientport): sockfd_(sockfd), clientip_(clientip), clientport_(clientport){}Task(){}void run(){// 测试代码char buffer[4096];// Tcp是面向字节流的,你怎么保证,你读取上来的数据,是"一个" "完整" 的报文呢?ssize_t n = read(sockfd_, buffer, sizeof(buffer)); // BUG?if (n > 0){buffer[n] = 0;std::cout << "client key# " << buffer << std::endl;std::string echo_string = init.translation(buffer);// sleep(5);// // close(sockfd_);// lg(Warning, "close sockfd %d done", sockfd_);// sleep(2);n = write(sockfd_, echo_string.c_str(), echo_string.size()); // 100 fd 不存在if(n < 0){lg(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));}}else if (n == 0){lg(Info, "%s:%d quit, server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);}else{lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd_, clientip_.c_str(), clientport_);}close(sockfd_);}void operator()(){run();}~Task(){}private:int sockfd_;std::string clientip_;uint16_t clientport_;
};

Init.hpp

#pragma once#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"const std::string dictname = "./dict.txt";
const std::string sep = ":";extern Log lg;//yellow:黄色...
static bool Split(std::string &s, std::string *part1, std::string *part2)
{auto pos = s.find(sep);if(pos == std::string::npos) return false;*part1 = s.substr(0, pos);*part2 = s.substr(pos+1);return true;
}class Init
{
public:Init(){std::ifstream in(dictname);if(!in.is_open()){lg(Fatal, "ifstream open %s error", dictname.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 iter = dict.find(key);if(iter == dict.end()) return "Unknow";else return iter->second;}
private:std::unordered_map<std::string, std::string> dict;
};

Log.hpp

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};

Main.cc

#include "TcpServer.hpp"
#include <iostream>
#include <memory>void Usage(std::string proc)
{std::cout<<"\n\rUsage: "<<proc <<" port[1024+]\n" <<std::endl;
}int main(int argc,char *argv[])
{if(argc != 2){Usage(argv[0]);exit(UsageError);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));tcp_svr->InitServer();tcp_svr->Start();return 0;
}

dict.txt

apple:苹果...
banana:香蕉...
red:红色...
yellow:黄色...
the: 这
be: 是
to: 朝向/给/对
and: 和
I: 我
in: 在...里
that: 那个
have: 有
will: 将
for: 为了
but: 但是
as: 像...一样
what: 什么
so: 因此
he: 他
her: 她
his: 他的
they: 他们
we: 我们
their: 他们的
his: 它的
with: 和...一起
she: 她
he: 他(宾格)
it: 它

ThreadPool.hpp

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum = 10;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){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(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 ThreadPool<T> *GetInstance(){if (nullptr == tp_) // ???{pthread_mutex_lock(&lock_);if (nullptr == tp_){std::cout << "log: singleton create done first!" << std::endl;tp_ = new ThreadPool<T>();}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 ThreadPool<T> &) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPool<T> *tp_;static pthread_mutex_t lock_;
};template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

我们更推荐多线程版的服务器(创建进程的成本比较高)

注意线程池版本的服务器不适合长服务,因为线程的个数是确定的

服务器的结构:

while(1)
{cliaddr_len = sizeof(cliaddr);connfd = accept(listenfd,(struct sockaddr *)&cliaddr_len);n = read(connfd,buf,MAXLINE);...close(connfd);
}

【附】:双工在物理层面上是指通信线路上可以同时发送和接收数据,然而在数据链路层的传输采用载波监听/碰撞检测的技术。指的就是在有线传输上只能存在一种电信号,所以也就不会存在两种电信号同时存在的场景了,即有线传输物理层面上没有双工一说,要么发送数据,要么接收数据。 (ps:无线传输中的码分复用可以同时存在)我们现在认为的双工是指逻辑连接层面上,而 UDP 是不需要进行逻辑连接的,只是单向发送,但双方随时都可以发送,如果你认为这是双工那也合理。而TCP的全双工也是逻辑层面上,通信两端随时都可以发送和接收数据,不需要像 HTTP/1.1 那样采用请求应答模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_1046075.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【算法】双指针算法

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 1. 283. 移动零1.1 分析1.2 代码 2. 1089. 复写零2.1 分析2.2 代码 3. 202. 快乐数3.1 分析3.2 代码 4. 11. 盛最多水的容器4.1 分析4.2 代码 5. LCR 179. 查找总价格为目标值的两个商品5.1 分析5.2 代码 6. 15. 三数之和…

antd vue table控件的使用(三)

今天就讲讲Ant Design Vue下的控件----table表格&#xff08;分页、编辑和删除功能&#xff09; 结合项目中的需求&#xff0c;看看如何配置,让table即可以展示列表&#xff0c;又可以直接编辑数据。需求&#xff1a; &#xff08;1&#xff09;展示即将检查的数据列表&#…

持续交付工具Argo CD的部署使用

Background CI/CD&#xff08;Continuous Integration/Continuous Deployment&#xff09;是一种软件开发流程&#xff0c;旨在通过自动化和持续集成的方式提高软件交付的效率和质量。它包括持续集成&#xff08;CI&#xff09;和持续部署&#xff08;CD&#xff09;两个主要阶…

Day5-Hive的结构和优化、数据文件存储格式

Hive 窗口函数 案例 需求&#xff1a;连续三天登陆的用户数据 步骤&#xff1a; -- 建表 create table logins (username string,log_date string ) row format delimited fields terminated by ; -- 加载数据 load data local inpath /opt/hive_data/login into table log…

怎么保证缓存与数据库的最终一致性?

目录 零.读数据的标准操作 一.Cache aside Patten--旁路模式 二.Read/Write Through Pattern--读写穿透 三.Write Back Pattern--写回 四.运用canal监听mysql的binlog实现缓存同步 零.读数据的标准操作 这里想说的是不管哪种模式读操作都是一样的&#xff0c;这是一种统一…

【开源社区】openEuler、openGauss、openHiTLS、MindSpore

【开源社区】openEuler、openGauss、openHiTLS、MindSpore 写在最前面开源社区参与和贡献的一般方式开源技术的需求和贡献方向 openEuler 社区&#xff1a;开源系统官方网站官方介绍贡献攻略开源技术需求 openGauss 社区&#xff1a;开源数据库官方网站官方介绍贡献攻略开源技术…

Unity之Unity面试题(五)

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之Unity面试题&#xff08;五&#xff09; TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取…

目标检测——RCNN系列学习(二)Faster RCNN

接着上一篇文章&#xff1a;目标检测——RCNN系列学习(一&#xff09;-CSDN博客 主要内容包含&#xff1a;Faster RCNN 废话不多说。 Faster RCNN [1506.01497] Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks (arxiv.org)https://arxiv.…

跨域问题一文解决

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Vue ⛺️稳中求进&#xff0c;晒太阳 一、为什么会出现跨域的问题&#xff1f; 是浏览器的同源策略&#xff0c;跨域也是因为浏览器这个机制引起的&#xff0c;这个机制的存在还是在于安全…

Netty 入门应用之Http服务WebSocket

Netty实现Http服务 主要的变化是在初始化器中引入了新的编解码器 一些创建的类作用和Netty HelloWorld的小demo一样我这里就不再次重复了 1、Http服务端代码 public class HttpServer {public static void main(String[] args) {// 创建Reactor// 用来管理channel 监听事件 …

力扣HOT100 - 238. 除自身以外数组的乘积

解题思路&#xff1a; 当前位置的结果就是它左部分的乘积再乘以它右部分的乘积。因此需要进行两次遍历&#xff0c;第一次遍历用于求左部分的乘积&#xff0c;第二次遍历在求右部分的乘积的同时&#xff0c;再将最后的计算结果一起求出来。 class Solution {public int[] prod…

Vue3学习01 Vue3核心语法

Vue3学习 1. Vue3新的特性 2. 创建Vue3工程2.1 基于 vue-cli 创建项目文件说明 2.2 基于 vite 创建具体操作项目文件说明 2.3 简单案例(vite) 3. Vue3核心语法3.1 OptionsAPI 与 CompositionAPIOptions API 弊端Composition API 优势 ⭐3.2 setup小案例setup返回值setup 与 Opt…

【vue/uniapp】使用 smooth-signature 实现 h5 的横屏电子签名

通过github链接进行下载&#xff0c;然后代码参考如下&#xff0c;功能包含了清空、判断签名内容是否为空、生成png/jpg图片等。 签名效果&#xff1a; 预览效果&#xff1a; 下载 smooth-signature 链接&#xff1a;https://github.com/linjc/smooth-signature 代码参考&a…

nexus搭建maven与docker镜像的私有仓库

引言 通过nexus搭建maven与docker镜像的私有仓库,实现jar包与镜像动态更新、共享、存储。 一、nexus部署 通过docker-compose部署nexus name: java services:#############################环境#############################env-nexus:restart: always## 3.58.1image: so…

ANSYS 2024 R1 HFSS部分更新介绍(附下载)

1. 优化Layout component工作流 • 支持多区域 - 支持参数化弯曲定义的刚柔结合的PCB • Phi 网格可用 • 支持Mesh Fusion •简化创建复杂装配体的过程 2. 提升求解器速度 • 分布式矩阵汇编的内存使用率改进 ‐减少分布式矩阵求解器的RAM消耗 • 分布式稀疏直接求解器&am…

物联网实战--驱动篇之(六)4G通讯(Air780E)

目录 一、4G模块简介 二、AIR780E驱动程序 三、AIR780使用注意事项 四、结合MQTT传输测试 一、4G模块简介 4G应该是我们日常生活最常见的一种互联网通讯方式了&#xff0c;每个智能手机都配置了&#xff0c;不过手机的4G跟我们物联网领域要用的4G有点区别。首先是物联网采用…

芯来科技、IAR和MachineWare携手加速符合ASIL标准RISC-V汽车芯片创新

支持软件开发团队在虚拟硬件平台上进行固件和MCAL开发 芯来科技&#xff08;Nuclei&#xff09;、IAR和MachineWare紧密合作&#xff0c;加速RISC-V ASIL合规汽车解决方案的创新。此次合作简化了汽车电子的固件和MCAL开发&#xff0c;提供了虚拟和物理硬件平台之间的无缝集成。…

Harmony鸿蒙南向驱动开发-SDIO

SDIO&#xff08;Secure Digital Input and Output&#xff09;由SD卡发展而来&#xff0c;与SD卡统称为MMC&#xff08;MultiMediaCard&#xff09;&#xff0c;二者使用相同的通信协议。SDIO接口兼容以前的SD卡&#xff0c;并且可以连接支持SDIO接口的其他设备。 运作机制 …

SpringBoot --pagehelper分页

目录 1.建立数据库 2.页面显示 3.基本逻辑 4.配置依赖 5.使用pagehelper 6.页面列表 页面 效果 1.建立数据库 create database if not exists my_book; use my_book; create table if not exists myBook (id int primary key auto_increment,name varchar(50) not …

Node.js常用快捷键

1.常用的终端命令&#xff1a; &#xff08;1&#xff09;del 文件名&#xff1a; 删除文件 &#xff08;2&#xff09;ipconfig: 查看IP命令 &#xff08;3&#xff09;mkdir 目录名 &#xff1a;在当前目录新建指定目录 &#xff08;4&#xff09;rd 目录名&#xff1a;在当前…