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

news2024/11/16 7:00:03

目录

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

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

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

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

相关文章

动态规划--最长公共子序列

最长公共子序列 动态规划算法思想最长公共子序列题目最优解结构性质递归方程递归实现核心函数测试测试结果 非递归实现(画表)核心函数测试测试结果 求出具体的子序列 动态规划算法思想 动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题﹐即将大规模变成…

通付盾携数智反欺诈应用防护解决方案亮相2023金融展

精彩亮相 银行数字化转型需求背景 数据驱动发展 数字经济时代&#xff0c;数据成为发展的重要资产&#xff0c;以数据驱动决策智能已是未来发展的必然趋势&#xff0c;智能化的决策将是重塑核心竞争力的关键抓手。 人工转向智能 银行的监测管理在一般业务场景中&#xff0c;…

Kyligence Zen 产品体验----设备销量商业数据

介绍 Kyligence Zen 是基于 Kyligence 核心 OLAP能力打造的一站式指标平台。凭借集业务模型、指标管理、指标加工、数据服务等于一体的解决方案,Kyligence 协助过多家金融、零售、制造企业客户搭建企业级指标平台。Kyligence Zen 是 Kyligence 基于丰富的指标平台建设实践打造…

MySQL优化二索引使用

1、索引分类 类型解释全局索引(FULLTEXT)全局索引&#xff0c;目前只有 MyISAM 引擎支持全局索引&#xff0c;它的出现是为了解决针对文本的模糊查询效率较低的问题&#xff0c;并且只限于 CHAR、VARCHAR 和 TEXT 列哈希索引(HASH)哈希索引是 MySQL 中用到的唯一 key-value 键…

《通过并行蒙特卡洛方法合成桡动脉的光电容积图(PPG),及其与体重指数(BMI)的相关性》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课…

界面控件DevExpress WPF富文本编辑器,让系统拥有Word功能(二)

DevExpress WPF控件的富文本编辑器允许开发者将文字处理功能集成到下一个WPF项目中&#xff0c;凭借其全面的文本格式选项、邮件合并以及丰富的终端用户选项集合&#xff0c;可以轻松地提供Microsoft Word功能。 DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足…

图片生成功能,ChatGPT和New Bing谁更厉害?

大家好&#xff0c;我是可夫小子&#xff0c;关注AIGC、读书和自媒体。解锁更多ChatGPT、AI绘画玩法。加我&#xff0c;备注&#xff1a;chatgpt&#xff0c;拉你进群。 ChatGPT和New Bing虽然是大语言模型&#xff0c;但也有「生成图」的能力&#xff0c;它们该如何调教&#…

人员拥挤检测系统 yolov5

人员拥挤检测系统通过YOLOv5网络模型算法技术&#xff0c;人员拥挤检测系统算法模型对校园/厂区车间/街道等场景的异常的人群聚集&#xff08;出现拥挤情况&#xff09;时&#xff0c;立刻抓拍存档并通知相关人员及时处理。在介绍Yolo算法之前&#xff0c;首先先介绍一下滑动窗…

“AIGC+”将在经济社会各领域持续大放异彩

Gartner 将生成性 Al 列为 2022 年 5大影响力技术之一。MIT 科技评论也将 Al 合成数据列为 2022 年十大突破性技术之一&#xff0c;甚至将生成性 AI&#xff08;Generative Al&#xff09;称为是 AI 领域过去十年最具前景的进展。未来&#xff0c;兼具大模型和多模态模型的 AIG…

第三十六章 Unity动画编辑器

本章节我们简单介绍一下Animation动画编辑窗口&#xff0c;请大家区别之前的Animation组件哦。首先&#xff0c;我们创建一个新的场景“SampleScene4.unity”&#xff0c;然后创建一个Plane和Cube&#xff0c; 给上图中的Cube添加一个木质材质最快的办法&#xff0c;就是将一张…

易基因:2023年植物表观转录组研究的最新进展(m6A+m5C)|深度综述

大家好这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 被称为表观转录组&#xff08;epitranscriptome&#xff09;的RNA修饰正成为基因调控的广泛调控机制。由于绘制转录组范围RNA修饰测序策略的改进&#xff0c;以及分别对沉积、去除和识别RNA修饰的wri…

谈谈HMI 的自动化生成技术

人机界面&#xff08;HMI&#xff09;是自动化领域不可或缺重要组成部分。尽管人机界面系统的设计看上去并没有太大的技术门槛&#xff0c;但是设计一个HMI系统的工作量是巨大的。如果你没有足够的耐心便完成不了一个通用的HMI系统。构建UI控件库是一个似乎永远完不成的事情&am…

【SWAT水文模型】SwatWeather软件使用教程

SwatWeather软件使用教程 1 SwatWeather天气模型发生器1.1 数据输入 2 各功能介绍2.1 计算降水2.2 计算气温2.3 计算辐射2.4 计算风速2.5 计算露点 参考 1 SwatWeather天气模型发生器 SwatWeather.exe 软件只要输入一定格式要求的文件&#xff0c;就可以根据提示进行所需 数据…

深入浅出循环语句—【C语言】

分支语句博客&#xff1a;http://t.csdn.cn/U2kZF 目录 ​编辑 前言&#xff1a;我们先来了解一下break 、continue在循环中的作用 1. while循环 while循环中的break while循环中的continue 2. for循环 for循环省略出错举例&#xff1a; for循环中的break for循环中的co…

JUC多并发编程 AQS

基础解释&#xff1a; 是用来实现锁或者其他同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石&#xff0c;主要用于锁分配给“谁”的问题。整体就是一个抽象的 FIFO 队列来完成资源获取线程的排队工作&#xff0c;并通过一个 int 类变量表示持有锁的状…

编译器的优化问题(构造、拷贝)、linux如何取消优化。

编译器优化问题&#xff1a; 不同编译器优化是不一样的&#xff0c;下面代码我都用的vs2019&#xff0c;并且在Debud模式下。&#xff08;Release也会进行优化&#xff09; 下面测试的时候我先采用Debug模式测试。 先写一个简单的类&#xff0c;进行打印测试&#xff1a; c…

从血缘进化论的角度,破解婆媳关系的世纪难题

从血缘进化论的角度&#xff0c;破解婆媳关系的世纪难题 有个粉丝的留言&#xff0c;很长很复杂&#xff0c;是关于他们家的婆媳关系问题。 青木老师&#xff0c;您好&#xff0c;我也有一些问题想咨询您&#xff0c;是关于婆媳关系的&#xff0c;字数有些多&#xff0c;分开…

多线程【线程概念+线程控制】

前置知识 在谈多进程之前&#xff0c;我们在谈一谈页表&#xff0c;在语言中:char* str”hello world”; *str”H”;运行时会报错&#xff0c;原因在于&#xff1a;字符串在已初始化数据区和代码区之间的&#xff0c;需要写的时候&#xff0c;我们需要对str进行虚拟地址和物理…

springboot第17集:Spring我的春天

Spring是一个开源免费的框架和容器&#xff0c;具有轻量级和非侵入式的特点。它支持控制反转(IoC)和面向切面(AOP)&#xff0c;同时提供了对事务和其他框架的支持。因此&#xff0c;简单来说&#xff0c;Spring就是一个轻量级的IoC和AOP容器框架。 假设有一个应用程序需要使用数…

【Golang】多线程爬虫的实现

〇、前言 Golang 是一种并发友好的语言&#xff0c;使用 goroutines 和 channels 可以轻松地实现多线程爬虫。具体地说&#xff0c;实现的是多协程。协程是一种比线程更轻量化的最小逻辑可运行单位&#xff0c;它不受操作系统调度&#xff0c;由用户调度。因此对于协程并发的控…