网络编程套接字(三) 简单的TCP英译汉服务器

发布于:2024-05-23 ⋅ 阅读:(39) ⋅ 点赞:(0)

网络编程套接字(二)中我们实现了简单的多执行流Tcp服务器。

如果想要让这里的TCP服务器处理其他任务,只需要修改对应的处理函数即可。对应到最终实现的线程池版本的TCP服务器,我们要修改的其实就只是任务类当中的handler方法。下面我们以实现简单的TCP英译汉服务器为例,看看更改后我们的TCP服务器能否正常为客户端提供英译汉服务。

简单的TCP英译汉服务器

封装Translate.hpp

英译汉TCP服务器要做的就是,根据客户端发来的英文单词找到其对应的中文意思,然后将该中文意思作为响应数据发给客户端。

创建一个recourse目录,里面创建dict.txt用来存放单词
在这里插入图片描述

然后对查找过程进行封装

//Translate.hpp
const std::string unknown = "unknown";
const std::string mydict = "./recourse/dict.txt";  //存放资源的路径
const std::string sep = " ";

class Translate
{
public:
    Translate(std::string dict_path = mydict) : _dict_path(dict_path)
    {
        LoadDict();
        Parse();
    }

    void LoadDict() //存放vector
    {
        std::ifstream in(_dict_path);
        std::string line;
        while(std::getline(in,line))
        {
            lines.push_back(line);
        }
        in.close();
    }

    void Parse()  //存放unordered_map
    {
        for(auto &line : lines)
        {
            auto pos = line.find(sep);//XXX YYY
            if(pos == std::string::npos) continue;
            else
            {
                std::string word = line.substr(0,pos);
                std::string chinese  = line.substr(pos+sep.size());
                _dict.insert(std::make_pair(word,chinese));
            }

        }
    }

    std::string Excute(const std::string &word) //查找
    {
        auto iter = _dict.find(word);
        if(iter == _dict.end())
        {
            return unknown;
        }
        else    
            return _dict[word];
    }

    ~Translate()
    {
    }

private:
    std::string _dict_path;
    std::unordered_map<std::string,std::string> _dict;
    std::vector<std::string> lines;
};

修改TcpServer.hpp中的Service

只需要修改Service里面write给客户端写入数据那部分即可

#include "Translate.hpp"

using namespace std;

using task_t = std::function<void()>;
const static int default_backlog = 5; // TODO

Translate trans; //创建一个trans对象进行查找

class TcpServer;

class TcpServer
{
public:

    // Tcp 连接全双工通信的.
    void Service(int sockfd)
    {
        char buffer[1024];
        // 一直进行IO
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                //std::cout << buffer << endl;
                // std::string echo_string = "server echo# ";
                // echo_string += buffer;
                // write(sockfd, echo_string.c_str(), echo_string.size());
                std::string value = trans.Excute(buffer);
                write(sockfd,value.c_str(),value.size());
            }
            else if (n == 0) // read如果返回值是0,表示读到了文件结尾(对端关闭了连接!)
            {
                cout << "client quit...\n" << endl;
                break;
            }
            else
            {
                cout<< "read socket error" << endl;
                break;
            }
        }
    }

    static void *HandlerRequest(void *args) // this
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->GetServer()->Service(td->SockFd());
        delete td;
        return nullptr;
    }
   
private:
    uint16_t _port;
    int _listensock; // TODO
    bool _isrunning;
};

代码测试

现在这个TCP服务器就能够给客户端提供英译汉的服务了,客户端发来的单词如果能够在映射表当中找到,那么服务端会将该单词对应发中文意思响应给客户端,否则返回unknow
在这里插入图片描述

地址转换函数

inet_ntoa函数

int inet_aton(const char *cp, struct in_addr *inp);

参数说明:

  • cp:待转换的字符串IP。
  • inp:转换后的整数IP,这是一个输出型参数。

返回值说明:

  • 如果转换成功则返回一个非零值,如果输入的地址不正确则返回零值。

inet_addr函数

in_addr_t inet_addr(const char *cp);

参数说明:

  • cp:待转换的字符串IP。

返回值说明:

  • 如果输入的地址有效,则返回转换后的整数IP;如果输入的地址无效,则返回INADDR_NONE(通常为-1)。

inet_pton函数

int inet_pton(int af, const char *src, void *dst);

参数说明:

  • af:协议家族。
  • src:待转换的字符串IP。
  • dst:转换后的整数IP,这是一个输出型参数。

返回值说明:

  • 如果转换成功,则返回1。
  • 如果输入的字符串IP无效,则返回0。
  • 如果输入的协议家族af无效,则返回-1,并将errno设置为EAFNOSUPPORT。

整数IP转字符串IP

inet_ntoa函数

char *inet_ntoa(struct in_addr in);

参数说明:

  • in:待转换的整数IP。

返回值说明:

  • 返回转换后的字符串IP。

inet_ntop函数

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数说明:

  • af:协议家族。
  • src:待转换的整数IP。
  • dst:转换后的字符串IP,这是一个输出型参数。
  • size:用于指明dst中可用的字节数。

返回值说明:

  • 如果转换成功,则返回一个指向dst的非空指针;如果转换失败,则返回NULL。

说明:

  • 我们最常用的两个转换函数是inet_addr和inet_ntoa,因为这两个函数足够简单。这两个函数的参数就是需要转换的字符串IP或整数IP,而这两个函数的返回值就是对应的整数IP和字符串IP。
  • 其中inet_pton和inet_ntop函数不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此这两个函数中对应的参数类型是void*。
  • 实际这些转换函数都是为了满足某些打印场景的,除此之外,更多的是用来做某些数据分析,比如网络安全方面的数据分析。

TCP协议通讯流程

通讯流程总览

下图是基于TCP协议的客户端/服务器程序的一般流程:

在这里插入图片描述
下面我们结合TCP协议的通信流程,来初步认识一下三次握手和四次挥手,以及建立连接和断开连接与各个网络接口之间的对应关系。

三次握手的过程

在这里插入图片描述
服务器初始化:

  • 调用socket,创建文件描述符。
  • 调用bind,将当前的文件描述符和IP/PORT绑定在一起,如果这个端口已经被其他进程占用了,就会bind失败。
  • 调用listen,声明当前这个文件描述符作为一个服务器的文件描述符,为后面的accept做好准备。
  • 调用accept,并阻塞,等待客户端连接到来。

这个建立连接的过程,通常称为三次握手。

注意: 连接并不是立马建立成功的,由于TCP属于传输层协议,因此在建立连接时双方的操作系统会自主进行三次协商,最后连接才会建立成功。

数据传输的过程

在这里插入图片描述
数据传输的过程:

  • 建立连接后,TCP协议提供全双工的通信服务,所谓全双工的意思是,在同一条连接中,同一时刻,通信双方可以同时写数据,相对的概念叫做半双工,同一条连接在同一时刻,只能由一方来写数据。
  • 服务器从accept返回后立刻调用read,读socket就像读管道一样,如果没有数据到达就阻塞等待。
  • 这时客户端调用write发送请求给服务器,服务器收到后从read返回,对客户端的请求进行处理,在此期间客户端调用read阻塞等待服务器端应答。
  • 服务器调用write将处理的结果发回给客户端,再次调用read阻塞等待下一条请求。
  • 客户端收到后从read返回,发送下一条请求,如此循环下去。

四次挥手的过程

在这里插入图片描述
断开连接的过程:

  • 如果客户端没有更多的请求了,就调用close关闭连接,客户端会向服务器发送FIN段(第一次)。
  • 此时服务器收到FIN后,会回应一个ACK,同时read会返回0(第二次)。
  • read返回之后,服务器就知道客户端关闭了连接,也调用close关闭连接,这个时候服务器会向客户端发送一个FIN(第三次)。
  • 客户端收到FIN,再返回一个ACK给服务器(第四次)。

在学习socket API时要注意应用程序和TCP协议层是如何交互的:

  • 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect会发出SYN段。
  • 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read返回0就表明收到了FIN段。

TCP和UDP对比

在这里插入图片描述