【Linux Network】网络编程套接字(代码练习)—TCP

本文详细介绍了Linux网络编程中的常用接口,如socket、bind、connect、listen和accept等,并展示了C/S回声通信的实现。接着,通过创建子进程和孙子进程的方式,以及使用线程和线程池来处理客户端请求,实现了服务器服务多个客户端的目标,讨论了不同方法的优缺点和资源管理问题。
摘要由CSDN通过智能技术生成

目录

1. 常用接口

2. 服务器和客户端的简单流程

3. C/S 回声通信

4. 创建子进程完成 C/S 回声通信

5. 创建孙子进程完成 C/S 回声通信

6. 创建线程完成 C/S 回声通信

7. 使用线程池完成 C/S 回声通信


Linux网络编程在✨ 本篇博文的代码虽然多,但都是修改一点点tcp_server.cc代码。tcp_client.cc、makefile代码是没有改动的,全部写出来是为了保证代码的完整性,还请大家耐心看下去!

1. 常用接口

 socket:创建套接字:

// 创建 socket 文件描述符
int socket(int domain, int type, int protocol);

返回值:

  • 套接字创建成功返回一个文件描述符 ,创建失败返回-1,同时错误码会被设置。

参数:

  • domain:网络通信设置为AF_INET(IPv4)或AF_INET6(IPv6)
  • type:基于TCP的网络通信,我们采用的就是SOCK_STREAM,叫做用户数据报服务;
  • protocol:创建套接字的协议类别,一般设置为0;

 struct sockaddr_in 结构体:

struct sockaddr_in当中的成员如下:

sin_family:表示通信机制(本地/网络)。
sin_port:表示端口号,是一个16位的整数。
sin_addr.s_addr:表示IP地址,是一个32位的整数。

bind:绑定端口号:

// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

返回值:

  • 绑定成功返回0,绑定失败返回-1,同时错误码会被设置。

参数:

  • socket:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
  • addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。

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

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

返回值说明:

  • 如果连接成功则返回0 如果失败则返回-1 错误码被设置

参数说明:

  • sockfd:特定的套接字,表示通过该套接字发起连接请求。
  • addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。

监听套接字:(TCP,服务器)

int listen(int sockfd, int backlog);

返回值:

  • 监听成功返回0,监听失败返回-1,同时错误码会被设置。

参数:

  • sockfd:需要设置为监听状态的套接字对应的文件描述符。
  • backlog:全连接队列的最大长度。如果有多个客户端同时发来连接请求,此时未被服务器处理的连接就会放入连接队列,该参数代表的就是这个全连接队列的最大长度,一般不要设置太大,设置为5或10即可。

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

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

返回值:

  • 获取连接成功返回接收到的套接字的文件描述符,获取连接失败返回-1,同时错误码会被设置。

参数说明:

  • sockfd:特定的监听套接字,表示从该监听套接字中获取连接。
  • addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:调用时传入期望读取的addr结构体的长度,返回时代表实际读取到的addr结构体的长度,这是一个输入输出型参数。

2. 服务器和客户端的简单流程

客户端:

  • 创建套接字;
  • 定义结构体;
  • 发起链接请求;
  • 业务逻辑;

服务器端:

  • 创建套接字;
  • 定义结构体;
  • 绑定端口号;
  • 监听客户端是否发送请求;
  • 接收请求;
  • 业务逻辑;

3. C/S 回声通信

结果演示:

源代码:

  • makefile
.PHONY:all
all:tcp_server tcp_client 

tcp_server:tcp_server.cc
	g++ -o $@ $^ -std=c++11
tcp_client:tcp_client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f tcp_server tcp_client
  • tcp_server.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_port"<<endl;
}

//服务
void service(int new_sock)
{
    while(true)
    {
        char buffer[1024]={0};
        ssize_t s = read(new_sock, buffer, sizeof(buffer));
        if(s>0)
        {
            buffer[s-1]='\0';
            cout<<"client say # "<<buffer<<endl;
            string message = "hello client:";
            message += buffer;
            write(new_sock, message.c_str(), message.size());
        }
        else if(s==0)
        {
            cout<<"client quit..."<<endl;
            break;
        }
        else 
        {
            cout<<"read failed!"<<endl;
            break;
        }
    }
    //在此要关闭文件描述符,否则会造成文件描述符泄露问题
    close(new_sock);
}

int main(int argc, char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return 1;
    }
    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(atoi(argv[1]));
    //3.绑定端口号
    if(bind(sock, (struct sockaddr*)&local, sizeof(local))<0)
    {
        cout<<"bind failed!"<<errno;
        return 3;
    }
    //4.监听
    if(listen(sock, 5)<0)
    {
        cout<<"listen failed!"<<errno<<endl;
        return 4;
    }
    for(; ;)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //4.接收请求
        int new_sock = accept(sock, (struct sockaddr*)&peer, &len);
        if(new_sock<0)
        {
            continue;
        }
        //获取客户端的ip
        string client_ip = inet_ntoa(peer.sin_addr);
        //获取客户端的port
        uint16_t client_port = ntohs(peer.sin_port);
        cout<<"get a new link -> : ip:["<<client_ip<<"] port:["<<client_port<<"]"<<" sock:"<<sock <<" new_sock:"<<new_sock<<endl;
        //服务
        service(new_sock);
    }
    return 0;
}
  • tcp_client.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_ip server_prot"<<endl;
}

int main(int argc, char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno<<endl;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;

    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    //客户端不用显示绑定
    //3.发起链接请求
    if(connect(sock, (struct sockaddr*)&server, len)<0)
    {
        cout<<"connect failed!"<<errno<<endl;
        return 3;
    }
    cout<<"connect success!"<<endl;
    //4.通信
    while(true)
    {
        cout<<"请输入:";
        char buffer[1024]={0};
        //从键盘读取数据
        fgets(buffer, sizeof(buffer), stdin);
        write(sock, buffer, strlen(buffer));
        ssize_t s = read(sock, buffer, sizeof(buffer));
        buffer[s]=0;
        cout<<"server say # "<<buffer<<endl;
    }
    return 0;
}

文件描述符泄露问题:

总结:上述是一种客户和服务器一对一的业务通信,在平常生活中不会使用到; 

4. 创建子进程完成 C/S 回声通信

结果演示:

源代码:

  • makefile
.PHONY:all
all:tcp_server tcp_client 

tcp_server:tcp_server.cc
	g++ -o $@ $^ -std=c++11
tcp_client:tcp_client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f tcp_server tcp_client
  • tcp_server.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_port"<<endl;
}

//服务
void service(int new_sock)
{
    while(true)
    {
        char buffer[1024]={0};
        ssize_t s = read(new_sock, buffer, sizeof(buffer));
        if(s>0)
        {
            buffer[s-1]='\0';
            cout<<"client say # "<<buffer<<endl;
            string message = "hello client:";
            message += buffer;
            write(new_sock, message.c_str(), message.size());
        }
        else if(s==0)
        {
            cout<<"client quit..."<<endl;
            break;
        }
        else 
        {
            cout<<"read failed!"<<endl;
            break;
        }
    }
    //在此要关闭文件描述符,否则会造成文件描述符泄露问题
    close(new_sock);
    exit(0);
}

int main(int argc, char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return 1;
    }

    //捕捉sigchld信号,父进程不用等待子进程
    //在Linux中父进程忽略子进程的SIGCHLD信号,子进程会自动回收资源
    signal(SIGCHLD, SIG_IGN);

    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(atoi(argv[1]));
    //3.绑定端口号
    if(bind(sock, (struct sockaddr*)&local, sizeof(local))<0)
    {
        cout<<"bind failed!"<<errno;
        return 3;
    }
    //4.监听
    if(listen(sock, 5)<0)
    {
        cout<<"listen failed!"<<errno<<endl;
        return 4;
    }
    for(; ;)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //4.接收请求
        int new_sock = accept(sock, (struct sockaddr*)&peer, &len);
        if(new_sock<0)
        {
            continue;
        }
        //获取客户端的ip
        string client_ip = inet_ntoa(peer.sin_addr);
        //获取客户端的port
        uint16_t client_port = ntohs(peer.sin_port);
        cout<<"get a new link -> : ip:["<<client_ip<<"] port:["<<client_port<<"]"<<" sock:"<<sock <<" new_sock:"<<new_sock<<endl;
        //服务
        pid_t pid = fork();
        if(pid<0)
        {
            //创建子进程失败
            continue;
        }
        else if(pid==0)
        {
            //child
            //子进程继承父进程sock new_sock文件描述符
            //避免造成文件描述符泄露问题,关闭sock
            close(sock);
            service(new_sock);
        }
        //parent
        close(new_sock);
    }
    return 0;
}
  • tcp_client.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_ip server_prot"<<endl;
}

int main(int argc, char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno<<endl;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;

    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    //客户端不用显示绑定
    //3.发起链接请求
    if(connect(sock, (struct sockaddr*)&server, len)<0)
    {
        cout<<"connect failed!"<<errno<<endl;
        return 3;
    }
    cout<<"connect success!"<<endl;
    //4.通信
    while(true)
    {
        cout<<"请输入:";
        char buffer[1024]={0};
        //从键盘读取数据
        fgets(buffer, sizeof(buffer), stdin);
        write(sock, buffer, strlen(buffer));
        ssize_t s = read(sock, buffer, sizeof(buffer));
        buffer[s]=0;
        cout<<"server say # "<<buffer<<endl;
    }
    return 0;
}

不回收资源造成僵尸进程的问题:

总结:

客户端发起链接请求;

服务端接收链接请求,创建子进程完成业务逻辑 ,捕捉SIGCHLD信号,不用等待子进程;

达到了一个服务器服务多个客户的目的;

5. 创建孙子进程完成 C/S 回声通信

结果演示:

源代码:

  • makefile
.PHONY:all
all:tcp_server tcp_client 

tcp_server:tcp_server.cc
	g++ -o $@ $^ -std=c++11
tcp_client:tcp_client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f tcp_server tcp_client
  • tcp_server.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_port"<<endl;
}

//服务
void service(int new_sock)
{
    while(true)
    {
        char buffer[1024]={0};
        ssize_t s = read(new_sock, buffer, sizeof(buffer));
        if(s>0)
        {
            buffer[s-1]='\0';
            cout<<"client say # "<<buffer<<endl;
            string message = "hello client:";
            message += buffer;
            write(new_sock, message.c_str(), message.size());
        }
        else if(s==0)
        {
            cout<<"client quit..."<<endl;
            break;
        }
        else 
        {
            cout<<"read failed!"<<endl;
            break;
        }
    }
    //在此要关闭文件描述符,否则会造成文件描述符泄露问题
    close(new_sock);
    exit(0);
}

int main(int argc, char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return 1;
    }

    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(atoi(argv[1]));
    //3.绑定端口号
    if(bind(sock, (struct sockaddr*)&local, sizeof(local))<0)
    {
        cout<<"bind failed!"<<errno;
        return 3;
    }
    //4.监听
    if(listen(sock, 5)<0)
    {
        cout<<"listen failed!"<<errno<<endl;
        return 4;
    }
    for(; ;)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //4.接收请求
        int new_sock = accept(sock, (struct sockaddr*)&peer, &len);
        if(new_sock<0)
        {
            continue;
        }
        //获取客户端的ip
        string client_ip = inet_ntoa(peer.sin_addr);
        //获取客户端的port
        uint16_t client_port = ntohs(peer.sin_port);
        cout<<"get a new link -> : ip:["<<client_ip<<"] port:["<<client_port<<"]"<<" sock:"<<sock <<" new_sock:"<<new_sock<<endl;
        //服务
        pid_t pid = fork();
        if(pid<0)
        {
            //创建子进程失败
            continue;
        }
        else if(pid==0)
        {
            //child
            //子进程继承父进程sock new_sock文件描述符
            //避免造成文件描述符泄露问题,关闭sock
            close(sock);
            if(fork()>0)  
            {
                close(new_sock);
                //退出的是子进程
                exit(0);   
            }  
            //向后走的是孙子进程
            //孙子进程会被OS进程领养
            //资源由OS回收
            service(new_sock);
        }
        //parent
        close(new_sock);
        //虽然父进程对子进程进行了等待,但子进程完成创建孙子进程后直接退出了,不会造成阻塞
        waitpid(pid, nullptr, 0);
    }
    return 0;
}
  • tcp_client.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_ip server_prot"<<endl;
}

int main(int argc, char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno<<endl;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;

    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    //客户端不用显示绑定
    //3.发起链接请求
    if(connect(sock, (struct sockaddr*)&server, len)<0)
    {
        cout<<"connect failed!"<<errno<<endl;
        return 3;
    }
    cout<<"connect success!"<<endl;
    //4.通信
    while(true)
    {
        cout<<"请输入:";
        char buffer[1024]={0};
        //从键盘读取数据
        fgets(buffer, sizeof(buffer), stdin);
        write(sock, buffer, strlen(buffer));
        ssize_t s = read(sock, buffer, sizeof(buffer));
        buffer[s]=0;
        cout<<"server say # "<<buffer<<endl;
    }
    return 0;
}

总结:

与上个模型一样都达到了一个服务器服务多个客户的目的;

只不过这个是通过创建孙子进程完成的; 

6. 创建线程完成 C/S 回声通信

结果演示:

源代码:

  • makefile
.PHONY:all
all:tcp_server tcp_client 

tcp_server:tcp_server.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcp_client:tcp_client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f tcp_server tcp_client
  • tcp_server.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_port"<<endl;
}

//服务
void service(int new_sock)
{
    while(true)
    {
        char buffer[1024]={0};
        ssize_t s = read(new_sock, buffer, sizeof(buffer));
        if(s>0)
        {
            buffer[s-1]='\0';
            cout<<"client say # "<<buffer<<endl;
            string message = "hello client:";
            message += buffer;
            write(new_sock, message.c_str(), message.size());
        }
        else if(s==0)
        {
            cout<<"client quit..."<<endl;
            break;
        }
        else 
        {
            cout<<"read failed!"<<endl;
            break;
        }
    }
    //在此要关闭文件描述符,否则会造成文件描述符泄露问题
    close(new_sock);
    return ;
}

void* HandlerRequest(void* args)
{
    //线程分离
    pthread_detach(pthread_self());
    int sock = *(int*)args;
    delete (int*)args;

    service(sock);
}

int main(int argc, char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return 1;
    }

    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(atoi(argv[1]));
    //3.绑定端口号
    if(bind(sock, (struct sockaddr*)&local, sizeof(local))<0)
    {
        cout<<"bind failed!"<<errno;
        return 3;
    }
    //4.监听
    if(listen(sock, 5)<0)
    {
        cout<<"listen failed!"<<errno<<endl;
        return 4;
    }
    for(; ;)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //4.接收请求
        int new_sock = accept(sock, (struct sockaddr*)&peer, &len);
        if(new_sock<0)
        {
            continue;
        }
        //获取客户端的ip
        string client_ip = inet_ntoa(peer.sin_addr);
        //获取客户端的port
        uint16_t client_port = ntohs(peer.sin_port);
        cout<<"get a new link -> : ip:["<<client_ip<<"] port:["<<client_port<<"]"<<" sock:"<<sock <<" new_sock:"<<new_sock<<endl;
        //服务
        //创建线程
        pthread_t tid;
        int *pram = new int(new_sock);
        pthread_create(&tid, nullptr, HandlerRequest, pram);
    }
    return 0;
}
  • tcp_client.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_ip server_prot"<<endl;
}

int main(int argc, char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno<<endl;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;

    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    //客户端不用显示绑定
    //3.发起链接请求
    if(connect(sock, (struct sockaddr*)&server, len)<0)
    {
        cout<<"connect failed!"<<errno<<endl;
        return 3;
    }
    cout<<"connect success!"<<endl;
    //4.通信
    while(true)
    {
        cout<<"请输入:";
        char buffer[1024]={0};
        //从键盘读取数据
        fgets(buffer, sizeof(buffer), stdin);
        write(sock, buffer, strlen(buffer));
        ssize_t s = read(sock, buffer, sizeof(buffer));
        buffer[s]=0;
        cout<<"server say # "<<buffer<<endl;
    }
    return 0;
}

总结:

创建子进程可以完成业务逻辑,创建线程也可以(创建成本比创建子进程少的多),采用线程分离方式,主线程不用等待创建出来的线程; 

7. 使用线程池完成 C/S 回声通信

创建线程也可完成上述业务逻辑,但每当有客户端需要通信时,才创建线程,来一个客户创建一个线程,创建线程的消耗也是蛮大的,在之前我们学习过线程池,我们可以一次创建多个线程,每当客户端需要通信时,分配线程,减少了创建线程的消耗;

结果演示:

源代码:

  • makefile
.PHONY:all
all:tcp_server tcp_client 

tcp_server:tcp_server.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcp_client:tcp_client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f tcp_server tcp_client
  • Task.hpp
#pragma once

#include <iostream>
#include <cstring>
#include <unistd.h>

namespace ns_task
{
    class Task
    {
    private:
        int sock;

    public:
        Task() : sock(-1) {}
        Task(int _sock) : sock(_sock)
        {
        }
        int Run()
        {
            //提供服务,我们是一个死循环
            // while (true)
            // {
            char buffer[1024];
            memset(buffer, 0, sizeof(buffer));
            ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = 0; //将获取的内容当成字符串
                std::cout << "client# " << buffer << std::endl;
                //拉取逻辑
                std::string echo_string = ">>>server<<<, ";
                echo_string += buffer;

                write(sock, echo_string.c_str(), echo_string.size());
            }
            else if (s == 0)
            {
                std::cout << "client quit ..." << std::endl;
                // break;
            }
            else
            {
                std::cerr << "read error" << std::endl;
                // break;
            }
            // }

            close(sock);
        }
        ~Task() {}
    };
}
  • thread_pool.hpp
#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>

namespace ns_threadpool
{
    const int g_num = 5;

    template <class T>
    class ThreadPool
    {
    private:
        int num_;
        std::queue<T> task_queue_; //该成员是一个临界资源

        pthread_mutex_t mtx_;
        pthread_cond_t cond_;

        static ThreadPool<T> *ins;

    private:
        // 构造函数必须得实现,但是必须的私有化
        ThreadPool(int num = g_num) : num_(num)
        {
            pthread_mutex_init(&mtx_, nullptr);
            pthread_cond_init(&cond_, nullptr);
        }
        ThreadPool(const ThreadPool<T> &tp) = delete;
        //赋值语句
        ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;

    public:
        static ThreadPool<T> *GetInstance()
        {
            static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
            // 当前单例对象还没有被创建
            if (ins == nullptr) //双判定,减少锁的争用,提高获取单例的效率!
            {
                pthread_mutex_lock(&lock);
                if (ins == nullptr)
                {
                    ins = new ThreadPool<T>();
                    ins->InitThreadPool();
                    std::cout << "首次加载对象" << std::endl;
                }
                pthread_mutex_unlock(&lock);
            }

            return ins;
        }

        void Lock()
        {
            pthread_mutex_lock(&mtx_);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&mtx_);
        }
        void Wait()
        {
            pthread_cond_wait(&cond_, &mtx_);
        }
        void Wakeup()
        {
            pthread_cond_signal(&cond_);
        }
        bool IsEmpey()
        {
            return task_queue_.empty();
        }

    public:
        // 在类中要让线程执行类内成员方法,是不可行的!
        // 必须让线程执行静态方法
        static void *Rountine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T> *tp = (ThreadPool<T> *)args;

            while (true)
            {
                tp->Lock();
                while (tp->IsEmpey())
                {
                    //任务队列为空,线程该做什么呢??
                    tp->Wait();
                }
                //该任务队列中一定有任务了
                T t;
                tp->PopTask(&t);
                tp->Unlock();

                t.Run();
            }
        }
        void InitThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < num_; i++)
            {
                pthread_create(&tid, nullptr, Rountine, (void *)this /*?*/);
            }
        }
        void PushTask(const T &in)
        {
            Lock();
            task_queue_.push(in);
            Unlock();
            Wakeup();
        }
        void PopTask(T *out)
        {
            *out = task_queue_.front();
            task_queue_.pop();
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&mtx_);
            pthread_cond_destroy(&cond_);
        }
    };

    template <class T>
    ThreadPool<T> *ThreadPool<T>::ins = nullptr;
} // namespace ns_threadpool
  • tcp_server.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Task.hpp"
#include "thread_pool.hpp"

using namespace std;
using namespace ns_threadpool;
using namespace ns_task;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_port"<<endl;
}

int main(int argc, char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return 1;
    }

    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(atoi(argv[1]));
    //3.绑定端口号
    if(bind(sock, (struct sockaddr*)&local, sizeof(local))<0)
    {
        cout<<"bind failed!"<<errno;
        return 3;
    }
    //4.监听
    if(listen(sock, 5)<0)
    {
        cout<<"listen failed!"<<errno<<endl;
        return 4;
    }
    for(; ;)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //4.接收请求
        int new_sock = accept(sock, (struct sockaddr*)&peer, &len);
        if(new_sock<0)
        {
            continue;
        }
        //获取客户端的ip
        string client_ip = inet_ntoa(peer.sin_addr);
        //获取客户端的port
        uint16_t client_port = ntohs(peer.sin_port);
        cout<<"get a new link -> : ip:["<<client_ip<<"] port:["<<client_port<<"]"<<" sock:"<<sock <<" new_sock:"<<new_sock<<endl;
        //服务
        //线程池
        //1. 构建一个任务
        Task t(new_sock);
        //2. 将任务push到后端的线程池即可
        ThreadPool<Task>::GetInstance()->PushTask(t);
    }
    return 0;
}
  • tcp_client.cc
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

//使用手册
void Usage(char* proc)
{
    cout<<"Usage:\n\t"<<proc<<" server_ip server_prot"<<endl;
}

int main(int argc, char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    //1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock<0)
    {
        cout<<"socket failed!"<<errno<<endl;
        return 2;
    }
    //2.定义结构体
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;

    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    //客户端不用显示绑定
    //3.发起链接请求
    if(connect(sock, (struct sockaddr*)&server, len)<0)
    {
        cout<<"connect failed!"<<errno<<endl;
        return 3;
    }
    cout<<"connect success!"<<endl;
    //4.通信
    while(true)
    {
        cout<<"请输入:";
        char buffer[1024]={0};
        //从键盘读取数据
        fgets(buffer, sizeof(buffer), stdin);
        write(sock, buffer, strlen(buffer));
        ssize_t s = read(sock, buffer, sizeof(buffer));
        buffer[s]=0;
        cout<<"server say # "<<buffer<<endl;
    }
    return 0;
}

 如果上述文章对您有所帮助的话,还请点赞👍,收藏😉,关注🎈

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瞳绣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值