linux篇【12】:网络套接字<中序>——tcp

目录

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

一.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.注意事项

tcp的服务器和客户端流程:

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

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

(2)多进程版本1

(3)多进程版本2

tcp过程:

(4)多线程版本

2.代码

clientTcp.cc

serverTcp.cc

 util.hpp


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

[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;

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

(2)ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));

如果手动传./serverTcpd 8080 0,IP地址传的是0,此时inet_aton(0, &local.sin_addr) 即inet_aton第一个参数传的就是0,把值为0的本地字符串风格IP转网络4字节IP存入local.sin_addr中;因为INADDR_ANY 这个宏的值就是0,0是字符串风格还是网络风格无所谓,所以就等价于:inet_aton(INADDR_ANY, &local.sin_addr));,也就等价于local.sin_addr.s_addr = INADDR_ANY;

总结:inet_aton(ip_.c_str(), &local.sin_addr) ip_为0时,inet_aton(0, &local.sin_addr) 等于 local.sin_addr.s_addr = INADDR_ANY。

例:

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获取连接。(accept  默认阻塞式等待)

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吗?不需要的,都无法设置监听,就更不需要获取连接。

tcp的服务器和客户端流程:

(3)TCP服务器的工作

init():①创建套接字。②填充服务器信息struct sockaddr_in。③将套接字和sockaddr_in 绑定bind。④socket设置为监听状态。

loop():⑤accept获取链接并获取客户端IP和port。⑥提供服务,读取内容后完成转换写回。

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

tcp过程:

init():①创建套接字。②填充服务器信息struct sockaddr_in。③将套接字和sockaddr_in 绑定bind。④socket设置为监听状态。

loop():⑤accept获取链接并获取客户端IP和port。⑥爷爷进程创建父亲,父亲二次fork创建孙子并自己退出,孙子进程去提供服务(所以只需要serviceSock_,父亲要提前关掉没用的监听套接字listenSock_),爷爷在外面wait父亲并重复进行接客的工作(所以爷爷要关掉没用的服务套接字serviceSock_,留下监听套接字listenSock_)

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

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

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 23
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值