c++高并发tcp网络服务器实例渐进式教程-02

这篇教程介绍了如何在Linux环境下使用C++11进行高并发TCP网络服务器的编程。从基础知识开始,包括服务端和客户端的代码编写、编译与运行,探讨了传统的串行服务端到并发服务端的演变,强调了C++11在提升socket并发性能上的应用。
摘要由CSDN通过智能技术生成

远古socket通信


一 前言

在计算机网络诞生不久的那个年代,计算机操作系统是单任务的,windows对应的是dos系统,一个时刻只允许运行一个进程,那时候的socket通信不存在并发的概念,就是单纯的串行socket通信。一个节点发送数据,一个节点接收数据。

基本的socket通信建立流程如下:

二 服务端

2.1 服务端代码编写

建立socket通信的步骤也是大多数教材的范例,以下是一个服务端的代码示例,我们可以简单回顾一下:

//server1.cpp
#include<unistd.h>
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<signal.h>

const uint16_t recv_buff_len = 1024;
const uint16_t port = 8880;

int main(int argc, char const *argv[])
{
    //创建TCP套接字文件描述符,AF_INET是指ipv4地址类型,SOCK_STREAM指流式套接字,0指流式套接字的默认协议TCP。
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if (fd == -1)
    {
        printf("create socket error!\n");
        return -1;
    }

    //因为第一步创建了ipv4地址类型的套接字文件描述符,所以选择sockaddr_in结构体,如果是ipv6选sockaddr_in6结构体,如果是本地通信文件地址类型,选sockaddr_un结构体。
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //绑定地址和端口。
    int ret = bind(fd,(struct sockaddr*)&serv_addr,sizeof(sockaddr));
    if (ret == -1)
    {
        std::cout << "bind addr error!" << std::endl;
        return -1;
    }

    //开始监听,最大连接数为1024
    if(listen(fd,1024) == -1){
        std::cout << "listen addr error!" << std::endl;
        return -1;
    }

    //用c++11的string构造发送缓冲区和接收缓冲区,string字符串的内存为连续的。&recv_buff.at(0)表示第一个元素的地址,即接收缓存的头指针。recv_buff.size()表示这段缓存的数据长度。
    std::string send_buf = R"({"radio": {"proto": "11axg","country": "Britain","bandwidth": "HT20","usrlimit": "32","chanid": "auto","bswitch": "1"}})";
    std::string recv_buff = "";
    recv_buff.resize(recv_buff_len);

    //主循环接收新连接,同时接收连接上的数据。
    while (1){
        int conn_fd = -1;
        conn_fd = accept(fd,(struct sockaddr*)NULL,NULL);
        if(-1 == conn_fd){
            std::cout << "connect error!" << std::endl;
            continue;
        }else{
            std::cout << "accept a new connet client!" << std::endl;
        }

        //收发这条连接上的数据,直到客户端断开这条连接,服务端才能跳出循环,为后面客户端提供服务。
        while (1){
            int recv_len = recv(conn_fd, &recv_buff.at(0),recv_buff.size(),0);
            if(recv_len == -1){
                std::cout << "recv data error!" << std::endl;
                close(conn_fd);
                break;
            }else if(recv_len == 0){
                std::cout << " client socket closed!" << std::endl;
                close(conn_fd);
                break;
            }
            
            std::cout << "recive client data : " << recv_buff << std::endl;
            int send_len = send(conn_fd,&send_buf.at(0),send_buf.size(),0);
            if(send_len == -1){
                std::cout << "send data error!" << std::endl;
                close(conn_fd);
                break;
            }

        }
    }
    close(fd);
    std::cout <<"quit server!" <<std::endl;
    return 0;
}

这种服务器是串行服务的,一次只能连接一个客户端,其他客户端需要排队等候,前面客户端断开连接后,服务端才能提供给新客户端提供连接,示意图如下:

2.2 服务端代码编译

g++ server1.cpp -o server1

代码编译运行环境为ubuntu20.04 server

cat /proc/version
Linux version 5.4.0-107-generic (buildd@lcy02-amd64-058) (gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)) #121-Ubuntu SMP Thu Mar 24 16:04:27 UTC 2022

2.3 服务端运行

运行服务器:

./server1

三 客户端

以下是一个客户端的代码示例:

//client1.cpp
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string>
#include<signal.h>

const uint16_t recv_buf_len = 1024;
const uint16_t port = 8880;
bool bLoop = true;
void handler(int sig){
    bLoop = false;
}
int main(int argc, char const *argv[])
{
    if(argc < 2){
        std::cout << "usage: ./client1 <ip address>" << std::endl;
        return -1;
    }

    //创建TCP套接字文件描述符,AF_INET是指ipv4地址类型,SOCK_STREAM指流式套接字,0指流式套接字的默认协议TCP。
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == fd){
        std::cout << "socket create error!" << std::endl;
        return -1;
    }

    //因为第一步创建了ipv4地址类型的套接字文件描述符,所以选择sockaddr_in结构体,如果是ipv6选sockaddr_in6结构体,如果是本地通信文件地址类型,选sockaddr_un结构体。
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);
    int ret = connect(fd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    if(-1 == ret){
        std::cout << "connet server error!" << std::endl;
        close(fd);
        return -1;
    }

    //用c++11的string构造发送缓冲区和接收缓冲区,string字符串的内存为连续的。&recv_buff.at(0)表示第一个元素的地址,即接收缓存的头指针。recv_buff.size()表示这段缓存的数据长度。
    std::string send_buf = "client1 keep alived.";
    std::string recv_buff = "";
    recv_buff.resize(recv_buf_len);

    //注册linux软中断信号SIGINT,SIGINT信号由就键盘Ctrl+C产生,handler出发后的处理函数。目的是希望客户端能ctrl+c后优雅的退出。
    signal(SIGINT,handler);

    while (bLoop){
        int send_len = send(fd,&send_buf.at(0),send_buf.size(),0);
        if(send_len == -1){
            std::cout << "send data error!" << std::endl;
            break;
        }

        sleep(0.5);
        int recv_len = recv(fd,&recv_buff.at(0),recv_buff.size(),0);

        if(recv_len == -1){
            std::cout << "receive error!" << std::endl;
            break;
        }else if(recv_len == 0){
            std::cout << " socket closed!" << std::endl;
            break;
        }
        std::cout << " receive server data: " << recv_buff << std::endl;
    }

    std::cout << "client1 quit!" <<std::endl;
    
    close(fd);
    return 0;
}

编译客户端代码:
g++ client1.cpp -o client1

启动客户端:

./client1 192.168.49.128

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值