linux篇【12】:计算机网络——tcp

news2024/11/24 11:23:26

目录

一.TCP套接字接口

1.inet_aton (和inet_addr一样,换一种方式而已)

2.listen——把套接字设置为监听状态

3.服务器获取客户端的连接 accept

返回值中套接字和参数中套接字的作用:

4.用到的部分函数

(1)strcasecmp —— 比较两个字符串,但会忽略大小写

(2)int isalpha(int c); ——是否是字母,是就返回1,不是就返回0。

(3)int islower(int c);——是否是小写字母,是就返回1,不是就返回0。

(4)int toupper(int c);——转换为大写字母。返回值是转换后的字母的值,如果无法进行转换,则为c。

(5)strcasecmp

5.客户端发起连接 connect

二.TCP套接字代码

1.注意事项

2.封装服务为多进程,多线程版本

(1)多进程版本1

(2)多进程版本2

(3)多线程版本

2.代码

clientTcp.cc

serverTcp.cc

 util.hpp


查看TCP网络服务器情况和端口使用情况

[zsh@ecs-78471 vscode]$ netstat -nltp

(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 ::1:25                  :::*                    LISTEN      -                   
[zsh@ecs-78471 vscode]$ 

一.TCP套接字接口

1.inet_aton (和inet_addr一样,换一种方式而已)

int inet_aton(const char *cp, struct in_addr *inp);(address to net 本地字符串风格IP转网络4字节IP)cp:字符串风格IP地址。inp:转换后的存到inp中。

返回值:成功返回1;失败返回0;

注意:这类函数在转变IP风格时都会自动进行主机字节序和网络字节序之间的转换。

例:

struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (
inet_aton(ip_.c_str(), &local.sin_addr));

2.listen——把套接字设置为监听状态

监听socket,为何要监听呢?——因为tcp是面向连接的!(面向 的解释:做任何工作前先做什么就是面向什么,比如面向对象就是进行任何工作前先定义对象)

面向连接:就是进行任何工作前先建立连接,让服务器在任何时候可以被客户端去连接。为了面向连接需要把套接字设置为监听状态。

将socket套接字设置为监听状态,使得套接字在任何时候都可以随时被客户端连接

man 3 listen

int listen(int socket, int backlog); 

socket:要设置的文件描述符。backlog:后面再说,现在随便写,比如5。

返回值:成功返回0,失败返回-1 错误码被设置。

3.服务器获取客户端的连接 accept

man 2 accept

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

服务器通过特定套接字sockfd获取连接

sockfd:文件描述符。src_addr和addrlen这俩参数和recvfrom后面俩参数一模一样(客户端套接字):

addr:(输出型参数)当服务器读取客户端发送的消息时——哪个客户端给你发的消息,就把这个客户端套接字信息存入addr中。(addr的类型是套接字类型指针struct sockaddr*,传入的网络套接字类型struct sockaddr_in*需要强转成此类型指针 struct sockaddr*。)

addrlen:(输入输出型参数)客户端这个缓冲区大小。(socklen_t就是unsigned int)

返回值:成功返回一个新的socketfd,错误就返回-1错误码被设置。

返回值中套接字和参数中套接字的作用:

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

小故事:景点餐厅会雇一个专门迎客的人(张三),当张三把客人引进餐厅,就由服务员服务客人了。张三就走了,接着去迎客。张三就是int sockfd 参数中套接字-监听套接字 listensocke_,服务员就是accept返回值的套接字 serviceSock 

监听套接字 listensocke_参数中套接字的核心工作:获取新的连接(当然,一定是有客户来连接了! )

serviceSock返回值中套接字的核心工作:主要是为用户提供网络服务的socket,主要是为客户提供IO服务

4.用到的部分函数

(1)strcasecmp —— 比较两个字符串,但会忽略大小写

比较两个字符串,但会忽略大小写

如果发现s1(或其前n个字节)小于s2,则返回一个小于0的整数;s1等于s2,则返回0;s1大于s2,则返回大于0的整数。

(2)int isalpha(int c); ——是否是字母,是就返回1,不是就返回0。

(3)int islower(int c);——是否是小写字母,是就返回1,不是就返回0。

(4)int toupper(int c);——转换为大写字母。返回值是转换后的字母的值,如果无法进行转换,则为c。

(5)strcasecmp

比较了两个字符串s1和s2,忽略了字符的大小写。如果发现s1分别小于、匹配或大于s2,则它返回一个小于、等于或大于0的整数。

if (strcasecmp(message.c_str(), "quit") == 0)
            quit = true;

返回值: 如果发现s1(或其前n个字节)小于、匹配或大于s2,则分别返回一个小于、等于或大小大于0的整数。

5.客户端发起连接 connect

connect 作用:通过客户端指定的流式套接字sockfd,向服务器发起链接请求(先填充需要连接的远端主机的基本信息)

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

sockfd:流式套接字。src_addr和addrlen这俩参数和sendto后面俩参数一模一样

addr:(输入型参数)向哪个主机发消息,套接字类型指针struct sockaddr*,传入的网络套接字类型struct sockaddr*需要强转成此类型指针 struct sockaddr*。

addrlen:(输入型参数)主机这个缓冲区大小。(socklen t就是unsigned int)

返回值:发送连接成功返回0,连接失败就返回-1错误码被设置

首次调用sendto 或 connect的时候 都会自动帮我们进行bind,client会自动bind自己的ip和port)

二.TCP套接字代码

1.注意事项

(1)因为TCP是流式类型套接字,所以利用read和write进行读取和写入数据。

(2)①客户端操作系统会自动bind,但是不需要自己显示的bind。

②需要listen吗?不需要的!监听本身就是等待别的客户端去连接你,所以客户端本身不需要设置监听状态,因为没人去连你的客户端

③需要accept吗?不需要的,都无法设置监听,就更不需要获取连接。

(3)TCP服务器的工作:①创建套接字。②bind绑定。③设置套接字监听状态。④服务器获取连接 accept。⑤获取客户端基本信息。⑥提供服务,读取内容后完成转换写回。

TCP客户端的工作:①创建套接字。② connect 填充服务器信息后向服务器发起链接请求。③写入数据后读出服务器转化的数据

(4)易错:服务器中

①accpet失败日志设置warnning,并continue重新获取客户端的连接

③memset(&local,0,sizeof(local));漏写(不写也行,但是写了给自己带来确定性,更好一些)

②inbuffer[s]='\0'; 漏写。

客户端中:

①volatile bool quit 漏写

② message.clear(); 和 message.resize(1024); 漏写。

2.封装服务为多进程,多线程版本

我们封装服务为多进程,多线程版本的目的:多个客户端访问时,让第一个客户端访服务器时,服务器上通过子进程为客户端提供服务,然后父进程就可以继续while循环,进行下一次阻塞式获取下一个客户端的链接并为他提供服务,是并发是进行的,

小提示:为什么不用waitpid()waitpid(); 默认是阻塞等待!我们本身就是追求多进程并发,阻塞相当于还是串行了,所以我们不能用waitpid()。那WNOHANG可以吗?——答:可以是可以,但是很麻烦,需要把各个子进程的pid保存进一个vector中,每次非阻塞等待需要轮询检测子进程pid看子进程是否退出,很麻烦,我们不选择这种方法。

(1)单进程(原始版本)

只能给一个客户提供服务,当为一个客户提供服务进入transService后,transService是死循环,除非提供完毕,否则函数不返回,则主执行流无法继续为其他客户提供服务

void loop()
    {
        signal(SIGCHLD, SIG_IGN); // only 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);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
            transService(serviceSock, peerIp, peerPort);
            
            close(serviceSock); //这一步是一定要做的!

        }
    }

(2)多进程版本1

利用signal(SIGCHLD, SIG_IGN); 父进程调用signal/sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。

注意:①父进程打开的文件会被子进程继承,所以子进程中本身用不到“接客”的listenSock_,所以建议关掉此文件描述符。close(listenSock_); //建议(类似管道关闭不需要的读写端一样)

②父进程accept创建的提供服务的文件描述符serviceSock就是让子进程继承使用的,那么子进程已经继承serviceSock后,父进程就用不到了,就需要关闭父进程对应的serviceSock。close(serviceSock); //这一步是一定要做的!(类似管道关闭不需要的读写端一样)

void loop()
    {
        signal(SIGCHLD, SIG_IGN); // only 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);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
            // transService(serviceSock, peerIp, peerPort);
            
            // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会
            pid_t id = fork();
            assert(id != -1);
            if(id == 0)
            {
                close(listenSock_); //建议
                //子进程
                transService(serviceSock, peerIp, peerPort);
                exit(0); // 进入僵尸
            }
            // 父进程
            close(serviceSock); //这一步是一定要做的!

        }
    }

监控脚本: 

(3)多进程版本2

通过创建孙子进程,孙子进程的爸爸直接终止,所以孙子进程是孤儿进程,孙子进程被系统领养,他的回收问题就交给了系统来回收。而爸爸进程通过爷爷进程来阻塞等待释放。

void loop()
    {
        // signal(SIGCHLD, SIG_IGN); // only 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);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
 // 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); //就用阻塞式
            assert(ret > 0);
            (void)ret;
  
        }
    } 

           

(4)多线程版本

利用多线程去服务客户,首先创造一个ThreadData类,方便函数方法调用transService传参。

class ServerTcp; // 申明一下ServerTcp

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;
public:
    ThreadData(uint16_t port, std::string ip, int sock,  ServerTcp *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock),this_(ts)
    {}
};  
————————上面是类外,下面是类内  
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self()); //设置线程分离
        ThreadData *td = static_cast<ThreadData*>(args);
        td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
        delete td;
        return nullptr;
    }
    void loop()
    {
        // signal(SIGCHLD, SIG_IGN); // only 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);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
 
            // 5.2 v2 版本 -- 多线程
            // 这里不需要进行关闭文件描述符吗??不需要啦
            // 多线程是会共享文件描述符表的!
            ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, (void*)td);

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

2.代码

clientTcp.cc

#include "util.hpp"
// 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
// 3. 需要listen吗?不需要的!
// 4. 需要accept吗?不需要的!

volatile bool quit = false;

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

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

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

    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout << "请输入你的消息>>> ";
        std::getline(std::cin, message);
        if (strcasecmp(message.c_str(), "quit") == 0)
            quit = true;

        ssize_t s = write(sock, message.c_str(), message.size());
        if (s > 0)
        {
            message.resize(1024);
            ssize_t s = read(sock, (char *)(message.c_str()), 1024);
            if (s > 0)
                message[s] = 0;
            std::cout << "Server Echo>>> " << message << std::endl;
        }
        else if (s <= 0)
        {
            break;
        }
    }
    close(sock);
    return 0;
}

serverTcp.cc

#include "util.hpp"
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

class ServerTcp; // 申明一下ServerTcp

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;
public:
    ThreadData(uint16_t port, std::string ip, int sock,  ServerTcp *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock),this_(ts)
    {}
};

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_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 = PF_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 struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);

        // 3. 监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(listenSock_, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
        // 运行别人来连接你了
    }
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self()); //设置线程分离
        ThreadData *td = static_cast<ThreadData*>(args);
        td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
        delete td;
        return nullptr;
    }
    void loop()
    {
        // signal(SIGCHLD, SIG_IGN); // only 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);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
            // transService(serviceSock, peerIp, peerPort);
            
            // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
            //     close(listenSock_); //建议
            //     //子进程
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0); // 进入僵尸
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!

            // 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); //就用阻塞式
            // assert(ret > 0);
            // (void)ret;

            // 5.2 v2 版本 -- 多线程
            // 这里不需要进行关闭文件描述符吗??不需要啦
            // 多线程是会共享文件描述符表的!
            ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, (void*)td);

            // waitpid(); 默认是阻塞等待!WNOHANG
            // 方案1

            // logMessage(DEBUG, "server 提供 service start ...");
            // sleep(1);
        }
    }
    // 大小写转化服务
    // TCP && UDP: 支持全双工
    void transService(int sock, const std::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]);
                }
                logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);

                write(sock, inbuffer, strlen(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:
    // sock
    int listenSock_;
    // port
    uint16_t port_;
    // ip
    std::string ip_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::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 = atoi(argv[1]);
    std::string ip;
    if(argc == 3) ip = argv[2];

    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}

 util.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

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

#define BUFFER_SIZE 1024

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

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

相关文章

技术分享 | MySQL 多版本并发控制「MVCC」

作者&#xff1a;贲绍华 爱可生研发中心工程师&#xff0c;负责项目的需求与维护工作。其他身份&#xff1a;柯基铲屎官。 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。 一、MySQL InnoD…

TaxiBGC ——分类学指导下的生物合成基因簇鉴定流程

谷禾健康 当前合成基因簇预测限制较大 微生物基因组中的生物合成基因簇 (BGC) 编码具有生物活性的次级代谢物 (SM)&#xff0c;它可以在微生物-微生物和宿主-微生物相互作用中发挥重要作用。 鉴于次级代谢物的生物学意义和当前对微生物组代谢功能的深刻兴趣&#xff0c;从高通…

通过 ffmpeg 串流对接 OBS 等直播软件

我们要将设备通过私有通道输出到 H264 流&#xff0c;传给 OBS 等直播软件使用。为此&#xff0c;设计了上图所示的串流工具。 设计思路 私有通道通过 API 接口提供 H264 流&#xff0c;要传给 ffmpeg &#xff0c;最简单的方法是通过进程间管道传输数据。这里 Dump 工具直接…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java美丽华驾校信息管理系统t93d7

毕业设计也不需要做多高端的程序&#xff0c;毕业设计对于大多数同学来说&#xff0c;为什么感觉到难&#xff0c;最重要的一个原因&#xff0c;那就是理论课到实践课的转变&#xff0c;很多人一下不适应&#xff0c;本能开始拒绝&#xff0c;如果是一个考试&#xff0c;大家都…

使用自定义函数实现数据编解码、格式处理与业务告警

背景 在物联网平台的设备数据接入场景中&#xff0c;开发者总是希望平台接入的设备数据格式标准统一&#xff0c;以便对数据进行统一处理。在实际情况中&#xff0c;由于业务需要&#xff0c;平台常常会面对不同类型、不同厂商的设备接入。即使设备接入协议已经统一使用 MQTT …

傻白探索Chiplet,Chiplet技术带来的“新四化”(三)

目录 一、IP芯片化 二、异质集成&#xff08;HeteroMaterial Integration&#xff09; 三、异构集成&#xff08;HeteroStructure Integration&#xff09; 四、IO增量化 五、总结 一、IP芯片化 IP&#xff08;Intelligent Property&#xff09;是具有知识产权核的集成电…

腾讯前端常考vue面试题(必备)

虚拟DOM的优劣如何? 优点: 保证性能下限: 虚拟DOM可以经过diff找出最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能要好很多,因此虚拟DOM可以保证性能下限无需手动操作DOM: 虚拟DOM的diff和patch都是在一次更新中自动进行的,我们无需手动…

driftingblues2靶机(nmap提权)

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;9qkq 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2021.1 信息收集 1.探测目标靶机 2.探测目标靶机开放端口和服务 3.用dirsearch扫描目录 dirsearch -u 192.168.…

π120E31兼容Si8620EC-B-IS 双通道数字隔离器

π120E31兼容Si8620EC-B-IS 双通道数字隔离器。具有出色的性能特征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器产品。 传输通道间彼此独立&#xff0c;可实现多种传输方向的配置&#xff0c;可实现3.0kVrms隔离耐压等级和 DC 到 600Mbps 信号传输。该系列…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java校园兼职招聘系统x6u36

毕业设计说实话没有想象当中的那么难&#xff0c;导师也不会说刻意就让你毕设不通过&#xff0c;不让你毕业啥的&#xff0c;你只要不是太过于离谱的&#xff0c;都能通过的。首先你得要对你在大学期间所学到的哪方面比较熟悉&#xff0c;语言比如JAVA、PHP等这些&#xff0c;数…

腾讯会议一直显示正在加入会议如何处理?

我们在使用腾讯会议时&#xff0c;一直显示正在加入会议&#xff0c;但是经过很长时间也没有反应&#xff0c;这该怎么办&#xff1f;下面小编就给大家带来了相关的解决办法&#xff0c;说不定有用。 腾讯会议一直显示正在加入会议怎么办&#xff1f; 1、手机上打开腾讯会议。 …

交易所步入「后FTX 时代」,WEEX唯客等后发新秀拉开补位战?

太阳底下没有新鲜事&#xff0c;11月上旬 FTX此轮的骤然崩溃&#xff0c;再次证明了加密行业没有「大而不能倒」的神话&#xff0c;也在一定程度上引爆了加密行业的信任危机与流动性困境。 但把盖子掀开、暴露出里面的风险&#xff0c;未尝不是一件好事——缺乏风控合规的中心…

【剧前爆米花--爪哇岛寻宝】面向对象的三大特性——封装、继承以及多态的详细剖析(下——封装)。

作者&#xff1a;困了电视剧 专栏&#xff1a;《JavaSE语法与底层详解》 文章分布&#xff1a;这是一篇关于Java面向对象三大特性——封装的文章&#xff0c;在本篇文章中我会分享封装的一些基础概念以及实现。 目录 封装定义和优点 访问限定符实现封装 private限定符 priv…

深度学习-LeNet(第一个卷积神经网络)

文章目录简介数据集模型搭建模型训练模型测试前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 简介 LeNet模型是在1998年提出的一种图像分类模型&#xff0c;应用于支票或邮件编码上的手写…

双模蓝牙MIDI模块BT401的功能简单描述和蓝牙MIDI协议

目录 一、蓝牙MIDI概念和功能简述 蓝牙MIDI&#xff0c;实际上是由苹果公司推广并且应用的&#xff0c;目的是借助于低功耗蓝牙来实现 主机和设备之间的无线连接 。协议的标准也是苹果定的。目前也充分的应用到安卓平台了 二、详细记录--功能说明 2.1 蓝牙MIDI的测试说明--m…

【笔记】计算机组成原理复习重点——篇三

计算机组成原理复习重点笔记 第二篇 计算机系统的硬件结构 第3章 系统总线第4章 存储器第5章 输入输出系统 第&#xff13;章 系统总线 3.1 总线的基本概念 一、为什么要用总线 计算机的各个系统功能部件连在一起才能协同工作&#xff0c;部件之间不可能采用全互联形式&…

MATLB|基于复杂网络的配电系统微电网优化配置

目录 一、概述 二、系统研究 三、复杂网络框架 四、结果与讨论 五、Matlab代码实现 一、概述 多年来&#xff0c;各个领域的科学家开发了一套广泛的工具&#xff1a;数学、计算和统计&#xff0c;旨在分析、建模和理解网络。网络研究的基础可以追溯到图论的发展&#xff0…

第三方软件测试机构如何选择?

什么是软件产品检测报告&#xff1f; 软件测试机构根据委托方提供的测试需求&#xff0c;对软件进行功能性的检测&#xff0c;保证软件功能能正常运行。 软件产品登记测试也是申请软件产品登记的必要条件&#xff0c;对于买方来说&#xff0c;通过第三方检测机构出具的测试报告…

yolov5修改骨干网络-使用自己搭建的网络-以efficientnetv2为例

yolov5修改骨干网络–原网络说明 yolov5修改骨干网络-使用pytorch自带的网络-以Mobilenet和efficientnet为例 yolov5修改骨干网络-使用自己搭建的网络-以efficientnetv2为例 增加网络的深度depth能够得到更加丰富、复杂的特征并且能够很好的应用到其它任务中。但网络的深度过深…

全面适配 Android12

本文目录 背景技术分析实战总结与展望 背景 2021 年 10 月 5 日 Google 发布 Android12 操作系统&#xff0c;安全性和隐私性大幅提升&#xff0c;各手机厂家陆续更新 Android12 操作系统。 2022 年随着各大 APP 应用市场推动 Android12 适配工作&#xff0c;开发者积极响应…