Tcp_socket

发布于:2025-02-11 ⋅ 阅读:(6) ⋅ 点赞:(0)

Tcp与Udp区别

tcp需要握手后建立连接才可以开始服务
使用listen握手

[listen]声明:

       int listen(int sockfd, int backlog);

[accept]声明:

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

[connect声明]

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);


listen返回的fd是给accept使用的,不可用来I/O
我们应对accept 返回的fd进行I/O

Tcp的 accept 返回的fd更像是文件,可以用read读取,因为Tcp是面向字节流
Udp的recvfrom返回的fd不可read等操作,因为Udp不是面向字节流

和Udp一样,Tcp也是全双工通信的(可同时读写)

telnet [域名] 端口号

远程登陆

struct sockaddr

struct sockaddr   基类
struct sockaddr_in,inet,网络间通信
struct sockaddr_un,unix,本机通信
这三个struct前2字节都是16位地址类型(C语言实现多态)


与Tcp协议server交流时,client应先connect,connect成功后才可以write/read
到client与server段开链接时,server中的read会立即返回0,表示与client失去连接

fd & 多进程

子进程继承父进程的文件描述符表。两张,父子各一张(可以理解为子进程浅拷贝父进程fd表,表中fd指向与父进程相同)

父子进程共享的fd中,如果其中一个进程关闭fd,另一个进程中对应fd不受影响,因为fd指向文件具有引用计数,只有引用数为0时,fd指向文件才会真正关闭

将子进程与父进程脱离的方法
signal(SIGCHLD,SIG_IGN),会使系统自动回收子进程
在子进程中生成孙子进程,之后关闭子进程,孙子进程就变为孤儿进程,由系统自动回收。

和进程不同,父子线程共享一张fd表,所以子线程中不用也不能关闭fd。

recv /send  与 read/write

使用recv & send 代替read & write
后者可能导致数据不完整,所以建议前者

[recv]

       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

[send]

 #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);


tips:

fd是有用的有限的资源,fd周期随进程。如果不关闭fd,会造成fd泄漏

类静态函数成员

类的静态成员函数不能直接访问类的实例成员(非静态成员),但可以访问类的静态成员。
这是因为静态成员函数与类的实力无关(可能使用静态成员函数时,类还没有实例化)

非静态成员函数可以访问静态成员函数(因为后者实例化一定比前者早)


server类中调用pthread_thread_creat时其函数参数的对应函数(void*(void*))不能接收类内普通自定义参数(因为其只能传递server的this),
只能使server类的静态函数成员作为pthread_thread_creat的函数参数。
通过想类的静态函数成员传递包含有线程所需data+server的this指针,在静态成员函数中通过this调用server类的普通成员函数(同时传递data),实现传参。

popen & pclose

封装了pipe 与 fork  /  exec

#include <stdio.h>

       FILE *popen(const char *command, const char *type);

       int pclose(FILE *stream);

板书笔记

TcpEchoShell_code

code/lesson35/1. EchoServer · whb-helloworld/112 - 码云 - 开源中国

 TcpServer.cc

#include "TcpServer.hpp"
#include "CommandExec.hpp"
#include <functional>
#include <memory>


using task_t = function<std::string (std::string)>;
// using namespace 
int main()
{
    ENABLE_CONSOLE_LOG();
    Command cmd;

    task_t task = [&cmd](std::string cmdstr){
        return cmd.Execute(cmdstr);
    };
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(task);
    tsvr->InitServer();
    tsvr->Start();
    return 0;
}

TcpServer.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>

#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

#define BACKLOG 8

// using namespace LogModule;
using namespace ThreadPoolModule;
static const uint16_t gport = 8080;
using handler_t = std::function<std::string(std::string)>;

class TcpServer
{
    using task_t = std::function<void()>;
    struct ThreadData
    {
        int sockfd;
        TcpServer *self;
    };
public:
    TcpServer(handler_t handler, int port = gport):
    _handler(handler), 
    _port(port),
    _isrunning(false)
    {

    }

    void InitServer()
    {
        // 先监听
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if(_listensockfd < 0)
        {
            LOG(LogLevel::FATAL)<<"socket error";
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO)<<"socket create success, _listensocked is "<<_listensockfd;
        // 后bind
        struct sockaddr_in local;// 网络通信用sockaddr_in, 本地用sockaddr_un
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        
        int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
        if(n < 0)
        {
            LOG(LogLevel::FATAL)<<"bind error";
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success, sockfd is: "<<_listensockfd;
        // 设置为监听状态
        n = listen(_listensockfd, BACKLOG);
        if(n < 0)
        {
            LOG(LogLevel::FATAL)<<"listen error";
            Die(LISTEN_ERR);
        }
        LOG(LogLevel::INFO)<<"listen success, socked is:"<<_listensockfd;
        // 此处可使用::signal(SIGCHLD, SIG_IGN)来将父子进程解绑
    }

    void HandlerRequest(int sockfd)
    {
        LOG(LogLevel::INFO)<<"HandlerRequest, sockfd is:"<<sockfd;
        char inbuffer[4096];
        while(true)
        {
            ssize_t n = recv(sockfd, inbuffer, sizeof inbuffer, 0);
            inbuffer[n] = 0;
            LOG(LogLevel::INFO)<<"server recived:"<<inbuffer;
            if(n > 0)
            {
                inbuffer[n] = 0;
                std::string cmd_result = _handler(inbuffer);// 回调
                ::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0);
                LOG(LogLevel::INFO)<<"server sent:"<<cmd_result;
            }
            else if(n == 0)
            {
                LOG(LogLevel::INFO)<<"client quit"<<sockfd;
                break;
            }
            else
            {
                break;
            }
        }
        ::close(sockfd);// 防止fd泄露
    }

    static void *ThreadEntry(void *args)// 设为静态函数就不用传递this
    {// 当使用多线程(不是封装好的线程池), pthread_thread_create的函数只能接收一个参数
        pthread_detach(pthread_self());
        ThreadData *data = (ThreadData *)args;
        data->self->HandlerRequest(data->sockfd);
        return nullptr;
    }

    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t peerlen = sizeof(peer);
            LOG(LogLevel::DEBUG)<<"accepting...";
            int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
            if(sockfd < 0)
            {
                LOG(LogLevel::WARNING)<<"accept error:"<<strerror(errno);
                continue;
            }
            // 连接成功
            LOG(LogLevel::INFO)<<"accept success, sockfd is:"<<sockfd;
            InetAddr addr(peer);
            LOG(LogLevel::INFO)<<"client info:"<<addr.Addr();
            // 使用线程池实现
            ThreadPool<task_t>::getInstance()->Equeue([this, sockfd](){
                this->HandlerRequest(sockfd);
            });
        }   
    }

    ~TcpServer()
    {

    }


private:
    int _listensockfd;// 监听socket
    uint16_t _port;
    bool _isrunning;

    // 处理上层任务入口
    handler_t _handler;
};

TcpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

// #include "Common.hpp"

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        std::cout<<"Usage:./client_tcp [server_ip] [server_port]"<<std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    int server_port = std::stoi(argv[2]);
    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cout<<"create socket failed"<<std::endl;
        return 2;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());
    // client 不需要显示进行 bind, tcp是面向连接的, connect底层自动bind
    int n = ::connect(sockfd, (struct sockaddr*)(&server_addr), sizeof(server_addr));
    if(n < 0)
    {
        std::cout<<"connet falied"<<std::endl;
        return 3;
    }
    std::cout<<"connet 成功\n"<<std::endl;
    std::string message;
    while(true)
    {  
        char inbuffer[1024];
        std::cout<<"input message: ";
        std::getline(std::cin, message);
        n = ::write(sockfd, message.c_str(), message.size());
        if(n > 0)
        {
            int m = read(sockfd, inbuffer, sizeof(inbuffer));
            if(m > 0)
            {
                inbuffer[m] = 0;
                std::cout<<inbuffer<<std::endl;
            }
            else break;
        }
        else break;
    }
    ::close(sockfd);
    return 0;
}


CommadExec.hpp

#pragma once 

#include <iostream>
#include <string>

class Command
{
public:
    std::string Execute(std::string cmdstr)
    {
        return std::string("命令执行完毕\n");
    }
};

Common.hpp

#pragma once 
#include <iostream>
#define Die(code) do{exit(code);}while(0)
#define CONV(v) (struct sockaddr *)(v)

enum
{
    USAGE_ERR = 1, 
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR
};

bool SplitString(std::string &in, std::string *key, std::string *val, std::string gap)
{
    size_t pos = in.find(gap);
    if (pos == std::string::npos) return false;
    *key = in.substr(0, pos);
    *val = in.substr(pos + gap.size());
    if(key->empty() || val->empty()) return false;
    return true;
}