1. TCP Server类封装测试

目的:测试TCP服务器是否能够正常绑定、监听、接收新连接。

1.1 调用如下

  1. static Logger::ptr g_logger = KIT_LOG_ROOT();
  2. void run()
  3. {
  4. auto addr = Address::LookUpAny("0.0.0.0:8888");
  5. //特意测试一下Unix域套接字是否有问题
  6. auto addr2 = UnixAddress::ptr(new UnixAddress("./tmp/unix_addr"));
  7. KIT_LOG_INFO(g_logger) << "addr=" << addr->toString();
  8. KIT_LOG_INFO(g_logger) << "addr2=" << addr2->toString();
  9. std::vector<Address::ptr> in_addrs, out_addrs;
  10. in_addrs.push_back(addr);
  11. in_addrs.push_back(addr2);
  12. TcpServer::ptr server(new TcpServer);
  13. while(!server->bind(in_addrs, out_addrs))
  14. sleep(2);
  15. server->start();
  16. }
  17. int main()
  18. {
  19. IOManager iom("tcp_server");
  20. iom.schedule(&run);
  21. return 0;
  22. }

BUG:接收客户端新连接的时Socket::ptr == nullptr

现象:Socket类封装的accept返回了一个空的智能指针,但是返回的通信套接字的是大于0的,Socket对象初始化发生问题。
image.png
问题解决:GDB调试跟踪发现传入的fd参数有误,是个低级错误…….
image.png
image.png

1.2 运行结果

已经能够正确的识别两个不同类型地址的套接字,一个是IPv4地址、TCP类型的套接字;一个是Unix通信文件路径、TCP类型的套接字。
image.png

小BUG:Unix域套接字无法设置IPPROTO_TCP这一级别的参数

现象:centOS环境下似乎可以设置,ubuntu环境下设置不了

  1. #define PATH "./tmp/unix_addr"
  2. int main()
  3. {
  4. int fd = socket(AF_UNIX, SOCK_STREAM, 0);
  5. if(fd < 0)
  6. std::cout << "socket error" << std::endl;
  7. struct sockaddr_un sockaddr;
  8. bzero(&sockaddr, sizeof(struct sockaddr_un));
  9. sockaddr.sun_family = AF_UNIX;
  10. strcpy(sockaddr.sun_path, PATH);
  11. int n = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path);
  12. int val = 1;
  13. int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
  14. if(ret < 0)
  15. {
  16. std::cout << "setsockopt1 error, errno=" << errno
  17. << ", is:"<< strerror(errno) << std::endl;
  18. }
  19. ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
  20. if(ret < 0)
  21. {
  22. std::cout << "setsockopt2 error, errno=" << errno
  23. << ", is:" << strerror(errno) << std::endl;
  24. }
  25. return 0;
  26. }

image.png

2. 实例:echo_server

目的:构建一个相对标准的对这一套服务器框架接口使用的小实例,以加强对接口使用的理解以及排错。

使用:

  1. 继承tcp_server.cpp封装好的TCP服务器,重写虚函数handleClient()执行本次服务器中我们希望执行的服务:接收来自客户端的消息,并且缓存在内存池ByteArray类中,又从内存池中读出数据,并且打印验证。
  2. 写一个run()方法,在其中构建一个EchoServer对象,作为服务器一直被调度。绑定任意地址0.0.0.0+8888端口,作为服务器的对外通信网络地址。开启服务器运行,开始监听、接收新客户端的连接请求。
  • **echo_server.cpp** ```cpp

    include “../kit_server/tcp_server.h”

    include “../kit_server/Log.h”

    include “../kit_server/iomanager.h”

    include “../kit_server/socket.h”

    include “../kit_server/bytearray.h”

include

include

include

include

include

static kit_server::Logger::ptr g_logger = KIT_LOG_ROOT();

class EchoServer: public kit_server::TcpServer { public: typedef std::shared_ptr ptr; enum Type { UNKOWN, TEXT = 1, BIN = 2 };

  1. //标识输出的内容是二进制还是文本
  2. EchoServer(Type type);
  3. //处理已经连接的客户端 完成数据交互通信
  4. void handleClient(kit_server::Socket::ptr client);

private: Type m_type = Type::UNKOWN;

};

EchoServer::EchoServer(Type type) :m_type(type) {

}

//处理已经连接的客户端 完成数据交互通信 void EchoServer::handleClient(kit_server::Socket::ptr client) { KIT_LOG_INFO(g_logger) << “handleClient start, client=” << *client;

  1. kit_server::ByteArray::ptr ba(new kit_server::ByteArray);
  2. while(1)
  3. {
  4. ba->clear();
  5. std::vector<struct iovec> iovs;
  6. ba->getWriteBuf(iovs, 1024);
  7. int ret = client->recv(&iovs[0], iovs.size());
  8. if(ret == 0)
  9. {
  10. KIT_LOG_INFO(g_logger) << "client closed:" << *client;
  11. break;
  12. }
  13. else if(ret < 0)
  14. {
  15. KIT_LOG_ERROR(g_logger) << "client error, errno=" << errno
  16. << ", is:" << strerror(errno);
  17. break;
  18. }
  19. ba->setPosition(ba->getPosition() + ret);
  20. ba->setPosition(0);
  21. if(m_type == Type::TEXT)
  22. {
  23. // KIT_LOG_INFO(g_logger) << "recv msg:" << ba->toString();
  24. std::cout << "recv msg:" << ba->toString() << std::endl;
  25. }
  26. else
  27. {
  28. // KIT_LOG_INFO(g_logger) << "recv msg:" << ba->toHexString();
  29. std::cout << "recv msg:" << ba->toHexString() << std::endl;
  30. }
  31. }

}

EchoServer::Type type = EchoServer::Type::TEXT;

void run() { EchoServer::ptr server(new EchoServer(type));

  1. auto addr = kit_server::Address::LookUpAny("0.0.0.0:8888");
  2. while(!server->bind(addr))
  3. sleep(1);
  4. server->start();

}

int main(int argc, char* argv[]) { if(argc < 2) { KIT_LOG_INFO(g_logger) << “used as:” << argv[0] << “-t] or [“ << argv[0] << “-b]”; return 0; }

  1. if(strcmp(argv[1], "-b") == 0)
  2. {
  3. type = EchoServer::Type::BIN;
  4. }
  5. kit_server::IOManager iom("echo_server", 2);
  6. iom.schedule(&run);
  7. return 0;

} ```

2.1 小测试1:使用telnet本地回环连接服务器

$ telnet 127.0.0.1 8888
image.png
可以看到服务器已经收到消息,并且能够成功写入内存池,又从内存池读出,并且完整显示。
image.png

2.2 小测试2:使用Web浏览器连接服务器

由于是从物理主机的浏览器上键入网络地址直接访问,需要将虚拟机Ubuntu上的防火墙关闭,打开外来连接的权限才能访问成功,此处略。

可以看到服务器成功收到来自浏览器的连接,并且收到HTTP的请求报文。
image.png