1) 框架结构

Lars reactor

2) Lars Reactor V0.1开发

  1. 我们首先先完成一个最基本的服务器开发模型,封装一个`tcp_server`类。

lars_reactor/include/tcp_server.h

  1. #pragma once
  2. #include <netinet/in.h>
  3. class tcp_server
  4. {
  5. public:
  6. //server的构造函数
  7. tcp_server(const char *ip, uint16_t port);
  8. //开始提供创建链接服务
  9. void do_accept();
  10. //链接对象释放的析构
  11. ~tcp_server();
  12. private:
  13. int _sockfd; //套接字
  14. struct sockaddr_in _connaddr; //客户端链接地址
  15. socklen_t _addrlen; //客户端链接地址长度
  16. };
  1. tcp_server.cpp中完成基本的功能实现,我们在构造函数里将基本的socket创建服务器编程写完,然后提供一个阻塞的do_accept()方法。

lars_reactor/src/tcp_server.cpp

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <strings.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. #include <sys/types.h> /* See NOTES */
  8. #include <sys/socket.h>
  9. #include <arpa/inet.h>
  10. #include <errno.h>
  11. #include "tcp_server.h"
  12. //server的构造函数
  13. tcp_server::tcp_server(const char *ip, uint16_t port)
  14. {
  15. bzero(&_connaddr, sizeof(_connaddr));
  16. //忽略一些信号 SIGHUP, SIGPIPE
  17. //SIGPIPE:如果客户端关闭,服务端再次write就会产生
  18. //SIGHUP:如果terminal关闭,会给当前进程发送该信号
  19. if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
  20. fprintf(stderr, "signal ignore SIGHUP\n");
  21. }
  22. if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
  23. fprintf(stderr, "signal ignore SIGPIPE\n");
  24. }
  25. //1. 创建socket
  26. _sockfd = socket(AF_INET, SOCK_STREAM /*| SOCK_NONBLOCK*/ | SOCK_CLOEXEC, IPPROTO_TCP);
  27. if (_sockfd == -1) {
  28. fprintf(stderr, "tcp_server::socket()\n");
  29. exit(1);
  30. }
  31. //2 初始化地址
  32. struct sockaddr_in server_addr;
  33. bzero(&server_addr, sizeof(server_addr));
  34. server_addr.sin_family = AF_INET;
  35. inet_aton(ip, &server_addr.sin_addr);
  36. server_addr.sin_port = htons(port);
  37. //2-1可以多次监听,设置REUSE属性
  38. int op = 1;
  39. if (setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {
  40. fprintf(stderr, "setsocketopt SO_REUSEADDR\n");
  41. }
  42. //3 绑定端口
  43. if (bind(_sockfd, (const struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
  44. fprintf(stderr, "bind error\n");
  45. exit(1);
  46. }
  47. //4 监听ip端口
  48. if (listen(_sockfd, 500) == -1) {
  49. fprintf(stderr, "listen error\n");
  50. exit(1);
  51. }
  52. }
  53. //开始提供创建链接服务
  54. void tcp_server::do_accept()
  55. {
  56. int connfd;
  57. while(true) {
  58. //accept与客户端创建链接
  59. printf("begin accept\n");
  60. connfd = accept(_sockfd, (struct sockaddr*)&_connaddr, &_addrlen);
  61. if (connfd == -1) {
  62. if (errno == EINTR) {
  63. fprintf(stderr, "accept errno=EINTR\n");
  64. continue;
  65. }
  66. else if (errno == EMFILE) {
  67. //建立链接过多,资源不够
  68. fprintf(stderr, "accept errno=EMFILE\n");
  69. }
  70. else if (errno == EAGAIN) {
  71. fprintf(stderr, "accept errno=EAGAIN\n");
  72. break;
  73. }
  74. else {
  75. fprintf(stderr, "accept error");
  76. exit(1);
  77. }
  78. }
  79. else {
  80. //accept succ!
  81. //TODO 添加心跳机制
  82. //TODO 消息队列机制
  83. int writed;
  84. char *data = "hello Lars\n";
  85. do {
  86. writed = write(connfd, data, strlen(data)+1);
  87. } while (writed == -1 && errno == EINTR);
  88. if (writed > 0) {
  89. //succ
  90. printf("write succ!\n");
  91. }
  92. if (writed == -1 && errno == EAGAIN) {
  93. writed = 0; //不是错误,仅返回0表示此时不可继续写
  94. }
  95. }
  96. }
  97. }
  98. //链接对象释放的析构
  99. tcp_server::~tcp_server()
  100. {
  101. close(_sockfd);
  102. }
  1. 好了,现在回到`lars_reactor`目录下进行编译。
  1. $~/Lars/lars_reactor/
  2. $make

lib下,得到了库文件。

接下来,做一下测试,写一个简单的服务器应用.

  1. $cd ~/Lars/lars_reactor/example
  2. $mkdir lars_reactor_0.1
  3. $cd lars_reactor_0.1

lars_reactor/example/lars_reactor_0.1/Makefile

  1. CXX=g++
  2. CFLAGS=-g -O2 -Wall -fPIC -Wno-deprecated
  3. INC=-I../../include
  4. LIB=-L../../lib -llreactor
  5. OBJS = $(addsuffix .o, $(basename $(wildcard *.cc)))
  6. all:
  7. $(CXX) -o lars_reactor $(CFLAGS) lars_reactor.cpp $(INC) $(LIB)
  8. clean:
  9. -rm -f *.o lars_reactor

lars_reactor/example/lars_reactor_0.1/lars_reactor.cpp

  1. #include "tcp_server.h"
  2. int main() {
  3. tcp_server server("127.0.0.1", 7777);
  4. server.do_accept();
  5. return 0;
  6. }
  1. 接下来,我们make进行编译,编译的时候会指定链接我们刚才生成的liblreactor.a库。

服务端:

  1. $ ./lars_reactor
  2. begin accept

客户端:

  1. $nc 127.0.0.1 7777
  2. hello Lars

得到了服务器返回的结果,那么我们最开始的0.1版本就已经搭建完了,但是实际上这并不是一个并发服务器,万里长征才刚刚开始而已。