【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_pton和inet_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);
将整个结构体清零;设置地址类型为AF_INET;网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;端口号为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 serverportint 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的时候进行自动随机bind int 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为4 if (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 serverportint 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的时候进行自动随机bind int 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为4 if (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=cprivate: 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 那样采用请求应答模式。