网络基础2(一)
1.前言在正式开始前先说一个问题这有读和写数据。TCP是面向字节流的那怎么保证读取上来的数据是一个完整的报文呢比如对方发了你好可能我们这只收到了‘你’字。TCP叫传输控制协议控制什么时候发、发多少、出错了怎么办由TCP来全全控制发送问题。我平时用的read、write都仅仅是用户到内核空间的拷贝函数也就是作为用户只需要在应用层把数据通过write写到TCP的发送缓冲区此时write调用直接返回但这时数据不一定已经发给对方了。也就是说用户只要把数据拷贝到TCP缓冲区里它的任务就算完了数据不一定发送到网络里。所以write这样的接口以及send、receve等接口不叫做网络收发它们真正作用就是用户到内核拷贝决定网络收发的协议是由TCP决定的。因为只有TCP协议了解网络当前的健康状态和接收方的接收能力所以TCP必须全全负责数据什么时候发发多少出错了怎么办。TCP是操作系统的网络模块部分用户把数据交给TCP缓冲区就是用户把数据交给了操作系统操作系统会做管理(我们把数据最终发到对端网络如同文件那把数据从内存缓冲区刷新到磁盘一样)。下面谈谈接收对于接收方来讲通过read调用说白了就是把数据从接收缓冲区拷贝到用户层缓冲区。对方发来了多少数据什么时候发的对方接收缓冲区来说接收到的数据完全由操作系统决定。对方可能把发的整个字符串分了三份发过来也可能把多次发的数据整体打包发过来。读的时候有没有读到应用层认识的完整报文是完全不确定的因此需要读端对读上来的数据进行更细节的分析所以要在应用层把协议定好来保证了因此应用层是要协议定制序列化和反序列化的。2.应用层下面谈一下应用层我们前面写的套接字代码都是在做应用层编程我们一直在定协议只不过定出来的协议比较草率。如我们规定好你给我发单词我给你翻译这都是通信双方约定的其实已经定协议了但远远不够。协议是一种约定我们平时通信时、读写数据时收发双方都是按字符串进行收发的如果我们发结构化的数据怎么办呢比如现在实现一个服务器版的计算器用户把两个数发过去服务器计算后把结果返回给客户。首先是约定方案一约定好把字符串发给对方对方根据字符串把数据解析出来解成一个1一个一个1最后运算完返回结果。这种做法其实不太好自己得做解析处理更重要的是客户端比如连发了4、5个报文到tcp接收缓冲区了读的时候一次性把4、5个都读了那怎么区分这是很多请求而不是1个请求呢所以要做各种分析。还有个方案定义个结构体知道网络是面向字节流的定义好结构体Request和Response发时拿request定义个变量直接把8字节发过去对方使用同样request类型就能提取a和b。这样发对象就可完整通信操作系统内是这样干的(内核协议一样)但应用层不太建议这样做。因为通常一个结构体在不同编译器下编译大小不一定是一样的而且自己要区分发来的是1个还是多个请求。接收缓冲区满了所有请求都写到发送缓冲区接收缓冲区好了后一下子把内容都读到发出去上面是要区分是几个报文。应用层定协议我们需要个关键字段此时定义好了两种协议。整个结构体中每一个结构体的每个字段每个字段里的每一种值我们都是要让客户端和服务器约定好的约定好后使用结构化的方式把约定表达出来这就叫做定义出了协议。规定好未来所有对应的request在客户端和服务器通信时它的表达式传过来永远都是x op y结果永远放result中结果是否正确放code里这样双方就约定好了。客户端给服务器发消息发送struct request req {1,2,/}填充好字段后对方可以接收。上面理论上可以但客户端可能有很多版本不好保证和服务器结构体大小一样所以不建议跨主机使用结构体对象来进行通信。下面聊个问题比如在QQ群里面聊天我在群里发了一句话大家可以在群里看到的消息不只是我发的话还有昵称、发送时间。虽然看起来我发了一句话我发的话、昵称、时间都是字符串事实是收到三个字符串。这三个字符串不是一个一个发的要不然这消息谁说的可能服务端都不清楚。我们一定要把这三个字符串变成一个字符串整体转发到所有人的客户端上然后再把一个字符串转化成三个字符串从群上显示出来如我们平时发消息时QQ内部要发一个msg把对应的信息写进去再把结构化数据转成字符串把这一个字符串经过网络发给对方主机。对方主机收到后也要识别struct msg想显示就访问结构体这里也是约定好了客户端和服务器的通用结构体这就叫定了一个聊天协议。网络通信时整个结构化的数据中各个字符串转为一个字符串的过程我们称为序列化把一个字符串打散为多个字符串转为结构化数据我们称为反序列化应用层最常用的就是这种方式我们把结构那叫协议的定制下面叫序列化反序列化序列反序列化目的是方便网络进行收发3.网络版计算器接下来要做的网络版的计算器也是一样的定好协议后先进行序列化对方收到后进行反序列化。有了request结构化数据后拿出里面的值可以制作response再逆向做一次上述工作下面来编写代码要完成网络通信的整套过程网络功能是必不可少的这把以前用过的套接字分装一下直接用首先我们要有个TCP服务器先写网络功能把套接字进行封装未来TCP服务器构建好对象创建等直接调用对客户端来说创建套接字后连接下面来完善填写填好后直接用至此把TCP服务器和套接字很快完成了。接下来完成服务处理(这用多进程这里忽略了父进程不用等待)让子进程进行数据计算大框架完成。下面先来定制协议我们要定制好双方都能认识的数据类型提前规定好每个类型的含义未来双方基于Request发起请求Response拿结果。协议定好后对于Request和Response要有序列化和反序列化的功能先写Request的序列化是把结构化的数据转成字符串转成什么字符串呢这做一个简单的约定转成x op y这样未来对方接收缓冲区收到“10 20”对方按空格解析为反序列化再处理都没问题。但接收缓冲区放了很多对方在read时怎么保证读到完整的报文有没有可能读一个半之类的。这可以简单一些处理规定好请求和请求之间用特殊符号把它们分隔开比如采用\n读的时候碰到\n说明\n之前的字符就全都收到了这样报文与报文之间就隔开了。这样已经能解决这里的问题了但在这还想在整个报文前再加个字段表示长度意思是后面报文长度是多少不包括\n这样读上来分析到长度时可知道把目标串开始后多少个字符给截出来。如果愿意还可带一个字段表示协议选择比如为1是整型计算为2是浮点数计算。这里我们这样写比如有很多消息时发过去的风格。将来都读上去后读到第一个\n知道报文长度是9然后从第一个\n往后读9个字符就可以读到完整报文了。后面中间\n其实不要也行但带上可以方便以后cout一下进行调试时看得清晰。这样有了长度加有效载荷同理Response也想达到同样的效果可通过函数完成相应工作这样序列化只需把内部字段转一下转换完后未来想经过网络发送通过Encode加报文长度和分割符。Response也是一样接下来进行反序列化在反序列化之前一定会收到“len”\n“x op y”\n这样的报文因此再提供一个 Decode提取出x op y然后再进行反序列化。假设写了先写反序列化这样把长字符串转为了结构化的数据。再对Response进行反序列化接下来对协议简单测试一下下面再完善Decode再测试一下(Request里加个空构造)至此把Request测试完了。下面再测一下Response目前完成了协议定制和序列化、反序列化的功能下面要把网络和协议结合起来。再添加servercal.hppTcpserver.hpp是进行网络通信服务的Servercal.hpp要利用网络服务器把协议和网络功能提一起然后把server.hpp交付给main函数下面写ServerCal.hpp得到了一个计算器结果再把结果返回这样就完成了calculator接口。怎么在网络中使用子进程通过sockfd进行读取此时从网络里收到一个数据。再然后初始化好后去回调得到结果最后写回去(一会说)。继续看现在服务器起来后网络中收到请求处理后去回调callback是传的Calcultaor方法(子进程写十三是方便读不完整继续读)。下面继续完成计算经过这样的代码可以根据Request形成Response进行返回返回时调用默认的拷贝构造至此Calcultor里计算完毕得到resp然后进行序列和Encode返回。它最终作为回调方法设置进TcpServer回调后把传入的信息流交给calbackinfo就是我们接下来要写的内容这样就把结果写回去了读出错break。改一下空串说明解析处理不正确所以直到正确再往后走。完善一下ServerCal的代码下面测试一下5回车后调用calback然后calback内部Decode找到子串后看到后面长度不够false返回失败后继续返回空串判断是空的继续去读取。继续看错误后得到4号错误码报了Other_op错误。下面聊个事情读上来了一个数据到buffer里此时并不清楚buffer里是很多报文还是不完整的报文若交给Calback不完整处理失败得到空串就继续continue。现在模拟一下不遵守协议的现象发现Debug打出的消息越来越长因为没有遵照协议服务器读取时是12(read返回值)的把读到的buffer加等到inbuffer_stream。没有遵守协议解析失败返回是空的就一直continue所以inbuffer_stream变得越来越长。Calcultor是收到一个个package首先进入Decode先寻找报头找不到就返回false。找到报头并计算报文长度长度不符合也返回false再往后走package一定包含一个完整报文。如果此时已经得到了完整的报文一方面这个报文已经被我们提取出去到content了另一方面移除流中信息如果这样Decode成功后package里就把收到的多个请求中的一个完整的请求移除了。下面来编写客户端(不像协议那样封装了)将来要./clientcal ip port这样输入因此有了ip和port就开始创建套接字和链接连好后客户端也要遵守相应协议包好头文件给些计算值(为了方便调式Request里写个接口)这样构建好了请求。接下来要把请求根据协议发送给服务器至此把请求给服务端写过去了。写完后客户端要读运算结果客户端read时也无法保证能读到一个完整报文所以也处理(Response也加debug接口)客户端的工作就是创建套接字发起连接形成随机请求写给对方读到结果后解码再反序列化。下面测试一下有可能客户端连续给服务器发多个请求现在服务器read时不清楚读了多少反正会解析一个然后把结果写回去再继续去读。若想一次处理完请求可这样改这样把一次读上来的都尽可能处理完。再改一下下面测试一下服务端收到了很多请求处理一个报文就短了一个所以服务器可处理多个报文。以上是手动写的序列化反序列化其实还有现呈的可自动序列和反序列化如jsonprotobuf。序列和反序列化工作让我们自己手写有些麻烦我们可用json或protobuf就行。json一般用来进行可视化的序列反序列化方便调试protobuf更多的是强调效率。今天简单用一下jsonC中要用json需要安装一个库要装别人的库是在系统中安装了头文件还有库文件。头文件一般被安装在这里库安装在这系统只认识/usr/include这个文件路径。下面简单用一下试一下它的序列化功能json中存在一个万能对象叫value它可以接收很多类型这儿定义个root。JSON是种K-V形式里面还重载方括号因此有了结构化的数据。下面想办法序列化定义writer对象序列化调writer方法下面测试一下json串里面已经把内容结构化成了字符串每个部分用逗号隔开这就完成了序列化。还有一种序列化它是按分隔形式序列化字符串把一个个条目用逗号隔开。那如何进行反序列化反序列化还是一样用万能对象value还要用 reader和它的parse方法第一个参数表示要反序列化的字符串第二个表示把反序列化的结果放哪个value对象里第三个参数直接缺省最后提数据当成整数提当成字符串提此时就完成了反序列化。下面测试一下再补充一下json可以套json。下面改一下前面的代码条件编译以后想用自己的也行不用就用json。先对Request做序列化序列化后out被带出去了做Encode无非就是把之前的空格风格换成了json风格。再来反序列化对Response也是一样的再次完善下面测试一下下面再这样测试这样完成了把序列化、反序列化换成json版本。从中也可体会到带len的好处这样不管正文怎么变都可拿着len直接全部提取完在Decode中至此完成了序列化反序列化的功能。最后回过头谈一下前面的Makefie定义的宏放开就可以无障碍在底层把序列化反序列化方案换成自定义做法。我们还可以把计算机服务守护进程化这样都重定向到/dev/null。还可以尝试用系统中的守护进程4.重谈OSI七层模型下面重谈OSI的7层模型物理、链路、网络我们暂时不接触传输层用了一下socket接口。会话层是负责建立断开链接管理的这个层相当于前面代码中TcpServer.hpp中获取新链接再由子进程维护连接使用情况。创建一个子进程对外提供服务相当于每次访问服务都建立了一个新的会话。表示层就是我们定义的协议这就是固有的数据格式序列化、反序列化添加报头动作就是格式的转换。应用层相当于CalculatorHelper这里的处理数据有每个字段解释怎么计算相当于应用上的协议上。上三层不能统一实现在操作系统因为随着场景在变。5.完整代码//Clientcal.cc #include iostream #include assert.h #include string #include Socket.hpp #include Protocol.hpp void Usage(const std::string proc) { std::cout \nUsage: proc serverip serverport\n std::endl; } // ./clientcal ip port int main(int argc, char *argv[]) { if (argc ! 3) { Usage(argv[0]); exit(0); } std::string serverip argv[1]; uint16_t serverport std::stoi(argv[2]); Sock sockfd; sockfd.Socket(); bool r sockfd.Connect(serverip, serverport); if (!r) return 1; srand(time(nullptr) ^ getpid()); int cnt 1; const std::string opers -*/%; std::string inbuffer_stream; while (cnt 10) { std::cout 第 cnt 次测试...., std::endl; int x rand() % 100 1; usleep(1234); int y rand() % 100; usleep(4321); char oper opers[rand()%opers.size()]; Request req(x, y, oper); req.DebugPrint(); std::string package; req.Serialize(package); package Encode(package); write(sockfd.Fd(), package.c_str(), package.size()); // std::cout 这是最新发出去的请求: n \n package; // n write(sockfd.Fd(), package.c_str(), package.size()); // std::cout 这是最新发出去的请求: \n n \n package; // n write(sockfd.Fd(), package.c_str(), package.size()); // std::cout 这是最新发出去的请求: \n n \n package; // n write(sockfd.Fd(), package.c_str(), package.size()); // std::cout 这是最新发出去的请求: \n n \n package; char buffer[128]; ssize_t n read(sockfd.Fd(), buffer, sizeof(buffer)); //我们也无法保证我们能读到一个完整的报文 if (n 0) { buffer[n] 0; inbuffer_stream buffer; //len\nresult code\n std::string content; bool r Decode(inbuffer_stream, content); // result code assert(r); Response resp; r resp.Deserialize(content); assert(r); resp.DebugPrint(); } std::cout std::endl; sleep(1); cnt; } sockfd.Close(); return 0; }//Daemon.hpp #pragma once #include iostream #include string #include cstdlib #include cstring #include unistd.h #include sys/wait.h #include sys/types.h #include sys/stat.h #include fcntl.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include pthread.h #include signal.h #include signal.h const std::string nullfile /dev/null; void Daemon(const std::string cwd ) { //1.忽略其它异常信号 signal(SIGCLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); signal(SIGSTOP, SIG_IGN); //2.将自己变成独立会话 if (fork() 0) exit(0); setsid(); //3.更改当前调用进程的工作目录 if (!cwd.empty()) chdir(cwd.c_str()); //4.标准输入、标准输出、标准错误重定向至/dev/null int fd open(nullfile.c_str(), O_RDWR); if (fd 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } }//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 logmessage(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\n, leftbuffer, rightbuffer); // // printf(%s, logtxt); // 暂时打印 // printLog(level, logtxt); // } 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; }; Log lg; // int sum(int n, ...) // { // va_list s; // char* // va_start(s, n); // int sum 0; // while(n) // { // sum va_arg(s, int); // printf(hello %d, hello %s, hello %c, hello %d,, 1, hello, c, 123); // n--; // } // va_end(s); //s NULL // return sum; // }//Makefile .PHONY:all all:servercal clientcal Flag-DMySelf1 Lib-ljsoncpp servercal:ServerCal.cc g -o $ $^ -stdc11 $(Lib) #$(Flag) clientcal:ClientCal.cc g -o $ $^ -stdc11 -g $(Lib) #$(Flag) .PHONY:clean clean: rm -f clientcal servercal//Protocol.hpp #pragma once #include iostream #include jsoncpp/json/json.h //#define MySelf 1 const std::string blank_space_sep ; const std::string protocol_sep \n; std::string Encode(std::string content) { std::string package std::to_string(content.size()); package protocol_sep; package content; package protocol_sep; return package; } // len\nx op y\n bool Decode(std::string package, std::string *content) { std::size_t pos package.find(protocol_sep); if (pos std::string::npos) return false; std::string len_str package.substr(0, pos); std::size_t len std::stoi(len_str); std::size_t total_len len_str.size() len 2; if (package.size() total_len) return false; *content package.substr(pos 1, len); package.erase(0, total_len); return true; } class Request { public: Request(int data1, int data2, char oper):x(data1), y(data2), op(oper) {} Request() {} public: bool Serialize(std::string *out) { #ifdef MySelf //构建报文有效载荷 std::string s std::to_string(x); s blank_space_sep; s op; s blank_space_sep; s std::to_string(y); *out s; return true; #else Json::Value root; root[x] x; root[y] y; root[op] op; Json::FastWriter w; *out w.write(root); return true; #endif } bool Deserialize(const std::string in) // x op y { #ifdef MySelf std::size_t left in.find(blank_space_sep); if (left std::string::npos) return false; std::string part_x in.substr(0, left); std::size_t right in.rfind(blank_space_sep); if (right std::string::npos) return false; std::string part_y in.substr(right 1); if (left 2 ! right) return false; op in[left 1]; x std::stoi(part_x); y std::stoi(part_y); return true; #else Json::Value root; Json::Reader r; r.parse(in, root); x root[x].asInt(); y root[y].asInt(); op root[op].asInt(); return true; #endif } void DebugPrint() { std::cout 请求构建完成 x op y ? std::endl; } public: //x op y int x; int y; char op; // * / % }; class Response { public: Response(int res, int c):result(res), code(c) {} Response() {} public: bool Serialize(std::string *out) { #ifdef MySelf //len\nresult code //构建报文的有效载荷 std::string s std::to_string(result); s blank_space_sep; s std::to_string(code); *out s; return true; #else Json::Value root; root[result] result; root[code] code; Json::FastWriter w; *out w.write(root); return true; #endif } bool Deserialize(const std::string in) //result code { #ifdef MySelf std::size_t pos in.find(blank_space_sep); if (pos std::string::npos) return false; std::string part_left in.substr(0, pos); std::string part_right in.substr(pos 1); result std::stoi(part_left); code std::stoi(part_right); return true; #else Json::Value root; Json::Reader r; r.parse(in, root); result root[result].asInt(); code root[code].asInt(); return true; #endif } void DebugPrint() { std::cout 结果响应完成, result: result , code: code std::endl; } public: int result; int code; // 0,可信否则!0具体是几表明对应的错误原因 };//ServerCal.cc #include TcpServer.hpp #include ServerCal.hpp #include Daemon.hpp #include unistd.h static void Usage(const std::string proc) { std::cout \nUsage: proc port\n\n std::endl; } // ./servercal 8080 int main(int argc, char *argv[]) { if (argc ! 2) { Usage(argv[0]); exit(0); } uint16_t port std::stoi(argv[1]); ServerCal cal; TcpServer *tsvp new TcpServer(port, std::bind(ServerCal::Calculator, cal, std::placeholders::_1)); tsvp-InitServer(); //Daemon(); daemon(0, 0); tsvp-Start(); return 0; } // int main() // { // Response resp(1000, 0); // std::string content; // resp.Serialize(content); // std::cout content std::endl; // std::string package Encode(content); // std::cout package; // content ; // bool r Decode(package, content); // std::cout content std::endl; // Response temp; // temp.Deserialize(content); // std::cout temp.result std::endl; // std::cout temp.code std::endl; // return 0; // } // int main() // { // Request req(12354566, 43454356, ); // std::string s; // req.Serialize(s); // s Encode(s); // std::cout s std::endl; // std::string content; // bool r Decode(s, content); // std::cout content std::endl; // Request temp; // temp.Deserialize(content); // std::cout temp.x std::endl; // std::cout temp.op std::endl; // std::cout temp.y std::endl; // return 0; // }//ServerCal.hpp #pragma once #include iostream #include Protocol.hpp enum{ Div_Zero 2, Mod_Zero, Other_Oper }; class ServerCal { public: ServerCal() {} Response CalculatorHelper(const Request req) { Response resp(0, 0); switch(req.op) { case : resp.result req.x req.y; break; case -: resp.result req.x - req.y; break; case *: resp.result req.x * req.y; case /: { if (req.y 0) resp.code Div_Zero; else resp.result req.x / req.y; } break; case %: { if (req.y 0) resp.code Mod_Zero; else resp.result req.x % req.y; } break; default: resp.code Other_Oper; break; } return resp; } std::string Calculator(std::string package) { std::string content; bool r Decode(package, content); //len\n10 20\n if (!r) return ; //10 20 Request req; r req.Deserialize(content); //10 20 - x10 op y20 if (!r) return; content ; Response resp CalculatorHelper(req); //result30 code0 resp.Serialize(content); //30 0 content Encode(content); //len\n30 0 return content; } ~ServerCal() {} };//Socket.hpp #pragma once #include iostream #include unistd.h #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include string.h #include signal.h #include Log.hpp enum{ SocketErr 2, BindErr, ListenErr }; //后面说 const int backlog 10; class Sock { public: Sock() {} ~Sock() {} public: void Socket() { sockfd_ socket(AF_INET, SOCK_STREAM, 0); if (sockfd_ 0) { lg(Fatal, socker error, %s: %d, strerror(errno), errno); exit(SocketErr); } } void Bind(uint16_t port) { struct sockaddr_in local; memset(local, 0, sizeof(local)); local.sin_family AF_INET; local.sin_port htons(port); local.sin_addr.s_addr INADDR_ANY; if (bind(sockfd_, (struct sockaddr*)local, sizeof(local)) 0) { lg(Fatal, bind error, %s: %d, strerror(errno), errno); exit(BindErr); } } void Listen() { if (listen(sockfd_, backlog) 0) { lg(Fatal, listen error, %s: %d, strerror(errno), errno); exit(ListenErr); } } int Accept(std::string* clientip, uint16_t* clientport) { struct sockaddr_in peer; socklen_t len sizeof(peer); int newfd accept(sockfd_, (struct sockaddr*)peer, len); if (newfd 0) { lg(Warning, accept error, %s: %d, strerror(errno), errno); return -1; } char ipstr[64]; inet_ntop(AF_INET, peer.sin_addr, ipstr, sizeof(ipstr)); *clientip ipstr; *clientport ntohs(peer.sin_port); return newfd; } bool Connect(const std::string ip, const uint16_t port) { struct sockaddr_in peer; memset(peer, 0, sizeof(peer)); peer.sin_family AF_INET; peer.sin_port htons(port); inet_pton(AF_INET, ip.c_str(), (peer.sin_addr)); int n connect(sockfd_, (struct sockaddr*)peer, sizeof(peer)); if (n -1) { std::cerr connect to ip : port std::endl; return false; } return true; } void Close() { close(sockfd_); } int Fd() { return sockfd_; } private: int sockfd_; };//TcpServer.hpp #pragma once #include functional #include Log.hpp #include Socket.hpp using func_t std::functionstd::string(std::string package); class TcpServer { public: TcpServer(uint16_t port, func_t callback):port_(port), callback_(callback) {} bool InitServer() { listensock_.Socket(); listensock_.Bind(port_); listensock_.Listen(); lg(Info, init server .... done); return true; } void Start() { signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); while (true) { std::string clientip; uint16_t clientport; int sockfd listensock_.Accept(clientip, clientport); if (sockfd 0) continue; lg(Info, accept a new link, sockfd: %d, clientip: %s, clientport: %d, sockfd, clientip.c_str(), clientport); //提供服务 if (fork() 0) { listensock_.Close(); std::string inbuffer_stream; //数据计算 while (true) { char buffer[1024]; ssize_t n read(sockfd, buffer, sizeof(buffer)); if (n 0) { buffer[n] 0; inbuffer_stream buffer; lg(Debug, debug:\n%s, inbuffer_stream.c_str()); while (true) { std::string info callback_(inbuffer_stream); if (info.empty()) break; lg(Debug, debug, response:\n%s, info.c_str()); lg(Debug, debug:\n%s, inbuffer_stream.c_str()); write(sockfd, info.c_str(), info.size()); } } else if (n 0) break; else break; } exit(0); } close(sockfd); } } ~TcpServer() {} private: uint16_t port_; Sock listensock_; func_t callback_; };
