当前位置: 首页 > news >正文

【Linux网络(三)】网络基础套接字

简易tcp代码 

  • 开始监听socket(TCP,服务器)

int listen(int socket, int backlog);

TCP是面向连接的 ,服务器一般是比较”被动“的,服务器要一直处于一种,一直等待连接到来的状态。就需要进行监听

该函数的作用是将套接字设置为listen状态   就可以通过该套接字等待新连接的到来。

后面TCP协议会解释第二个参数  这里先直接用

  • 接收请求(TCP,服务器)

int accept(int socket,struct sockaddr* address,socklen t* address len);

第一个参数: socket 刚刚打开的,并且已经设置为listen状态的套接字

第二、三个参数:是输出型参数,可以知道是哪个客户端来连接我

成功了返回一个整数的文件描述符 --->4  失败返回-1 

  • 建立连接(TCP,客户端)

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

将套接字连接到远程服务器的函数

指定的套接字、指定的网络目标地址去发起连接

//单进程版本

//多进程版本

多线程版本的

TcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"


const int defaultfd = -1; //缺省值定义
const std::string defaultip = "0.0.0.0";
const int backlog = 10;  //一般不要设置的太大 后面会解释

Log lg;

enum
{
    UsageError = 1,
    SockError,
    BindError,
    ListenError
};
class TcpServer;

//设置一组线程数据
class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p,TcpServer* t):sockfd(fd),clientip(ip),clientport(p),tsvr(t)
    {}
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip):listensock_(defaultfd),port_(port),ip_(ip)
    {}
    void InitServer()
    {
        //1、创建监听套接字
        listensock_ = socket(AF_INET, SOCK_STREAM, 0); //第二个参数是字节流 是tcp
        if(listensock_ < 0)
        {
            lg(Fatal,"create listensock_ ,errno: %d, errstring: %s",errno,strerror(errno));
            exit(SockError);
        }
        lg(Info, "create socket success, listensock_:%d", listensock_);
        //2、填充字段
        struct sockaddr_in local;  //现在只是在用户空间上填充了变量   还需要进行绑定
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));//点分十进制的字符串风格的ip转为4字节的
        //local.sin_addr.s_addr = INADDR_ANY;
        //3、将套接字与ip地址和端口号进行绑定
        if(bind(listensock_, (struct sockaddr*)&local,sizeof(local)) < 0)
        {
            lg(Fatal,"bind error,errno: %d, errstring: %s",errno,strerror(errno));
            exit(BindError);
        }

        //4、将套接字设置为监听状态
        //Tcp是面向连接的 通信之前得先进行连接 所以server就必须得等别人来连
        if(listen(listensock_,backlog) < 0)
        {
            lg(Fatal,"listen error,errno: %d, errstring: %s",errno,strerror(errno));
            exit(ListenError);
        }
        //套接字已经被设置为listen状态
    }
    static void *Routine(void *args)
    {
        pthread_detach(pthread_self()); //把自己设置为分离状态
        //主线程一直在获取新连接,然后就不管了,让新线程去进行服务
        //多线程不用关闭文件描述符表 因为在线程中所有的资源都是共享的 关闭就会出现混乱
        ThreadData *td = static_cast<ThreadData*>(args); //线程拿到数据
        td->tsvr->Service(td->sockfd, td->clientip, td->clientport);
        delete td; //在堆上开辟的空间
        return nullptr;

    }
    void Start()
    {
        //signal(SIGCHLD,SIG_IGN);

        lg(Info, "tcpServer is running...");
        for(;;)
        {
            //1、获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
            //被创建的、被绑定的、被监听的socket_套接字,它只是从底层把新的连接获取上来。而未来真正提供IO通信服务的并不是创建的sockfd_
            //而是accept返回的sockfd    sockfd_一般只有一个  而sockfd可以有多个   我们把sockfd_叫作listsnsock_
            if(sockfd < 0)
            {
                lg(Warning,"accept error,errno: %d, errstring: %s",errno,strerror(errno));
                continue;
                //服务器获取连接失败不会退出  只会去获取别人连接

            }
            //拿到谁连的我的客户端信息
            uint16_t clientport = ntohs(client.sin_port); 
            char clientip[32];
            inet_ntop(AF_INET,&(client.sin_addr),clientip, sizeof(clientip)); //IPV4 客户端的ip地址
            //2、根据新连接来进行通信
            lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d",sockfd, clientip, clientport);
            //------------单进程版
            // Service(sockfd, clientip, clientport);  //如果给一客户端提供服务的时候 其他客户端不能请求连接 服务器也不能给他提供服务 它只能等着 这就是单进程版的
            // close(sockfd);

            //-------------多进程版
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     close(listensock_); //要关闭这个套接字 可以理解为 子进程是去提供服务的 不需要监听套接字 只需要sockfd就好了
            //     //child
            //     //子进程提供服务
            //     if(fork() > 0) exit(0);  //子进程已经退出  
            //     //后面提供服务的是孙子进程   因为孙子进程的父进程已经退出了, 那孙子进程怎么办 它会被系统领养 执行完之后会被系统自动回收

            //     Service(sockfd, clientip, clientport); //父进程打开的文件描述符 会被子进程继承下去
            //     close(sockfd);
            //     exit(0);
            // }
            // close(sockfd);//sockfd是服务套接字  这个套接字已经去提供服务了  而父进程是进行监听的
            // //father  --- 去获取新连接
            // pid_t rid = waitpid(id, nullptr, 0); //这是阻塞等待 阻塞的等待子进程退出 要怎么解决这个问题????? 创建孙子进程 让子进程退出 即可
            // (void)rid;

            //----------多线程版  --- 因为创建一个进程成本太高了

            //线程创建了 我们要给线程传递的参数 sockfd 客户端ip 客户端port 创建一个类
            ThreadData *td = new ThreadData(sockfd,clientip,clientport,this); //传入三个参数
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);
            //pthread_join(); //主线程不去等待 否则就会卡住 不能实现并发运行 如果主线程去等待新线程的退出 主线程就会卡住 所以应该让新线程自己等待自己退出

  

        }
    }
    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        //客户端如果自己退了服务器应该怎么办
        //服务器就会读到0
        while(true)
        {
            //因为tcp是面向字节流的 所以读网络就像读取文件一样
            char buffer[4096];
            ssize_t n = read(sockfd, buffer, sizeof(buffer)); //收一个消息
            if(n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say#" << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(),echo_string.size()); //发回去
            }
            else if(n == 0)
            {
                lg(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport,sockfd);
                break;
            }
            else //读取出错
            {
                lg(Warning, "read error,sockfd: %d,client ip: %s,client port: %d",sockfd,clientip.c_str(), clientport);
                break;
            }

        }
    }
    ~TcpServer()
    {

    }
private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

线程池版本

Makefile

.PHONY:all
all:tcpserver tcpclient

tcpserver:Main.cc
	g++ -o $@ $^ -std=c++11
tcpclient:TcpClient.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm tcpserver tcpclient

Main.cc

#include "TcpServer.hpp"
#include <iostream>
#include <memory>


void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;   //提示输入正确的端口号
}
// ./tcpserver 8080
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(UsageError); //不等于2 直接终止进程
    }
    uint16_t port = std::stoi(argv[1]);  //将字符串转为整数 因为命令函参数中的端口号是字符串类型

    std::unique_ptr<TcpServer> tcp_Svr(new TcpServer(port));
    tcp_Svr->InitServer();
    tcp_Svr->Start();


    return 0;
}

TcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"


const int defaultfd = -1; //缺省值定义
const std::string defaultip = "0.0.0.0";
const int backlog = 10;  //一般不要设置的太大 后面会解释
extern Log lg;

enum
{
    UsageError = 1,
    SockError,
    BindError,
    ListenError
};
class TcpServer;

//设置一组线程数据
class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p,TcpServer* t):sockfd(fd),clientip(ip),clientport(p),tsvr(t)
    {}
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip):listensock_(defaultfd),port_(port),ip_(ip)
    {}
    void InitServer()
    {
        //1、创建监听套接字
        listensock_ = socket(AF_INET, SOCK_STREAM, 0); //第二个参数是字节流 是tcp
        if(listensock_ < 0)
        {
            lg(Fatal,"create listensock_ ,errno: %d, errstring: %s",errno,strerror(errno));
            exit(SockError);
        }
        lg(Info, "create socket success, listensock_:%d", listensock_);
        //2、填充字段
        struct sockaddr_in local;  //现在只是在用户空间上填充了变量   还需要进行绑定
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));//点分十进制的字符串风格的ip转为4字节的
        //local.sin_addr.s_addr = INADDR_ANY;
        //3、将套接字与ip地址和端口号进行绑定
        if(bind(listensock_, (struct sockaddr*)&local,sizeof(local)) < 0)
        {
            lg(Fatal,"bind error,errno: %d, errstring: %s",errno,strerror(errno));
            exit(BindError);
        }

        //4、将套接字设置为监听状态
        //Tcp是面向连接的 通信之前得先进行连接 所以server就必须得等别人来连
        if(listen(listensock_,backlog) < 0)
        {
            lg(Fatal,"listen error,errno: %d, errstring: %s",errno,strerror(errno));
            exit(ListenError);
        }
        //套接字已经被设置为listen状态
    }
    static void *Routine(void *args)
    {
        pthread_detach(pthread_self()); //把自己设置为分离状态
        //主线程一直在获取新连接,然后就不管了,让新线程去进行服务
        //多线程不用关闭文件描述符表 因为在线程中所有的资源都是共享的 关闭就会出现混乱
        ThreadData *td = static_cast<ThreadData*>(args); //线程拿到数据
        //td->tsvr->Service(td->sockfd, td->clientip, td->clientport);
        delete td; //在堆上开辟的空间
        return nullptr;

    }
    void Start()
    {
        //signal(SIGCHLD,SIG_IGN);

        lg(Info, "tcpServer is running...");
        for(;;)
        {
            //1、获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
            //被创建的、被绑定的、被监听的socket_套接字,它只是从底层把新的连接获取上来。而未来真正提供IO通信服务的并不是创建的sockfd_
            //而是accept返回的sockfd    sockfd_一般只有一个  而sockfd可以有多个   我们把sockfd_叫作listsnsock_
            if(sockfd < 0)
            {
                lg(Warning,"accept error,errno: %d, errstring: %s",errno,strerror(errno));
                continue;
                //服务器获取连接失败不会退出  只会去获取别人连接

            }
            //拿到谁连的我的客户端信息
            uint16_t clientport = ntohs(client.sin_port); 
            char clientip[32];
            inet_ntop(AF_INET,&(client.sin_addr),clientip, sizeof(clientip)); //IPV4 客户端的ip地址
            //2、根据新连接来进行通信
            lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d",sockfd, clientip, clientport);
            //------------单进程版
            // Service(sockfd, clientip, clientport);  //如果给一客户端提供服务的时候 其他客户端不能请求连接 服务器也不能给他提供服务 它只能等着 这就是单进程版的
            // close(sockfd);

            //-------------多进程版
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     close(listensock_); //要关闭这个套接字 可以理解为 子进程是去提供服务的 不需要监听套接字 只需要sockfd就好了
            //     //child
            //     //子进程提供服务
            //     if(fork() > 0) exit(0);  //子进程已经退出  
            //     //后面提供服务的是孙子进程   因为孙子进程的父进程已经退出了, 那孙子进程怎么办 它会被系统领养 执行完之后会被系统自动回收

            //     Service(sockfd, clientip, clientport); //父进程打开的文件描述符 会被子进程继承下去
            //     close(sockfd);
            //     exit(0);
            // }
            // close(sockfd);//sockfd是服务套接字  这个套接字已经去提供服务了  而父进程是进行监听的
            // //father  --- 去获取新连接
            // pid_t rid = waitpid(id, nullptr, 0); //这是阻塞等待 阻塞的等待子进程退出 要怎么解决这个问题????? 创建孙子进程 让子进程退出 即可
            // (void)rid;

            //----------多线程版  --- 因为创建一个进程成本太高了

            //线程创建了 我们要给线程传递的参数 sockfd 客户端ip 客户端port 创建一个类
            // ThreadData *td = new ThreadData(sockfd,clientip,clientport,this); //传入三个参数
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Routine, td);
            //pthread_join(); //主线程不去等待 否则就会卡住 不能实现并发运行 如果主线程去等待新线程的退出 主线程就会卡住 所以应该让新线程自己等待自己退出

            //-------线程池版本  预先创建一批线程 并且提供短服务  有线程进来的时候也会有线程退出
            //构建一个任务对象 把任务传进来
            Task t(sockfd, clientip, clientport); //构建任务
            ThreadPool<Task>::GetInstance()->Push(t);//把任务给线程池

        }
    }
    // void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    // {
    //     //客户端如果自己退了服务器应该怎么办
    //     //服务器就会读到0
    //     while(true)
    //     {
    //         //因为tcp是面向字节流的 所以读网络就像读取文件一样
    //         char buffer[4096];
    //         ssize_t n = read(sockfd, buffer, sizeof(buffer)); //收一个消息
    //         if(n > 0)
    //         {
    //             buffer[n] = 0;
    //             std::cout << "client say#" << buffer << std::endl;
    //             std::string echo_string = "tcpserver echo# ";
    //             echo_string += buffer;

    //             write(sockfd, echo_string.c_str(),echo_string.size()); //发回去
    //         }
    //         else if(n == 0)
    //         {
    //             lg(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport,sockfd);
    //             break;
    //         }
    //         else //读取出错
    //         {
    //             lg(Warning, "read error,sockfd: %d,client ip: %s,client port: %d",sockfd,clientip.c_str(), clientport);
    //             break;
    //         }

    //     }
    // }
    ~TcpServer()
    {

    }
private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

TcpClient.cc

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

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << "serverip serverport\n"<< std::endl;
}
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    //通过命令函参数已经拿到了server的IP地址和port
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);  //port必须由字符串转为整数

    int sockfd = socket(AF_INET,SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 1;
    }
   
    //将套接字连接到远程服务器
    //客户端要知道服务器的ip地址和端口号才能和server建立连接  这是要让别人告诉服务器的 即在服务器运行时 命令行参数里面带的
    //   ./tcpclient serverip serverport    设置main函数的参数就好了

    //因为要传入的参数是sockaddr类型的 所以 我们要先定义出sockaddr
    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));//字符串风格转四字节
    //至此就将要连接的目标服务器的字段就填好了

     //tcp客户端要不要bind? 要。 但是是由系统进行随机bind  只不过不需要显示的绑定
    //那什么时候进行绑定呢??
    //客户端发起connect的时候,进行自动随机bind
    int n = connect(sockfd,(struct sockaddr*)&server,sizeof(server));
    if(n < 0)
    {
        std::cerr << "connect error..." << std::endl;
        return 2;
    }
    std::string message;
    while(true)
    {
        std::cout << "Please Enter# ";
        std::getline(std::cin,message);
        //将消息写给对方
        write(sockfd, message.c_str(),message.size());//往sockfd里面写 --- 发消息
        //读对方给我返回的消息
        char inbuffer[4096];
        int n = read(sockfd, inbuffer,sizeof(inbuffer));//从sockfd里面读 --- 收消息
        if(n > 0)
        {
            inbuffer[n] = 0;//字符串最后以0结尾
            std::cout << inbuffer << std::endl;
        }

    }
    close(sockfd);
    return 0;
}

ThreadPool.hpp

#pragma once
#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defaultnum = 5;

template<class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);  //没有资源就去休眠
    }
    bool IsQueueEmpty()
    {
        return Tasks_.empty();
    }

public:
    static void *HandlerTask(void * args) //线程函数只有一个参数  如果类内函数的话它的第一个参数是this指针 就会存在参数个数不符的情况  加static就可以让他变成静态成员函数
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);        //线程检测  有任务就处理  没任务就休眠
        std::string name = tp->GetThreadName(pthread_self());
        while(true)
        {
            tp->Lock();

            while(tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();  //拿任务
            tp->Unlock();  //解锁之后去处理任务 因为你拿到任务之后,这个任务就是你的  所以可以在解锁之后去处理你的任务

            t();
        }
    }

    //线程池对外接口 启动线程池
    void Start()
    {
        int num = threads_.size();
        for(int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i+1);
            pthread_create(&(threads_[i].tid),nullptr,HandlerTask, this);  //创建线程时直接给线程传递当前对象
        }
    }
    T Pop()
    {
        T t = Tasks_.front();
        Tasks_.pop();
        return t;
    }
    //线程池对外接口 给线程池中放入任务
    void Push(const T &t)
    {
        Lock();
        Tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if(nullptr == tp_)
        {
            pthread_mutex_lock(&lock_);
            if(nullptr == tp_)
            {
                std::cout << "log: singletton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defaultnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_,nullptr);
        pthread_cond_init(&cond_,nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator = (const ThreadPool<T> &) = delete;
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> Tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;//指针有获取单例的方法
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

 Log.hpp

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include  <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch(level)
        {
            case Info:
                return "Info";
            case Debug:
                return "Debug";
            case Warning:
                return "Warning";
            case Error:
                return "Error";
            case Fatal:
                return "Fatal";
            default:
                return "None";
        }
    }
    void printLog(int level, const std::string &logtxt)
    {
        switch(printMethod)
        {
        case Screen:
            std::cout << logtxt <<std::endl;
            break;
        case Onefile:
            printOneFile(LogFile,logtxt);
            break;
        case Classfile:
            printClassFile(level,logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path +logname;
        int fd = open(_logname.c_str(),O_WRONLY | O_CREAT | O_APPEND, 0666);
        if(fd < 0)
        {
            return;
        }
        write(fd,logtxt.c_str(),logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level);
    }

    ~Log()
    {

    }
    void operator()(int level,const char *format,...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
        ctime->tm_year + 1900,ctime->tm_mon + 1, ctime->tm_mday,
        ctime->tm_hour, ctime->tm_min,ctime->tm_sec);

        va_list s;
        va_start(s,format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer),format,s);
        va_end(s);

        char logtxt[SIZE * 2];
        snprintf(logtxt,sizeof(logtxt), "%s %s", leftbuffer,rightbuffer);

        printLog(level,logtxt);
    }

private:
    int printMethod;
    std::string path;
};
Log lg;

Task.hpp


#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
extern Log lg;

class Task
{
public:
    Task(int sockfd, const std::string &clientip, const uint16_t &clientport)
    :sockfd_(sockfd),clientip_(clientip),clientport_(clientport)
    {}
    void run()
    {
        //客户端如果自己退了服务器应该怎么办
        //服务器就会读到0
        char buffer[4096];
       
            //因为tcp是面向字节流的 所以读网络就像读取文件一样
            ssize_t n = read(sockfd_, buffer, sizeof(buffer)); //收一个消息
            if(n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say#" << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd_, echo_string.c_str(),echo_string.size()); //发回去
            }
            else if(n == 0)
            {
                lg(Info, "%s:%d quit, server close sockfd: %d", clientip_.c_str(), clientport_,sockfd_);
                
            }
            else //读取出错
            {
                lg(Warning, "read error,sockfd: %d,client ip: %s,client port: %d",sockfd_,clientip_.c_str(), clientport_);
            }
            close(sockfd_);
    }
    void operator()()
    {
        run();
    }
    ~Task()
    {

    }
private:
    int sockfd_;
    std::string clientip_;
    uint16_t clientport_;
};

 守护进程

任务是要指派给进程组的  自成进程组自成会话

想让一个进程自成会话

#include <unistd.h>

pid_t  setsid(void);

将组id设置为会话id

成功返回新的 session ID 调用进程的pid ,否则返回-1 错误码被设置 -1被返回

要创建一个新的会话,调用进程不能是这个组的leader(组长)

因为进程启动 我的id和我的组id是完全一样的,我就是组长。那如何保证自己不是组长??

一般是第一个进程是组长,那如果我不是第一个进程,那就不是组长了。

if(fork() > 0)     exit(0);  //父进程退出

setsid(); //让子进程去创建守护进程

守护进程的本质,也是孤儿进程!因为它的父进程已经退出了  立马就要被系统领养。 

它是一个新的会话,它不会受任何用户登录的影响。

#include <unistd.h>

int  daemon(int  nochdir, int  noclose)

在后台运行

nochdir:设置为0,工作在根目录下,否则就是在当前目录下

noclose:为0,将标准输入、输出、错误 重定向到  /dev.null 否则不改变

 Daemon.hpp

#pragma once

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string nullfile = "/dev/null";

void Daemon(const std::string &cwd = "")
{
    //1、忽略其他异常信号
    signal(SIGCLD,SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);
    
    //2、将自己变成独立的会话
    if(fork() > 0)
    exit(0);  //进来的是父进程 出去的是子进程  
    setsid(); //让自己变成一个新的会话

    //3、更改当前调用进程的工作目录
    if(!cwd.empty()) //当前的cwd不为空 
    chdir(cwd.c_str());

    //4、将标准输入、标准输出、标准错误 全部重定向到/dev/null 里面
    int fd = open(nullfile.c_str(), O_RDWR);
    if(fd > 0)
    {
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);
        close(fd);
    }

}

tcp通信是全双工通信的。

tcp底层会提供两个缓冲区, tcp的发送缓冲区、tcp的接收缓冲区  用户自己上层也有自己的缓冲区,要发数据,先将数据写到发送缓冲区,然后发送缓冲区通过网络交给对方的接收缓冲区。对方就可以读数据了。  其实就是发送缓冲区和接收缓冲区是独立的。  多线程对同一个fd进行读写时是不会互相影响的,因为资源是互相隔离的。  对这个fd进行读的时候也可以对他写。

相关文章:

  • 【CXX-Qt】1.5 使用CMake构建
  • 《深入理解AOP编程:从基础概念到Spring实现》
  • C++中std::shuffle 的使用
  • MySQL 多列 IN 查询详解:语法、性能与实战技巧
  • 当 Selenium 的 click() /send_keys()等方法失效时:JavaScript 在 UI 自动化测试中的神奇用法
  • 工作记录 2017-02-06
  • gitlab 提交pr
  • 搭建Nginx
  • springboot第三站(1) web开发引入
  • Docker下载,包含Win、Mac
  • The test of the entire book_《Effective Modern C++》notes
  • Spring Boot集成PageHelper:轻松实现数据库分页功能
  • Linux系统之qrencode工具的安装与基本使用
  • 云安全相关博客阅读(四)
  • 使用静态库动态库也要头文件
  • 【Netty】消息分发处理方式
  • Unity shader管道液体流入并流出效果
  • Spring Boot 静态访问配置属性的解决方案
  • EditRocket for Mac v5.0.2 文本编辑器 支持M、Intel芯片
  • 从信息熵上看图像
  • 中国海警依法驱离日非法进入我钓鱼岛领海船只
  • 复旦设立新文科发展基金,校友曹国伟、王长田联合捐赠1亿元
  • 中邮保险斥资8.69亿元举牌东航物流,持股比例达5%
  • 外交部:中欧关系50年发展最宝贵经验是相互尊重,求同存异
  • 印巴军事对峙加剧,小规模冲突收场还是走向大战?
  • 巴菲特批评贸易保护主义:贸易不该被当成武器来使用