Socket编程TCP

V1- Echo Server

Inite

UDP一样,TCP我们也学着使用他的接口来实现一些简单的网络通信功能。

首先是实现echo功能,实际上我们后面实现的也是echo功能,只不过对他改进而已。

首先看到我们main函数的主逻辑:

那么接下来就开始实现TcpServer的封装。

首先我们要创建套接字,tcp这里创建的套接字我们称为监听套接字 ,具体原因后面阐述。

然后我们需要选择socket的第二个参数为:

这是有连接、面向字节流的数据传输方式,正对应我们的tcp协议。

随后自然是绑定:

然后和UDP不同的是,TCP是面向连接的通信,因此我们要先建立连接.我们在服务端获取连接用的函数是listen:

第一个参数传入我们的监听套接字,第二个参数目前不做讨论,传入8.

完整初始化逻辑:

cpp 复制代码
void InitServer()
    {
        int listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (listensockfd < 0)
        {
            LOG(FATAL, "socket create error\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "socket create success\n");

        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(listensockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            LOG(FATAL, "bind failed\n");
            exit(BIND_ERROR);
        }

        if (listen(listensockfd, gblcklog) < 0)
        {
            LOG(FATAL, "listen erroe\n");
            exit(LISTEN_ERR);
        }
        LOG(INFO, "listen success\n");
    }

Loop

获取连接之后,我们还要建立连接,这里用的函数就是accept

这里要注意我们的返回值:

没错,返回了一个文件描述符。这里才是我们通信的地方。

一开始创建的套接字只是源源不断监听有没有客户端向我们建立连接的,因此叫做监听套接字。

建立完连接就要开始实现我们的功能:

Service

由于我们的TCP协议是面向字节流的,我们可以直接用write和read函数向文件缓冲区写和读:

因为这样的逻辑已经反复写过很多遍就不做赘述了。

Clinet

首先还是老一套的逻辑,我们必须知道服务器的ip地址和端口号:

然后我们要向服务器建立连接,这里客户端建立连接的函数用connect:

最后向sockfd写入即可:

cpp 复制代码
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage:" << argv[0] << "server-ip server-port" << std::endl;
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    ::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);

    int n = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        std::cerr << "connect error" << std::endl;
        exit(2);
    }

    while (true)
    {
        std::string message;
        std::cout << "Enter #";
        std::getline(std::cin, message);

        write(sockfd, message.c_str(), message.size());

        char echo_buffer[1024];
        n = read(sockfd, echo_buffer, sizeof(echo_buffer) - 1);
        if (n > 0)
        {
            echo_buffer[n] = 0;
            std::cout << echo_buffer << std::endl;
        }
    }

    ::close(sockfd);

    return 0;
}

我们来试行一下:

没有毛病。

但这个实现有个问题,就是服务器是单进程的,意味着同时只能响应一个客户端,这自然是不好的。

V2- Echo Server 多进程版本

为解决上述问题,我们自然可以采用多进程的做法:

注意到我们其实并不想让父进程等待子进程退出信息,我们可以手动忽略子进程的退出信号

然后子进程最好将监听套接字关闭,防止误操作。

父进程则是将sockfd关闭,防止文件描述符溢出。

V3-EchoServer多线程版本

进程创建的开销还是要比线程大的,我们可以考虑使用多线程来解决上述问题:

因为线程是共享文件描述符的,因此我们不需要也不能关闭文件了。

此外,我们必然不想要线程等待,因此要让线程分离。

但是我们Service的参数这么多该如何传进去呢?

可以考虑封装一个内部类:

这样我们还把this指针传进去,就能调用内部成员函数了:

尝试调用:

V4-EchoServer线程池版本

有了多线程版本自然可以实现一个线程池版本,这个其实非常简单,只需要将任务入队列即可:

调用一下:

可以看到大体没有问题,还需要调整一些细节。这里就不调整了,毕竟这个并不适合用线程池。

V5-多线程远程命令执行

一直都是执行无聊的echo命令也没有意思,我们这里实现一个远程传来命令,服务器执行并返回结果。

为了更好地实现IO和业务的解耦,我们传入相应的回调函数即可:


Command

那么接下来我们实现指令功能吧。

首先自然是简单的初始化工作:

然后就可以调整ServerMain的逻辑:

然后我们就要接收远端传来的指令,这里可以用TCP的读写接口:

和write、read相比就是多了个flags标记位罢了,这里依旧置0处理。

那么问题来了,我们该如何调用指令呢?

用我们自主实现的shell吗?

当然不用这么麻烦,这里直接调用一个库函数即可:

这里就会将指令运行的结果放到文件里。这个文件也是一个类似管道的内核文件。第二个参数就是该文件的打开方式,我们当然是以只读形式打开:

讲道理我们现在已经可以运行了,但是远端执行的指令不可控,我们还可以设置一个简单的白名单。只允许执行白名单上的指令:

然后就要做白名单检查了:

不过老实说这个白名单心理安慰作用比较大。

毕竟我们可以;或|或&&等形式执行多条命令。

运行结果尚可。

完整代码

最后完整代码奉上

相关推荐
科技块儿2 分钟前
如何通过部署IP离线库,实现批量、高速、无网络依赖的IP查询能力?
网络·网络协议·tcp/ip
nihui1236 分钟前
Kali Linux 中 Nmap 工具详细使用指南
linux·网络·web安全
txinyu的博客7 分钟前
结合游戏场景解析UDP可靠性问题
java·开发语言·c++·网络协议·游戏·udp
深圳南柯电子36 分钟前
深圳南柯电子|EMC电磁兼容测试系统:5G时代应对频段的干扰挑战
网络·人工智能·互联网·实验室·emc
fy zs41 分钟前
传输层协议UDP
网络协议·tcp/ip·udp
w陆压43 分钟前
2.TCP三次握手、四次挥手
网络·网络协议·计网知识点
紫色的路1 小时前
TCP消息边界处理的精妙算法
c++·网络协议·tcp/ip·算法
知乎的哥廷根数学学派1 小时前
基于高阶统计量引导的小波自适应块阈值地震信号降噪算法(MATLAB)
网络·人工智能·pytorch·深度学习·算法·机器学习·matlab
DeepFlow 零侵扰全栈可观测1 小时前
DeepFlow 实践:利用 eBPF 实现覆盖从网关到数据库的全栈分布式追踪
网络·分布式·云原生·云计算
松涛和鸣1 小时前
51、51单片机
c语言·网络·单片机·嵌入式硬件·tcp/ip·51单片机