【Linux】一篇文章搞定 CPP模拟实现TCP协议下socket通信

1. TCP 编程流程图

在这里插入图片描述

2. 数据收发阶段使用的API

#include <sys/types.h>
#include <sys/socket.h>

int send(int s, const void *msg, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

2.1 send接口

int send(int sockfd, const void *msg, size_t len, int flags);
  • sockfd:客户端调用则填入客户端创建出来的套接字描述符;服务端调用则填入服务端创建出来的用于数据收发的套接字描述符
  • msg:待发送的数据
  • len:待发送的数据的长度
  • flags:填入0表示阻塞发送

返回值:小于0表示失败

2.2 recv接口

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd:客户端调用则填入客户端创建出来的套接字描述符;服务端调用则填入服务端创建出来的用于数据收发的套接字描述符
  • buf:接收数据的缓冲区
  • len:缓冲区的最大接受数据长度
  • flags:填入0表示阻塞接收

返回值:

返回值ssize_t含义
<0接收失败
=0对端关闭连接
>0接收了多少个字节数据

3. 两个队列

  • TCP编程中有两个队列:未完成连接队列已完成连接队列
  • 未完成连接队列:保存目前正在处于三次握手之中的连接
  • 已完成连接队列:保存的是已经完成三次握手的连接

4. 总结TCP 编程双端流程

在这里插入图片描述

5. 单线程:TCP协议下 单客户端与单服务端 socket通信

5.1 客户端代码

#include <stdio.h>                                                                  
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;

int main()
{
    // 1. 创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    // 2. 发起连接
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("192.168.153.128");
    addr.sin_port = htons(19999);
    int ret = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret < 0)
    {
        perror("connect");
        return -1;
    }

    while(1)
    {
        // 3. 发送数据
        string s;
        cout<<"You want to say:";
        fflush(stdout);
        getline(cin,s);
        ret = send(sockfd, s.c_str(),s.size(),0);
        if(ret < 0)
        {
            perror("send");
            continue;
        }

        // 4. 接受数据
        char buf[1024] = {0};
        ret = recv(sockfd, buf,strlen(buf)-1,0);
        if(ret < 0)
        {
            perror("recv");
            continue;
        }
        else if(ret == 0)
        {
            printf(" server shutdown!\n");
            break;
        }
        cout << "server say:" << buf << endl;
    }


    // 5. 关闭套接字
    close(sockfd);

    return 0;
}

5.2 服务端代码

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>                                                                        
#include <iostream>
using namespace std;

int main()
{
    // 1. 创建套接字
    //创建一个使用流式套接字+IPv4的套接字
    int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(listen_sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    // 2. 绑定地址信息  
    char ip_str[] = "192.168.153.128";
    uint32_t ip = inet_addr(ip_str);
    uint16_t port = htons(19999);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ip;
    addr.sin_port = port;
    int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }

    // 3. 监听:告诉内核已经准备好可以接受连接啦
    ret = listen(listen_sockfd, 1);
    if(ret < 0)
    {
        perror("listen");
        return -1;
    }

    // 4.接受连接 
    int new_sockfd = accept(listen_sockfd, NULL, NULL);
    if(new_sockfd < 0)
    {
        perror("accept");
        return -1;
    }

    while(1)
    {
        // 5. 接收数据
        char buf[1024] = { 0 };
        ret = recv(new_sockfd, buf, strlen(buf) - 1, 0);
        if(ret < 0)
        {
            perror("recv");
            continue;
        }
        else if(ret == 0)
        {
            // 若客户端已经关闭连接,则需要关闭用于数据收发的套接字
            printf(" client shutdown !\n");
            close(new_sockfd);
            break;
        }
        cout << "client say:" << buf << endl;

        // 6. 发送数据
        string s;
        cout << "you want to say:";
        getline(cin,s);
        ret = send(new_sockfd,s.c_str(),s.size(),0);
        if(ret < 0)
        {
            perror("send");
            continue;
        }
    }

    // 7. 关闭监听套接字
    close(listen_sockfd);

    return 0;
}

5.3 两个终端交互演示

[gongruiyang@localhost client]$ g++ cli.cpp -o client
[gongruiyang@localhost client]$ ./client 
You want to say:hello Server!
server say:hello Client!
You want to say:Bye~
server say:Bye~
You want to say:^C
[gongruiyang@localhost client]$ g++ cli.cpp -o client
[gongruiyang@localhost client]$ ./client 
You want to say:hello Server!
server say:hello Client!
You want to say:Bye~
server say:Bye~
You want to say:^C

5.4 尝试多客户端连接发现问题

  • 再启动一个客户端,尝试连接正在运行的服务端,发现不能与服务端正常通信

  • 原因分析:是因为我们accept到一个连接请求后,一直在while中循环接收与发送数据,没有继续调用accept,导致不能接收新的连接请求

  • 所以当前的这个TCP,是无法处理多个连接的

  • 解决方法:在服务端使用多线程多线程来处理

6. 多进程:TCP协议下 多客户端与单服务端 socket通信

6.1 客户端代码

#include <stdio.h>                                                                  
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;

int main()
{
    // 1. 创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    // 2. 发起连接
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("192.168.153.128");
    addr.sin_port = htons(19999);
    int ret = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret < 0)
    {
        perror("connect");
        return -1;
    }

    while(1)
    {
        // 3. 发送数据
        string s;
        cout<<"You want to say:";
        fflush(stdout);
        getline(cin,s);
        ret = send(sockfd, s.c_str(),s.size(),0);
        if(ret < 0)
        {
            perror("send");
            continue;
        }

        // 4. 接受数据
        char buf[1024] = {0};
        ret = recv(sockfd, buf,strlen(buf)-1,0);
        if(ret < 0)
        {
            perror("recv");
            continue;
        }
        else if(ret == 0)
        {
            printf(" server shutdown!\n");
            break;
        }
        cout << "server say:" << buf << endl;
    }


    // 5. 关闭套接字
    close(sockfd);

    return 0;
}

6.2 服务端代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <iostream>
#include <string>
using namespace std;

#include <signal.h>
#include <wait.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void signal_handler(int signum)
{
    wait(NULL);
}

int main()
{
    // 1. 创建套接字
    //创建一个使用流式套接字+IPv4的套接字
    int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(listen_sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    // 2. 绑定地址信息  
    char ip_str[] = "192.168.153.128";
    uint32_t ip = inet_addr(ip_str);
    uint16_t port = htons(19999);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ip;
    addr.sin_port = port;
    int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }

    // 3. 监听:告诉内核已经准备好可以接受连接啦
    ret = listen(listen_sockfd, 1);
    if(ret < 0)
    {
        perror("listen");
        return -1;
    }

    // 为了防止子进程退出后产生僵尸进程,需要处理子进程发出的SIGCHLD信号
    signal(SIGCHLD,signal_handler);

    while(1)
    {
        int new_sockfd = accept(listen_sockfd, NULL, NULL);
        if(new_sockfd < 0)
        {
            perror("accept");
            continue;
        }

        // 4.接受连接 
        //接收到了新连接,需要创建子进程去处理
        pid_t pid = fork();
        if(pid < 0)
        {
            perror("fork");
            continue;
        }
        else if (pid == 0)
        {
            close(listen_sockfd);
            while(1)
            {
                // 5. 接收数据
                char buf[1024] = { 0 };
                ret = recv(new_sockfd, buf, strlen(buf) - 1, 0);
                if(ret < 0)
                {
                    perror("recv");
                    continue;
                }
                else if(ret == 0)
                {
                    // 若客户端已经关闭连接,则需要关闭用于数据收发的套接字
                    printf(" client shutdown !\n");
                    close(new_sockfd);
                    break;
                }
                cout << "client say:" << buf << endl;

                // 6. 发送数据
                string s;
                cout << "you want to say:";
                getline(cin,s);
                ret = send(new_sockfd,s.c_str(),s.size(),0);
                if(ret < 0)
                {
                    perror("send");
                    continue;
                }
            }
        }
        close(new_sockfd);
    }


    // 7. 关闭监听套接字
    close(listen_sockfd);

    return 0;
}

6.3 三个终端交互演示

服务端

[gongruiyang@localhost server]$ ./pro_svr 
client say:I'm Client1
you want to say:Hello Client1
client say:I'm Client2
you want to say:Hello Client2
^C

客户端1

[gongruiyang@localhost client]$ ./client 
You want to say:I'm Client1
server say:Hello Client1
You want to say:1
 server shutdown!

客户端2

[gongruiyang@localhost client]$ ./client 
You want to say:I'm Client2
server say:Hello Client2
You want to say:1
 server shutdown!

7. 多线程:TCP协议下 多客户端与单服务端 socket通信

7.1 客户端代码

#include <stdio.h>                                                                  
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;

int main()
{
    // 1. 创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    // 2. 发起连接
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("192.168.153.128");
    addr.sin_port = htons(19999);
    int ret = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret < 0)
    {
        perror("connect");
        return -1;
    }

    while(1)
    {
        // 3. 发送数据
        string s;
        cout<<"You want to say:";
        fflush(stdout);
        getline(cin,s);
        ret = send(sockfd, s.c_str(),s.size(),0);
        if(ret < 0)
        {
            perror("send");
            continue;
        }

        // 4. 接受数据
        char buf[1024] = {0};
        ret = recv(sockfd, buf,strlen(buf)-1,0);
        if(ret < 0)
        {
            perror("recv");
            continue;
        }
        else if(ret == 0)
        {
            printf(" server shutdown!\n");
            break;
        }
        cout << "server say:" << buf << endl;
    }


    // 5. 关闭套接字
    close(sockfd);

    return 0;
}

7.2 服务端代码

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <iostream>
using namespace std;                                                                                             

#include <pthread.h>

struct SockVal
{
    int new_sockfd;
};

void* work_task(void* arg)
{
    pthread_detach(pthread_self());
    SockVal* sv = (SockVal*)arg;
    int new_sockfd = sv->new_sockfd;

    while(1)
    {
        // 5. 接收数据
        char buf[1024] = { 0 };
        int ret = recv(new_sockfd, buf, strlen(buf) - 1, 0);
        if(ret < 0)
        {
            perror("recv");
            continue;
        }
        else if(ret == 0)
        {
            // 若客户端已经关闭连接,则需要关闭用于数据收发的套接字
            printf(" client shutdown !\n");
            close(new_sockfd);
            break;
        }
        cout << "client say:" << buf << endl;

        // 6. 发送数据
        string s;
        cout << "you want to say:";
        getline(cin,s);
        ret = send(new_sockfd,s.c_str(),s.size(),0);
        if(ret < 0)
        {
            perror("send");
            continue;
        }
    }
    delete sv;
    return NULL;
}

int main()
{
    // 1. 创建套接字
    //创建一个使用流式套接字+IPv4的套接字
    int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(listen_sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    // 2. 绑定地址信息  
    char ip_str[] = "192.168.153.128";
    uint32_t ip = inet_addr(ip_str);
    uint16_t port = htons(19999);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ip;
    addr.sin_port = port;
    int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }

    // 3. 监听:告诉内核已经准备好可以接受连接啦
    ret = listen(listen_sockfd, 1);
    if(ret < 0)
    {
        perror("listen");
        return -1;
    }

    while(1)
    {
        // 4.接受连接 
        int new_sockfd = accept(listen_sockfd, NULL, NULL);
        if(new_sockfd < 0)
        {
            perror("accept");
            continue;
        }
        // 使用多线程处理 接受的连接    
        SockVal* sv = new SockVal();
        sv->new_sockfd = new_sockfd;
        pthread_t ptid;
        int ret = pthread_create(&ptid,NULL,work_task,(void*)sv);
        if(ret < 0)
        {
            perror("pthread_create");
            continue;
        }
    }

    // 7. 关闭监听套接字
    close(listen_sockfd);

    return 0;
}

7.3 三个终端交互演示

Server

[gongruiyang@localhost server]$ ./server 
client say:Hi! I'm ClientA!
you want to say:Hi! ClientA! I'm Server!
client say:Hi! I'm ClientB!
you want to say:Hi! ClientB! I'm Server!
client say:Bye~~~
you want to say:Bye~~
 client shutdown !
client say:Bye~~
you want to say:Bye~~
 client shutdown !
^C

ClientA

[gongruiyang@localhost client]$ ./client 
You want to say:Hi! I'm ClientA!
server say:Hi! ClientA! I'm Server!
You want to say:Bye~~~
server say:Bye~~
You want to say:^C

ClientB

[gongruiyang@localhost client]$ ./client 
You want to say:Hi! I'm ClientB!
server say:Hi! ClientB! I'm Server!
You want to say:Bye~~
server say:Bye~~
You want to say:^C
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,针对你的问题,这里是一个使用 QSslSocket 编写 TCP 服务器并进行单向认证的示例代码: ```cpp #include <QtNetwork> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // 创建 SSL 证书 QSslCertificate cert; cert.fromPath("server.crt"); // 创建 SSL 密钥 QSslKey key; key.fromPath("server.key", QSsl::Rsa); // 创建 TCP 服务器 QTcpServer server; if (!server.listen(QHostAddress::Any, 8888)) { qWarning() << "Unable to start the server:" << server.errorString(); return 1; } qDebug() << "Server is listening on port 8888..."; // 监听新连接 QObject::connect(&server, &QTcpServer::newConnection, [&]() { // 获取客户端套接字 QTcpSocket *socket = server.nextPendingConnection(); qDebug() << "New client connected:" << socket->peerAddress().toString(); // 进行 SSL 握手 QSslSocket *sslSocket = new QSslSocket(socket); sslSocket->setLocalCertificate(cert); sslSocket->setPrivateKey(key); sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone); sslSocket->startServerEncryption(); // 处理 SSL 握手成功信号 QObject::connect(sslSocket, &QSslSocket::encrypted, [&]() { qDebug() << "SSL handshake completed successfully."; // 发送欢迎消息 sslSocket->write("Welcome to the server!\r\n"); sslSocket->flush(); }); // 处理 SSL 错误信号 QObject::connect(sslSocket, &QSslSocket::sslErrors, [&](const QList<QSslError> &errors) { qDebug() << "SSL error occurred:" << errors; sslSocket->close(); }); // 处理客户端消息 QObject::connect(sslSocket, &QSslSocket::readyRead, [&]() { qDebug() << "Received data from client:" << sslSocket->readAll(); }); // 处理客户端断开连接 QObject::connect(sslSocket, &QSslSocket::disconnected, [&]() { qDebug() << "Client disconnected."; sslSocket->deleteLater(); }); }); return app.exec(); } ``` 在上述示例代码中: - 我们首先创建了一个 `QSslCertificate` 对象和一个 `QSslKey` 对象,用于对 SSL 连接进行身份验证; - 然后,我们创建了一个 `QTcpServer` 对象,并监听端口 8888; - 在有新连接到来时,我们会获取客户端套接字,并创建一个 `QSslSocket` 对象,用于进行 SSL 握手; - 在 SSL 握手成功后,我们会向客户端发送欢迎消息,并处理客户端发送过来的消息; - 如果在 SSL 握手过程中发生错误,我们会将 SSL 套接字关闭。 请注意,为了使示例代码更加简洁,我们在 `setPeerVerifyMode` 中将验证模式设置为 `QSslSocket::VerifyNone`,这意味着我们在单向认证中并没有对客户端进行身份验证。如果你需要进行双向认证,可以将验证模式设置为 `QSslSocket::VerifyPeer` 并实现 `QSslSocket::peerVerify` 信号的处理函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值