1) 框架结构

2) Lars Reactor V0.1开发
我们首先先完成一个最基本的服务器开发模型,封装一个`tcp_server`类。
lars_reactor/include/tcp_server.h
#pragma once#include <netinet/in.h>class tcp_server{public://server的构造函数tcp_server(const char *ip, uint16_t port);//开始提供创建链接服务void do_accept();//链接对象释放的析构~tcp_server();private:int _sockfd; //套接字struct sockaddr_in _connaddr; //客户端链接地址socklen_t _addrlen; //客户端链接地址长度};
在tcp_server.cpp中完成基本的功能实现,我们在构造函数里将基本的socket创建服务器编程写完,然后提供一个阻塞的do_accept()方法。
lars_reactor/src/tcp_server.cpp
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <strings.h>#include <unistd.h>#include <signal.h>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <arpa/inet.h>#include <errno.h>#include "tcp_server.h"//server的构造函数tcp_server::tcp_server(const char *ip, uint16_t port){bzero(&_connaddr, sizeof(_connaddr));//忽略一些信号 SIGHUP, SIGPIPE//SIGPIPE:如果客户端关闭,服务端再次write就会产生//SIGHUP:如果terminal关闭,会给当前进程发送该信号if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {fprintf(stderr, "signal ignore SIGHUP\n");}if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {fprintf(stderr, "signal ignore SIGPIPE\n");}//1. 创建socket_sockfd = socket(AF_INET, SOCK_STREAM /*| SOCK_NONBLOCK*/ | SOCK_CLOEXEC, IPPROTO_TCP);if (_sockfd == -1) {fprintf(stderr, "tcp_server::socket()\n");exit(1);}//2 初始化地址struct sockaddr_in server_addr;bzero(&server_addr, sizeof(server_addr));server_addr.sin_family = AF_INET;inet_aton(ip, &server_addr.sin_addr);server_addr.sin_port = htons(port);//2-1可以多次监听,设置REUSE属性int op = 1;if (setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {fprintf(stderr, "setsocketopt SO_REUSEADDR\n");}//3 绑定端口if (bind(_sockfd, (const struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {fprintf(stderr, "bind error\n");exit(1);}//4 监听ip端口if (listen(_sockfd, 500) == -1) {fprintf(stderr, "listen error\n");exit(1);}}//开始提供创建链接服务void tcp_server::do_accept(){int connfd;while(true) {//accept与客户端创建链接printf("begin accept\n");connfd = accept(_sockfd, (struct sockaddr*)&_connaddr, &_addrlen);if (connfd == -1) {if (errno == EINTR) {fprintf(stderr, "accept errno=EINTR\n");continue;}else if (errno == EMFILE) {//建立链接过多,资源不够fprintf(stderr, "accept errno=EMFILE\n");}else if (errno == EAGAIN) {fprintf(stderr, "accept errno=EAGAIN\n");break;}else {fprintf(stderr, "accept error");exit(1);}}else {//accept succ!//TODO 添加心跳机制//TODO 消息队列机制int writed;char *data = "hello Lars\n";do {writed = write(connfd, data, strlen(data)+1);} while (writed == -1 && errno == EINTR);if (writed > 0) {//succprintf("write succ!\n");}if (writed == -1 && errno == EAGAIN) {writed = 0; //不是错误,仅返回0表示此时不可继续写}}}}//链接对象释放的析构tcp_server::~tcp_server(){close(_sockfd);}
好了,现在回到`lars_reactor`目录下进行编译。
$~/Lars/lars_reactor/$make
在lib下,得到了库文件。
接下来,做一下测试,写一个简单的服务器应用.
$cd ~/Lars/lars_reactor/example$mkdir lars_reactor_0.1$cd lars_reactor_0.1
lars_reactor/example/lars_reactor_0.1/Makefile
CXX=g++CFLAGS=-g -O2 -Wall -fPIC -Wno-deprecatedINC=-I../../includeLIB=-L../../lib -llreactorOBJS = $(addsuffix .o, $(basename $(wildcard *.cc)))all:$(CXX) -o lars_reactor $(CFLAGS) lars_reactor.cpp $(INC) $(LIB)clean:-rm -f *.o lars_reactor
lars_reactor/example/lars_reactor_0.1/lars_reactor.cpp
#include "tcp_server.h"int main() {tcp_server server("127.0.0.1", 7777);server.do_accept();return 0;}
接下来,我们make进行编译,编译的时候会指定链接我们刚才生成的liblreactor.a库。
服务端:
$ ./lars_reactorbegin accept
客户端:
$nc 127.0.0.1 7777hello Lars
得到了服务器返回的结果,那么我们最开始的0.1版本就已经搭建完了,但是实际上这并不是一个并发服务器,万里长征才刚刚开始而已。
