网络编程套接字----TCP协议

news2024/9/23 15:20:19

文章目录

  • 前言
  • 一、简单TCP网络程序
  • 二、TCP socket API 详解
    • socket()
    • bind()
    • listen()
    • accept()
    • connect()
  • 三、TCP网络编程
    • 头文件
    • 日志文件
    • 客户端
    • 服务器
      • 单进程版本
      • 多进程版本
      • 多线程版本
  • 四、线程池版的TCP
    • 更改提供的服务
  • 总结


前言

接着上节课我们讲了udp套接字网络编程,这节课我来给大家讲解基于Tcp套接字网络编程.


正文开始!

一、简单TCP网络程序

和上一章的UDP类似,实现一个简单的大小写转化的功能.

二、TCP socket API 详解

下面介绍程序中用到的socket API,这些函数都在sys/socket.h中.

socket()

在这里插入图片描述

  • socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
  • 应用程序可以像读写文件一样用read/write在网络上收发数据;
  • 如果socket()调用出错返回-1;
  • 对于IPv4,family参数指定为AF_INET;
  • 对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议;
  • prorocol指定为0即可.

bind()

在这里插入图片描述

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;服务器需要调用bind绑定一个固定的网络地址和端口号;
  • bind()成功返回0,失败返回-1;
  • bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号;
  • 上一章我给大家讲过,struct sockaddr*是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度.

在程序中对myaddr参数是这样初始化的:

bzero(&myaddr,sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
myaddr.sin_port = htons(PORT);

  1. 将整个结构体清零;
  2. 设置地址类型为AF_INET;
  3. 网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立连接时才确定下来到底用哪个IP地址;
  4. 端口号为PORT,默认定义为8080;

listen()

在这里插入图片描述

  • listen()声明sockdf处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略,这里设置不会太大(一般是5),
  • listen()成功返回0,失败返回-1;

accept()

在这里插入图片描述

在这里插入图片描述

  • 三次握手完成后,服务器调用accept()接受连接;
  • 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
  • 如果给addr 参数串NULL,表示不关心客户端的地址;
  • addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的,缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
  • 返回值是一个socket套接字,传入的第一个参数也是一个socket套接字,这些都是文件描述符.
  • 传入的套接字参数的核心工作是:获取新的连接(一定是有客户来连接了!)—>监听套接字
  • 返回值的套接字的核心工作:主要是为用户提供网络服务的socket,主要是进行IO服务;

connect()

在这里插入图片描述

  • 客户端需要调用connect()连接服务器;
  • connect()和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址;
  • connect()成功返回0,出错返回-1;

三、TCP网络编程

头文件

util.hpp

#include <iostream>
#include <string>
#include<cstring>
#include<cstdlib>
#include <ctype.h>

#include<signal.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h> 
#include<pthread.h>

#define SOCKET_ERR   1
#define BIND_ERR     2
#define LISTEN_ERR   3
#define USAGE_ERR    4
#define CONN_ERR     5

#define BUFFER_SIZE 1024

日志文件

log.hpp

#pragma once
#include<cstdio>
#include<ctime>
#include<cstdarg>
#include<cassert>
#include<cstdlib>
#include<cstring>
#include<cerrno>
#define DEBUG     0
#define NOTICE    1
#define WARINING  2
#define FATAL     3

const char* log_level[]={"DEBUG","NOTICE","WARINING","FATAL"};

//logMessage(DEBUG,"%d",10);
void logMessage(int level,const char* format,...)
{
    assert(level>=DEBUG);
    assert(level<=FATAL);
    char logInfo[1024];
    char* name=getenv("USER");
    va_list ap; //ap--->char*
    va_start(ap,format);

    vsnprintf(logInfo,sizeof(logInfo)-1,format,ap);

    va_end(ap); //ap=NULL

    FILE* out=(level==FATAL)?stderr:stdout;
    fprintf(out,"%s | %u | %s | %s\n",\
    log_level[level],(unsigned int)time(nullptr),\
    name==nullptr?"unknow":name,logInfo);
}

客户端

ClientTcp.cc

#include "util.hpp"
#include "log.hpp"

using namespace std;

volatile bool quit =false;

static void Usage(string proc)
{
    cerr << "Usage\n\t" << proc << " ip port" << endl;
    cerr << "Example\n\t" << proc << " 127.0.0.1 8080\n"
         << endl;
}

// 2.需要bind吗??--->需要,但是不需要显式的bind!
// 3.需要listen吗?不需要的!
// 4.需要accept吗?不需要的!
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    string serverIp = argv[1];
    uint16_t serverPort = stoi(argv[2]);

    // 1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        cerr << "socket: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // 2.connect,发起连接请求,你想谁发起请求呢?当然是想服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    inet_aton(serverIp.c_str(), &server.sin_addr);
    server.sin_port = ntohs(serverPort);
    // 2.2发送请求,connect会自动帮我们进行bind!
    if (connect(sock, (const sockaddr *)&server, sizeof(server)) != 0)
    {
        cerr << "connect: " << strerror(errno) << endl;
        exit(CONN_ERR);
    }
    cout << "info connect success: " << sock << endl;

    string message;
    while(!quit)
    {
        cout<<"请输入你的消息>> ";
        getline(cin,message);
        if(strcasecmp(message.c_str(),"quit")==0)
            quit=true;
        ssize_t s=write(sock,message.c_str(),message.size());
        if(s>0)
        {
            char outbuffer[BUFFER_SIZE];
            ssize_t s=read(sock,outbuffer,BUFFER_SIZE);
            if(s>0)
            {
                message[s]='\0';
                cout<<"Server Echo>> "<<message<<endl;
            }
        }
        else if(s<=0)
        {
            break;
        }
        message.clear();
    }

    close(sock);
    return 0;
}

由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配.

注意:

  • 客户端不是不允许bind(),只是没有必要调用bind()固定一个端口号.否则如果在同一台机器上启动多个客户端,就会出现端口号被占用导致不能正确建立连接.
  • 服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器端口号都不一样,客户端要连接服务器就会遇到麻烦;

服务器

在这里服务器分为三个版本

单进程版本


#include "util.hpp"
#include "log.hpp"
using namespace std;

class ServerTcp
{
public:
    ServerTcp(uint16_t port, string ip = "")
        : _listenSock(-1), _port(port), _ip(ip)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1.创建socket
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            logMessage(FATAL, "socket:%s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket:&s,%d", strerror(errno), _listenSock);
        // 2.bind绑定
        // 2.1填充服务器
        struct sockaddr_in local; //用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        _ip.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(_ip.c_str(), &local.sin_addr));
        // 2.2本地socket信息,写入_sock对应的内核区域
        if (bind(_listenSock, (const sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s", strerror(errno));
        // 3.监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(_listenSock, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s", strerror(errno));
        //允许别人来连接你了
    }
    void loop()
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                //获取连接失败
                logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5.提供服务,小写转大写
            // // 5.0 v0版本----单进程--一旦进行transService,主执行流就无法进行向后执行,只能提供完毕服务后才能进行accept
             transService(serviceSock, peerIp, peerPort);

            // logMessage(DEBUG,"server provide service start ...");
            // sleep(1);
        }
    }
    // TCP && UDP支持全双工
    void transService(int sock, string &clientIp, uint16_t clientPort)
    {
        assert(sock > 0);
        assert(!clientIp.empty());
        assert(clientPort > 1024);
        char inbuffer[BUFFER_SIZE];
        while (true)
        {
            ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为读到的都是字符串
            if (s > 0)
            {
                // read success
                inbuffer[s] = '\0';
                if (strcasecmp(inbuffer, "quit") == 0)
                {
                    logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                    break;
                }
                logMessage(DEBUG, "trans before: %s[%d]>> %s", clientIp.c_str(), clientPort, inbuffer);
                //可以进行大小写转化了
                for (int i = 0; i < s; i++)
                {
                    if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                        inbuffer[i] = toupper(inbuffer[i]);
                }
                write(sock, inbuffer, sizeof(inbuffer));
                logMessage(DEBUG, "trans after: %s[%d]>> %s", clientIp.c_str(), clientPort, inbuffer);
            }
            else if (s == 0)
            {
                // pipe:读端一直在读,写端不写了,并且关闭了写端,读端会如何?--->s==0,代表对端关闭
                // s==0,代表对方关闭,Client退出
                logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            else
            {
                logMessage(DEBUG, "%s[%d] -- read: %s", clientIp.c_str(), clientPort, strerror(errno));
                break;
            }
        }
        //只要走到这里,一定是client退出了,服务到此结束
        close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄露!
        logMessage(DEBUG, "server close %d done", sock);
    }

private:
    int _listenSock;
    uint16_t _port;
    string _ip;
};
static void Usage(string proc)
{
    cerr << "Usage\n\t" <<proc<<" port ip"<<endl;
    cerr << "Example\n\t" <<proc<<" 8080  127.0.0.1\n"<<endl;
}

// ./serverTcp local_port [local_ip]
int main(int argc, char * argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=stoi(argv[1]);
    string ip;
    if(argc==3)
    {
        ip=argv[2];
    }
    ServerTcp svr(port,ip);
    svr.init();
    svr.loop();
    return 0;
}

在这里插入图片描述

测试多个连接的情况
在启动一个客户端,尝试连接服务器,发现第二个客户端,不能正确的和服务器进行通信.
分析原因,是因为我们accept了一个请求之后,就一直while循环尝试read,没有继续低啊用到的accept,导致不能接受新的请求.
也就是说一旦进行transService,主执行流就无法进行向后执行,只能提供完毕服务后才能进行accept!

我们当前这个TCP,只能处理一个连接,这是不科学的.

多进程版本

这种多进程版本只在Linux下有效!因为子进程执行完代码以后就进入僵尸状态,需要等父进程来回收资源.
这种方法让父进程忽略SIGCHLD的信号,就是子进程执行完自己的代码直接退出即可,不让父进程回收自己的资源.

void loop()
    {
        signal(SIGCHLD,SIG_IGN);//只在Linux下有效
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                //获取连接失败
                logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5.提供服务,小写转大写
             //5.1 V1---多进程版本---父进程打开的文件会被子进程继承!
             pid_t id=fork();
             assert(id!=-1);
             if(id==0)
             {
                 close(_listenSock);
                 //子进程
                 transService(serviceSock, peerIp, peerPort);
                 exit(0);
             }
             //父进程
             close(serviceSock);//这一步是一定要做的!


            // logMessage(DEBUG,"server provide service start ...");
            // sleep(1);
        }
    }

创建爸爸进程之后,爸爸进程又创建了孙子进程,然后直接让爸爸进程退出,然后爷爷进程直接回收爸爸进程的资源,然后让孙子进程去执行代码,又因为孙子进程没有父进程,就是一个孤儿进程,就是被系统领养了,然后孤儿进程的资源回收就交给了系统进程.

    void loop()
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                //获取连接失败
                logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5.提供服务,小写转大写

            // //5.1 V1.1---多进程版本
            //爷爷进程
            pid_t id=fork();
            if(id==0)
            {
                //爸爸进程
                close(_listenSock);
                //又进行了一次fork
                if(fork()>0) exit(0);
                //孙子进程--就没有爸爸进程了--孤儿进程--被系统领养了--回收问题就交给了系统来回收
                transService(serviceSock, peerIp, peerPort);
                exit(0);
            }
            close(serviceSock);
            //爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
            pid_t ret=waitpid(id,nullptr,0);//就用阻塞式等待
            (void)ret;


            // logMessage(DEBUG,"server provide service start ...");
            // sleep(1);
        }
    }

多线程版本

class ServerTcp;
struct ThreadData
{
    ThreadData(int sock, string clientIp, uint16_t clientPort,ServerTcp* ts)
    :_sock(sock)
    ,_clientIp(clientIp)
    ,_clientPort(clientPort)
    ,_this(ts)
    {

    }
    int _sock;
    string _clientIp;
    uint16_t _clientPort;
    ServerTcp* _this;
};

    static void* startRountine(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td=static_cast<ThreadData*>(args);
        td->_this->transService(td->_sock,td->_clientIp,td->_clientPort);
        delete(td);
    }
    void loop()
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                //获取连接失败
                logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5.提供服务,小写转大写
   
            //5.2 v2版本---多线程
            //这里不需要关闭文件描述符了!
            //多线程是会共享文件描述符表的!
            ThreadData* td=new ThreadData(serviceSock,peerIp,peerPort,this);
            pthread_t tid;
            pthread_create(&tid,nullptr,startRountine,&td);


            // logMessage(DEBUG,"server provide service start ...");
            // sleep(1);
        }
    }

在这里插入图片描述
这样我们就可以进行正常的大小写转化服务啦!!!

四、线程池版的TCP

lock.hpp

#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock, nullptr);
    }

    void lock()
    {
        pthread_mutex_lock(&_lock);
    }
    void unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }

private:
    pthread_mutex_t _lock;
};

class LockGuard
{
public:
    LockGuard(Mutex* mutex)
        : _mutex(mutex)
    {
        _mutex->lock();
        std::cout<<"加锁成功..."<<std::endl;
    }
    ~LockGuard()
    {
        _mutex->unlock();
        std::cout<<"解锁成功..."<<std::endl;
    }

private:
    Mutex* _mutex;
};

Task.hpp

#pragma once

#include <iostream>
#include <string>
#include<functional>
#include"log.hpp"
#include<pthread.h>
class Task
{
public:

    //等价于
    // typedef std::function<void(int,std::string,uint16_t)> callback_t;
    using callback_t=std::function<void (int, std::string, uint16_t)>;

private:
    int _sock;//给用户提供任务IO服务的sock
    std::string _ip;//client ip
    uint16_t _port;//client port
    callback_t _func;//回调方法
public:
    Task():_sock(-1),_port(-1)
    {}
    Task(int sock,std::string ip,uint16_t port,callback_t func)
    :_sock(sock),_ip(ip),_port(port),_func(func)
    {}
    void operator()()
    {
        logMessage(DEBUG,"线程ID[%p]->处理%s:%d的请求 开始啦....",\
            pthread_self(),_ip.c_str(),_port);

        _func(_sock,_ip,_port);
        
        logMessage(DEBUG,"线程ID[%p]->处理%s:%d的请求 结束啦....",\
            pthread_self(),_ip.c_str(),_port);
    }

    ~Task()
    {}
};

ThreadPool.hpp

#pragma once

#include <iostream>
#include <queue>
#include <cassert>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
#include<sys/prctl.h>
#include "Task.hpp"
#include "lock.hpp"
using namespace std;

const int gThreadNum = 15;

//设计为懒汉模式
template <class T>
class ThreadPool
{
private:
    ThreadPool(int threadNum = gThreadNum)
        : _threadNum(threadNum), _isStart(false)
    {
        assert(_threadNum > 0);
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T> &) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if (nullptr == instance)//仅仅是过滤重复的判断
        {
            LockGuard lockguard(&mutex);//进入代码块,加锁,退出代码块,自动解锁
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
            }
        }
        return instance;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
    int ThreadNum()
    {
        return _threadNum;
    }

    //类内成员,成员函数都有默认参数this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        while (true)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            T t = tp->pop();
            tp->unlockQueue();
            t();//让指定的线程处理这个任务
            
        }
    }
    void start()
    {
        assert(!_isStart);
        for (int i = 0; i < _threadNum; i++)
        {
            pthread_t tmp;
            pthread_create(&tmp, nullptr, threadRoutine, this);
        }
        _isStart = true;
    }
    void push(const T &in)
    {
        lockQueue();
        _taskQueue.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }

private:
    void lockQueue() { pthread_mutex_lock(&_mutex); }
    void unlockQueue() { pthread_mutex_unlock(&_mutex); }
    bool haveTask() { return !_taskQueue.empty(); }
    void waitForTask() { pthread_cond_wait(&_cond, &_mutex); }
    void choiceThreadForHandler() { pthread_cond_signal(&_cond); }
    T pop()
    {
        T tmp = _taskQueue.front();
        _taskQueue.pop();
        return tmp;
    }

private:
    bool _isStart;
    int _threadNum;
    queue<T> _taskQueue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
    static ThreadPool<T> *instance;
};

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

serverTcp.cc

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

class ServerTcp;
struct ThreadData
{
    ThreadData(int sock, string clientIp, uint16_t clientPort, ServerTcp *ts)
        : _sock(sock), _clientIp(clientIp), _clientPort(clientPort), _this(ts)
    {
    }
    int _sock;
    string _clientIp;
    uint16_t _clientPort;
    ServerTcp *_this;
};
//大小写转化
// TCP && UDP支持全双工
void transService(int sock, const string &clientIp, uint16_t clientPort)
{
    assert(sock > 0);
    assert(!clientIp.empty());
    assert(clientPort > 1024);
    char inbuffer[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为读到的都是字符串
        if (s > 0)
        {
            // read success
            inbuffer[s] = '\0';
            if (strcasecmp(inbuffer, "quit") == 0)
            {
                logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            logMessage(DEBUG, "trans before: %s[%d]>> %s", clientIp.c_str(), clientPort, inbuffer);
            //可以进行大小写转化了
            for (int i = 0; i < s; i++)
            {
                if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                    inbuffer[i] = toupper(inbuffer[i]);
            }
            write(sock, inbuffer, sizeof(inbuffer));
            logMessage(DEBUG, "trans after: %s[%d]>> %s", clientIp.c_str(), clientPort, inbuffer);
        }
        else if (s == 0)
        {
            // pipe:读端一直在读,写端不写了,并且关闭了写端,读端会如何?--->s==0,代表对端关闭
            // s==0,代表对方关闭,Client退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] -- read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }
    //只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄露!
    logMessage(DEBUG, "server close %d done", sock);
}

class ServerTcp
{
public:
    ServerTcp(uint16_t port, string ip = "")
        : _listenSock(-1), _port(port), _ip(ip), _tp(nullptr)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1.创建socket
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            logMessage(FATAL, "socket:%s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket:&s,%d", strerror(errno), _listenSock);
        // 2.bind绑定
        // 2.1填充服务器
        struct sockaddr_in local; //用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        _ip.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(_ip.c_str(), &local.sin_addr));
        // 2.2本地socket信息,写入_sock对应的内核区域
        if (bind(_listenSock, (const sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s", strerror(errno));
        // 3.监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(_listenSock, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s", strerror(errno));
        //允许别人来连接你了

        // 4.加载线程池
        _tp = ThreadPool<Task>::getInstance();
    }
    void loop()
    {
        // signal(SIGCHLD,SIG_IGN);//只在Linux下有效
        _tp->start();
        logMessage(DEBUG, "thread pool start success,thread num: %d", _tp->ThreadNum());
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            // 4.1 _listenScok:监听&&获取新的连接--->sock
            // 4.2 serviceSock:给用户提供新的socket服务
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                //获取连接失败
                logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5.提供服务,小写转大写
            Task t(serviceSock, peerIp, peerPort, transService);
            _tp->push(t);
        }
    }

private:
    int _listenSock;
    uint16_t _port;
    string _ip;
    //引入线程池
    ThreadPool<Task> *_tp;
};
static void Usage(string proc)
{
    cerr << "Usage\n\t" << proc << " port ip" << endl;
    cerr << "Example\n\t" << proc << " 8080  127.0.0.1\n"
         << endl;
}

// ./serverTcp local_port [local_ip]
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = stoi(argv[1]);
    string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }
    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}

在这里插入图片描述
在这里插入图片描述
我把打印出来的日志消息全部放入一个日志文件中,在日志文件中可以查看到!

我们设置了对应的任务是死循环,那么线程池提供服务,就显得不太合适.因为初始化的线程数量只有五个,就代表只能有五个人连接服务器.

一般我们给线程池跑入的任务都是短任务.

更改提供的服务

这里我们需要用到一个函数popen();

在这里插入图片描述

popen()可以执行shell命令,并读取此命令的返回值;

popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程.可以通过这个管道执行标准输入输出操作.这个管道必须由pclose()函数关闭!!!而不是fclose()函数(若使用fclose()函数关闭则会产生僵尸进程.)

tyoe参数只能是读或者写中的一种,得到的返回值(标准I/O流)也具有和type相应的只读或者只写类型.如果type是"r"则文件指针连接到command的标准输出;如果type是"w"则文件指针连接到command的标准输入.

command蚕食是一个只想以NULL结束的shell命令字符串的指针.这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令!

serverTcp.cc

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

class ServerTcp;
struct ThreadData
{
    ThreadData(int sock, string clientIp, uint16_t clientPort, ServerTcp *ts)
        : _sock(sock), _clientIp(clientIp), _clientPort(clientPort), _this(ts)
    {
    }
    int _sock;
    string _clientIp;
    uint16_t _clientPort;
    ServerTcp *_this;
};

void execCommand(int sock, const string &clientIp, uint16_t clientPort)
{
    assert(sock > 0);
    assert(!clientIp.empty());
    assert(clientPort > 1024);
    char command[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, command, sizeof(command) - 1); //我们认为读到的都是字符串
        if (s > 0)
        {
            command[s]='\0';
            logMessage(DEBUG,"[%s:%d] exec [%s]",clientIp.c_str(),clientPort,command);
            std::string safe;
            if((safe.find("rm")!=std::string::npos)||(safe.find("unlink")!=std::string::npos))
            {
                break;
            }
            // 我们是以r方式打开的文件,没有写入
            //所以我们无法通过dup2的方式得到对应的结果
            FILE* fp=popen(command,"r");
            if(fp==nullptr)
            {
                logMessage(WARINING,"exec %s failed, because: %s",command,strerror(errno));
                break;
            }

            char line[1024];
            while(fgets(line,sizeof(line)-1,fp)!=nullptr)
            {
                write(sock,line,strlen(line));
            }
            // dup2(sock,fp->_fileno);
            // fflush(fp);
            pclose(fp);
            logMessage(DEBUG,"[%s]:%d exec [%s]... done",clientIp.c_str(),clientPort,command);
        }
        else if (s == 0)
        {
            // pipe:读端一直在读,写端不写了,并且关闭了写端,读端会如何?--->s==0,代表对端关闭
            // s==0,代表对方关闭,Client退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] -- read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }
    //只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄露!
    logMessage(DEBUG, "server close %d done", sock);
}

class ServerTcp
{
public:
    ServerTcp(uint16_t port, string ip = "")
        : _listenSock(-1), _port(port), _ip(ip), _tp(nullptr)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1.创建socket
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            logMessage(FATAL, "socket:%s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket:&s,%d", strerror(errno), _listenSock);
        // 2.bind绑定
        // 2.1填充服务器
        struct sockaddr_in local; //用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        _ip.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(_ip.c_str(), &local.sin_addr));
        // 2.2本地socket信息,写入_sock对应的内核区域
        if (bind(_listenSock, (const sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s", strerror(errno));
        // 3.监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(_listenSock, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s", strerror(errno));
        //允许别人来连接你了

        // 4.加载线程池
        _tp = ThreadPool<Task>::getInstance();
    }
    void loop()
    {
        // signal(SIGCHLD,SIG_IGN);//只在Linux下有效
        _tp->start();
        logMessage(DEBUG, "thread pool start success,thread num: %d", _tp->ThreadNum());
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            // 4.1 _listenScok:监听&&获取新的连接--->sock
            // 4.2 serviceSock:给用户提供新的socket服务
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                //获取连接失败
                logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            Task t(serviceSock, peerIp, peerPort, execCommand);
            _tp->push(t);

        }
    }

private:
    int _listenSock;
    uint16_t _port;
    string _ip;
    //引入线程池
    ThreadPool<Task> *_tp;
};
static void Usage(string proc)
{
    cerr << "Usage\n\t" << proc << " port ip" << endl;
    cerr << "Example\n\t" << proc << " 8080  127.0.0.1\n"
         << endl;
}

// ./serverTcp local_port [local_ip]
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = stoi(argv[1]);
    string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }
    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}

在这里插入图片描述


总结

(本章完!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/133273.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++基础】11:文件操作

文件操作 OVERVIEW文件操作一、文本文件&#xff1a;1.指定打开方式&#xff1a;2.文本文件的写操作&#xff1a;3.文本文件的读操作&#xff1a;二、二进制文件&#xff1a;1.二进制文件的写操作&#xff1a;2.二进制文件的读操作&#xff1a;程序运行时产生的数据都属于临时的…

JAVA设计模式--行为型模式--状态模式

1.状态模式&#xff08;State Pattern&#xff09; 1.1介绍 类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。 在状态模式中&#xff0c;我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。 1.2意图&#xff1a; 允许对象在…

Python数据分析案例16——水质检测(支持向量机)

本次带来图片分类的案例&#xff0c;水质检测。 数据展示 五种类别的水质&#xff0c;图片形式储存的&#xff1a; 前面1是代表水质的类别标签&#xff0c;后面是样本个数。 图片特征构建 import numpy as np import pandas as pd import matplotlib.pyplot as plt import o…

pytorch应用(入门5)CNN卷积神经网络、提取层结构、提取参数

目录第一天第二章&#xff1a;简单的神经网络第三章&#xff1a;深度学习工作流程卷积模块介绍卷积池化层池化层书中的代码池化层相关资料提取 &#xff08;各&#xff09;层&#xff08;的&#xff09;结构如何提取参数及自定义初始化LeNetAlexNetVGGNetCIFAR 10VGGNetGoogLeN…

JSP ssh医疗报销管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 JSP ssh 医疗报销管理系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用 B/S模式开发。开发环境为TOMCAT7.…

【C++基础】07:多态

多态 OVERVIEW多态一、多态1.基本概念&#xff1a;2.E1-计算器类3.纯虚函数&抽象类&#xff1a;4.E2-制作饮品5.虚析构&纯虚析构&#xff1a;6.E3-电脑组装二、运算符重载1.加号运算符重载&#xff1a;2.左移>>运算符重载&#xff1a;3.递增运算符重载&#xff1…

数据结构课设-小老鼠走迷宫(JAVA版)

学校题目: 题目内容: 程序开始运行时显示一个迷宫地图&#xff0c;迷宫中央有一只老鼠&#xff0c;迷宫的右下方有一个粮仓。游戏的任务是使用键盘上的方向健操纵老鼠在规定的时间内走到粮仓处。 基本要求&#xff1a; ⑴老鼠形象可以辨认&#xff0c;可用键盘操纵老鼠上下…

小米万兆路由器里的Docker安装可道云(Kodexplorer)私有网盘

小米2022年12月份发布了万兆路由器&#xff0c;里面可以使用Docker。 今天尝试在小米的万兆路由器里安装可道云(Kodexplorer)私有网盘。 准备工作 先将一块USB外接硬盘格式化为ext4格式&#xff0c;然后外接到小米路由器上&#xff0c; 然后创建虚拟内存&#xff0c;我这里最…

Vectornet源码详解

代码资源地址见最后 1.数据与环境配置 首先,我们需要下载这个API,方便对数据做特征工程,目前这个api还不支持windows版本 https://github.com/argoverse/argoverse-api 按照上面的步骤所说,第一步需要下载这个项目,第二步需要下载一些配置文件,并按照上述方…

Linux 信号处理简析

1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 分析背景 本文基于 ARM32 架构 Linux 4.14 内核源码进行分析。 3. 信号概述 3.1 信号分类 信号这个概念&#xff0c;起始于 UNIX 操作系统&…

4、程序计数器PC

介绍 JVM中的程序计数寄存器&#xff08;Program Counter Register&#xff09;中&#xff0c;Register的命名源于CPU的寄存器&#xff0c;寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。这里&#xff0c;并非是广义上所指的物理寄存器&#xff0c;或许…

XINDOO的2022年年终总结

已经好几个月没有认认真真写一篇博客了&#xff0c;借着年底静下心来认认真真写一篇年终总结&#xff0c;这也是我人生中第10篇的年终总结了。 先看下去年立的flag&#xff0c;不用想去年立的flag一个都没完成。首先1 算是勉强及格&#xff1b;2 redis的博客一篇没写&#xff1…

前端学习第二站——JavaScript

目录 1. 简介 2. 变量与数据类型 2.1 声明变量 2.2 基本类型 2.3 对象类型 2.3.1 函数 Function ​ 2.3.2 数组Array 2.3.3 对象 Object ⭐️⭐️ 3. 运算符和表达式 1) 2) || 4) ... 5) [] {} 4. 控制语句 1) for in 2) for of 3) try catch 1. 简介 JavaScr…

移动应用安全过去及未来发展情况思考汇总

声明 本文是学习移动安全总结 2019. 下载地址而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 序言 随着2019年的逝去&#xff0c;二十一世纪第二个十年也已随之结束。回顾过去的十年&#xff0c;我们的生活随着科技的进步发生了翻天覆地的变化&#x…

MySQL索引最佳实践及Explain详解

一、Explain工具介绍 使用EXPLAIN关键字可以模拟优化器执行SQL语句&#xff0c;分析你的查询语句或是结构的性能瓶颈在 select 语句之前增加 explain 关键字&#xff0c;MySQL 会在查询上设置一个标记&#xff0c;执行查询会返回执行计划的信息&#xff0c;而不是执行这条SQL …

leetcode 169. 多数元素-java题解

题目所属分类 超经典问题 可以选用投票法 原题链接 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 代码案例&#xff1a;输入…

java 瑞吉外卖优化day2 读写分离 ShardingJDBC

问题分析&#xff1a; mysql主从复制 介绍&#xff1a; 补充&#xff1a;从库可以有多个 提前准备好两台服务器&#xff0c;分别安装Mysql并启动服务成功 主库Master 192.168.138.100 从库Slave 192.168.138.101 Window系统则是在my.ini文件里直接配置 mysql -uroot -…

Day 16-Vue3 技术_新的组件

1.Fragment —— 片段组件 在Vue2中: 组件必须有一个根标签。 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个内置Fragment虚拟元素中&#xff0c; 最后是不参与页面渲染的&#xff01; 好处: 减少标签层级, 减小内存占用。 <template><fragment>…

UART实验

目录 一、UART 1.1简介 1.2并行通信和串行通信 1.3单工和双工 1.4波特率 1.5UART帧格式 1.6UART硬件连接 1.7UART控制器 二、Exynos4412的UART控制器 三、UART寄存器 四、UART编程 五、输入输出重定向 六、使用串口控制LED 一、UART 1.1简介 Universal Asynch…

Elasticsearch学习

基本概念 运维角度物理概念 分片&#xff08;shard&#xff09;&#xff1a;一个索引所占用的物理空间分配 primary shard&#xff1a;解决数据水平扩展问题&#xff0c;分片数据量过大时&#xff0c;可以通过增加DataNode节点扩容。一个主分片等于一个lucene实例。创建索引时…