TCP套接字相关知识

TCP特有的相关接口

listen接口

#include 
#include 

int listen(int sockfd,int backlog);


返回值

成功 0被返回
失败 -1被返回

accept接口

#include 
#include 

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

 返回值

返回文件描述符


connect接口

#include 
#include 

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

 返回值 

成功 0被返回。
失败 -1被返回。
并且在connect的时候返回成功自定bind。

普通版本代码

服务端

套接字的创建

// 1. 创建socket文件套接字对象
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0)
{
    logMessage(FATAL, "create socket error");
    exit(SOCKET_ERR);
}
logMessage(NORMAL, "create socket success");

 bind连接

// 2.bind绑定自己的网络信息
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(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
    logMessage(FATAL, "bind socket error");
    exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");

 listen监听接口

TCP是面向连接的所以需要将套接字设置为监听状态


// 3. 设置socket 为监听状态
if (listen(_listensock, gbacklog) < 0) // TODO
{
    logMessage(FATAL, "listen socket error");
    exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");

客户端在何时连接? 

 客户端连接服务端的时候,服务端需要就可以accept到客户端发来的信息。

accept接口

// 4. server 获取新链接
// sock 和客户端进行通信的fd
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock, (struct sockaddr *)&peer, &len);

if (sock < 0)
{
    logMessage(ERROR, "accept error, next");
    cout << "sock: " << sock << endl;

    continue;
}
logMessage(NORMAL, "accept a new link success,get new sock: %d", sock);

serviceIO 服务端的服务

先读后写

void serviceIO(int sock)
{
    char buffer[1024];
    while (true)
    {
        size_t n = read(sock, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            // 目前我们把读到的数据当成字符串,截至目前
            buffer[n] = 0;
            std::cout << "recv message: " << buffer << std::endl;

            std::string outbuffer = buffer;
            outbuffer += " server[echo]";

            write(sock, outbuffer.c_str(), outbuffer.size()); // 多路转接
        }

        else if (n == 0)
        {
            // 代表client退出
            logMessage(NORMAL, "client quit , me too!");
            break;
        }
    }
    close(sock);
}

客户端代码

创建套接字


// 1. 创建socket
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
    cerr << "socket create error" << endl;
}

// 2. tcp的客户端要不要bind? 要的 要不要显示的bind?
// 不要! 这里尤其是client port要让OS自定随机自定!
// 3. 要不要listen?不要!只有服务端需要!
// 4. 要不要accept?不要!只有服务端需要!
// 5. 要什么呢??要发起链接!

 链接  发送数据


void start()
{
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(_serverport);
    server.sin_addr.s_addr = inet_addr(_serverip.c_str());

    int con = connect(_sock, (struct sockaddr *)&server, sizeof(server));
    if (con != 0)
    {
        cerr << "socket connect error" << endl;
    }
    else
    {
        string msg;
        while (true)
        {
            cout << "Enter# ";
            getline(cin, msg);
            write(_sock, msg.c_str(), msg.size());

            char buffer[NUM];
            int n = read(_sock, buffer, sizeof(buffer) - 1);

            if (n > 0)
            {

                // 目前我们把读到的数据当字符串,截至目前
                buffer[n] = 0;
                cout << "Server回显# " << buffer << endl;
                buffer[0] = 0;
            }
            else
            {
                break;
            }
        }
    }
}

普通版本完整代码

makefile

cc=g++

.PHONY:all
all:tcpclient tcpserver

tcpclient:tcpClient.cc
	$(cc) -o $@ $^ -std=c++11

tcpserver:tcpServer.cc
	$(cc) -o $@ $^ -lpthread -std=c++11

.PHONY:clean
clean:
	rm -f tcpclient tcpserver

tcpserver.hpp


#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "log.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
using namespace std;

namespace server
{

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

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    class TcpServer;

    class ThreadData
    {
    public:
        ThreadData(TcpServer *self, int sock)
            : _self(self), _sock(sock)
        {
        }
        ~ThreadData()
        {
        }
        TcpServer *_self;
        int _sock;
    };

    class TcpServer
    {
    public:
        TcpServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
        {
        }
        void initServer()
        {

            // 1. 创建socket文件套接字对象
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success");

            // 2.bind绑定自己的网络信息
            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(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            // 3. 设置socket 为监听状态
            if (listen(_listensock, gbacklog) < 0) // TODO
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }

        void start()
        {
            for (;;)
            {
                // 4. server 获取新链接
                // sock 和客户端进行通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);

                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next");
                    cout << "sock: " << sock << endl;

                    continue;
                }
                // logMessage(NORMAL, "accept a new link success,get new sock: %d", sock);
                logMessage(NORMAL, "accept a new link success");
                cout << "sock: " << sock << endl;

                    // 5. 这里就是一个sock,未来通信我们就用这个sock,面向字节流的,后续全部都是文件操作!

                    // version 1
                    serviceIO(sock);
                close(sock); // 对一个就已经使用完毕的sock,我们要关闭这个sock
                // 要不然会导致。
                // version 多进程版 多线程版 线程池版

                
            }
        }

       

        void serviceIO(int sock)
        {
            char buffer[1024];
            while (true)
            {
                ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
                if (n > 0)
                {
                    // 目前我们把读的数据当成字符串,截止目前
                    buffer[n] = 0;
                    cout << "recv message: " << buffer << endl;

                    string outbuffer = buffer;
                    outbuffer += "server[echo]";

                    write(sock, outbuffer.c_str(), outbuffer.size());
                }
                else if (n == 0)
                {
                    // 代表客户端退出
                    logMessage(NORMAL, "client quit,me too!");
                    break;
                }
            }
        }
        ~TcpServer()
        {
        }

    private:
        int _listensock; // 不是用来进行数据通信的,它是用来监听链接到来,获取新链接的!
        uint16_t _port;
    };
}

tcpserver.cc

#include "tcpServer.hpp"
#include "daemon.hpp"
#include 

using namespace server;
using namespace std;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

// tcp服务器,启动上和udp server一模一样
// ./tcpServer local_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=atoi(argv[1]); // 字符串转整型

    unique_ptr tsvr(new TcpServer(port));

    tsvr->initServer();
    // daemonSelf();
    tsvr->start();



    return 0;
}

tcpclient.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

#define NUM 1024

class TcpClient
{
public:
    TcpClient(const string &serverip, const uint16_t &serverport)
        : _sock(-1), _serverip(serverip), _serverport(serverport)
    {
    }
    void initClient()
    {
        // 1. 创建socket
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            cerr << "socket create error" << endl;
        }
        // 2. tcp的客户端要不要bind? 要的 要不要显示的bind?
        // 不要! 这里尤其是client port要让OS自定随机自定!
        // 3. 要不要listen?不要!只有服务端需要!
        // 4. 要不要accept?不要!只有服务端需要!
        // 5. 要什么呢??要发起链接!
    }

    void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());


        int con = connect(_sock, (struct sockaddr *)&server, sizeof(server));
        if (con != 0)
        {
            cerr << "socket connect error" << endl;
        }
        else
        {
            string msg;
            while (true)
            {
                cout << "Enter# ";
                getline(cin, msg);
                write(_sock, msg.c_str(), msg.size());

                char buffer[NUM];
                int n = read(_sock, buffer, sizeof(buffer) - 1);

                if (n > 0)
                {

                    // 目前我们把读到的数据当字符串,截至目前
                    buffer[n] = 0;
                    cout << "Server回显# " << buffer << endl;
                    buffer[0] = 0;
                }
                else
                {
                    break;
                }
            }
        }
    }
    ~TcpClient()
    {
        if (_sock >= 0)
        {
            close(_sock);
        }
    }

private:
    int _sock;
    string _serverip;
    uint16_t _serverport;
};

tcpclient.cc

#include "tcpClient.hpp"
#include 

using namespace std;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " serverip serverport\n\n";
}

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr tcli(new TcpClient(serverip, serverport));

    tcli->initClient();
    tcli->start();

    return 0;
}


// log.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 

using namespace std;

void logMessage(int level, const char *message)
{
    cout << message << endl;
}

多进程版本

版本一

孙子进程进行提供服务

// 多进程版本1
// version 2
pid_t id = fork();

if (id == 0) // child
{
    close(_listensock); // 子进程中不需要监听因此关闭监听的文件描述符
    if (fork() > 0)
        exit(0);

    serviceIO(sock);
    close(sock);
    exit(0);
}

// father
pid_t ret = waitpid(id, nullptr, 0);

if (ret > 0)
{
    cout << "waitsuccess: " << ret << endl;
}

版本二

通过屏蔽信号SIGCHLD来防止僵尸进程

// 多进程版本2
signal(SIGCHLD, SIG_IGN);

pid_t id = fork();
if (id == 0) // child
{
    close(_listensock);
    // if(fork()>0) exit(0);
    serviceIO(sock);
    close(sock);
    exit(0);
}
close(sock);

多线程版本

static void *threadRoutine(void *args)
{
    pthread_detach(pthread_self()); // 线程分离 互不干扰
    ThreadData *td = static_cast(args);

    td->_self->serviceIO(td->_sock);
    delete td;
    close(td->_sock);
    return nullptr;
}

pthread_t tid;
ThreadData *td = new ThreadData(this, sock);
pthread_create(&tid, nullptr, threadRoutine, td);

线程池版本

线程池初始化

ThreadPool::getInstance()->run();

线程池

ThreadPool::getInstance()->push(Task(sock, serviceIO));
####################################
Task.hpp
####################################

#pragma once

#include 
#include 
#include 
#include 

void serviceIO(int sock)
{
    char buffer[1024];
    while (true)
    {
        size_t n = read(sock, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            // 目前我们把读到的数据当成字符串,截至目前
            buffer[n] = 0;
            std::cout << "recv message: " << buffer << std::endl;

            std::string outbuffer = buffer;
            outbuffer += " server[echo]";

            write(sock, outbuffer.c_str(), outbuffer.size()); // 多路转接
        }

        else if (n == 0)
        {
            // 代表client退出
            logMessage(NORMAL, "client quit , me too!");
            break;
        }
    }
    close(sock);
}

// 计算数据的类
class Task
{
    using func_t = std::function;

public:
    Task()
    {
    }

    Task(int sock, func_t func)
        : _sock(sock), _callback(func)
    {
    }

    // 该函数是消费任务的打印信息
    void operator()()
    {
        _callback(_sock);
    }

private:
    int _sock;
    func_t _callback;
};

####################################
ThreadPool.hpp
####################################

#pragma once

#include "Thread.hpp"
#include "LockGuard.hpp"
#include "log.hpp"
#include 
#include 
#include 
#include 
#include 

using namespace ThreadNs;

const int gnum = 10;

template 
class ThreadPool;

template 
class ThreadData
{
public:
    ThreadPool *threadpool;
    std::string name;

public:
    ThreadData(ThreadPool *tp, const std::string &n)
        : threadpool(tp), name(n)
    {
    }
};

template 
class ThreadPool
{
private:
    static void *handlerTask(void *args)
    {
        ThreadData *td = (ThreadData *)args;
        while (true)
        {
            T t;
            {
                LockGuard lockguard(td->threadpool->mutex());

                while (td->threadpool->isQueueEmpty())
                {
                    td->threadpool->threadWait();
                }

                // pop的本质,是将任务从公共的队列当中,拿到当前线程自己独立的栈中
                t = td->threadpool->pop();
            }
            t();
        }
        delete td;
        return nullptr;
    }
    // 单例模式:构造函数设置为私有
    ThreadPool(const int &num = gnum) : _num(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);

        for (int i = 0; i < _num; i++)
        {
            _threads.push_back(new Thread());
        }
    }
    void operator=(const ThreadPool &) = delete;
    ThreadPool(const ThreadPool &) = delete;

    // 为了解决static handlerTask
public:
    void lockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }

    void unlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }
    bool isQueueEmpty()
    {
        return _task_queue.empty();
    }
    void threadWait()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }
    T pop()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }
    pthread_mutex_t *mutex()
    {
        return &_mutex;
    }

public:
    void run()
    {
        for (const auto &t : _threads)
        {
            ThreadData *td = new ThreadData(this, t->threadname());

            t->start(handlerTask, td);
            std::cout << t->threadname() << " start ..." << std::endl;
            // logMessage(DEBUG, "%s start ...", t->threadname().c_str());
        }
    }

    void push(const T &in)
    {
        LockGuard lockguard(&_mutex);
        _task_queue.push(in);
        pthread_cond_signal(&_cond);
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);

        for (const auto &t : _threads)
            delete t;
    }

    static ThreadPool *getInstance()
    {
        // 最外边这个if的作用是:
        // 如果tp已经被创建,那么就不用创建了,因为是单例模型
        if (nullptr == tp)
        {
            _singlock.lock();
            if (nullptr == tp)
            {
                tp = new ThreadPool();
            }
            _singlock.unlock();
        }

        return tp;
    }

private:
    int _num;

    std::vector _threads; // 线程集合
    std::queue _task_queue;      // 任务队列

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static ThreadPool *tp;
    static std::mutex _singlock;
};

template 
ThreadPool *ThreadPool::tp = nullptr;

template 
std::mutex ThreadPool::_singlock;

日志


#pragma once

#include 
#include 
#include 
#include 
#include 

using namespace std;

#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"

#define DEBUG 0
#define NORMAL 1
#define WRNING 2
#define ERROR 3
#define FATAL 4

// 根据不同的日志等级来返回不同的日志信息
const char *to_levelstr(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WRNING:
        return "WRNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return nullptr;
    }
}
// [日志等级] [时间戳/时间] [pid] [message]
// [WARNING] [2023-6-29 8:25:08] [123] [创建socket失败]

// va_list start;

// start 指针

// va_start(start); -- 把start指向第一个

// va_arg(start,float); 让这个指针向后移动指定类型的大小

// va_end(start); 让start这个指针设为NULL
// while (*p)
// {
//     switch (*p)
//     {
//     case '%':
//         p++;
//         if (*p == 'f')
//             arg = va_arg(start, float);
//         // ...
//     }
// }
// va_end(start);

// void logMessage(DEBUG, "hello %f, %d, %c",3.14,10,'C');
void logMessage(int level, const char *format, ...)
{
#define NUM 1024
    char logprefix[NUM];
    // logprefix
    // [日志等级] [时间戳/时间] [pid] 
    snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",
             to_levelstr(level), (long int)time(nullptr), getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);
    // [message]
    vsnprintf(logcontent, sizeof(logcontent), format, arg);

    cout << logprefix << logcontent << endl;

    FILE *log = fopen(LOG_NORMAL, "a");
    FILE *err = fopen(LOG_ERR, "a");

    if (log != nullptr && err != nullptr)
    {
        FILE *curr = nullptr;
        if (level == DEBUG || level == NORMAL || level == WRNING)
            curr = log;
        if (level == ERROR || level == FATAL)
            curr = err;
        if (curr)
            fprintf(curr, "%s%s\n", logprefix, logcontent);

        fclose(log);
        fclose(err);
    }
}

守护进程

解析

不受用户登录注销的进程。

setsid 接口

#include 

pid_t setsid(void);

你可能感兴趣的:(Linux网络编程,tcp/ip,网络,服务器)