IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> c++高并发tcp网络服务器实例渐进式教程-03 -> 正文阅读

[系统运维]c++高并发tcp网络服务器实例渐进式教程-03

上古TCP服务器


上一篇的远古服务器,是单线程+阻塞IO模式的,一次只能连接一个客户端,无法同时为多个客户端提供连接。现在我们为每个客户端的连接新建一个线程,收发客户端数据都放在各自的线程里面,互不干扰。模式如下图:

一 服务端编码

//server2.cpp
#include<unistd.h>
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<signal.h>
#include<thread>
#include<vector>

const uint16_t recv_len = 1024;
const uint16_t port = 8880;
bool bLoop = true;

void callback_func(int conn_fd){
    //用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_len);
    while (bLoop){
        int r_len = recv(conn_fd, &recv_buff.at(0),recv_buff.size(),0);
        if(r_len == -1){
            std::cout << "recv data error!" << std::endl;
            close(conn_fd);
            break;
        }else if(r_len == 0){
            std::cout << "remote client " << conn_fd << " closed!" << std::endl;
            close(conn_fd);
            break;
        }else{
            std::cout << "recive client data : " << recv_buff << std::endl;
        }
        
        int s_len = send(conn_fd,&send_buf.at(0),send_buf.size(),0);
        if(s_len == -1){
            std::cout << "send data error!" << std::endl;
            close(conn_fd);
            break;
        }
    }  
}

int main(int argc, char const *argv[])
{
    std::vector<std::thread> vec_conn;
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if (fd == -1){
        printf("create socket error!\n");
        return 0;
    }

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

    if (bind(fd,(struct sockaddr*)&serv_addr,sizeof(sockaddr)) == -1){
        std::cout << "bind addr error!" << std::endl;
        return 0;
    }

    if(listen(fd,1024) == -1){
        std::cout << "listen addr error!" << std::endl;
        return 0;
    }

    //在阻塞io模式下,循环接新的客户端连接。
    while (1){
        int 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!" << "conn id=" << conn_fd << std::endl;
        }
        //把新连接与新的线程绑定。使用c++11的线程库
        std::thread t(callback_func,conn_fd);

        //把线程设置为后台模式。
        t.detach();

        //把新线程对象保存到容器,防止对象释放。
        vec_conn.push_back(std::move(t));
    }
    close(fd);
    vec_conn.clear();
    std::cout <<"quit server!" <<std::endl;
    return 0;
}

二 服务端编译运行

编译:
g++ -std=c++11 server2.cpp -lpthread -o server2
运行:
./server2

三 客户端编译运行

客户端代码client1.cpp沿用上一节的代码,编译步骤和启动方法不变。

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

启动客户端:

./client1 192.168.49.128

在这里插入图片描述

可以再启动多个客户端,验证服务器能否并发。

./client1 192.168.49.128

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

四 问题与总结

我们看到这个服务器可以同时为多个客户端提供连接服务,我们用主线程+多子线程+阻塞IO模型,服务器具备了初步的并发能力。
服务器每收到一个客户端连接就会开辟单独新的线程对象,但是开辟线程是要耗费系统资源的,线程数量不能无限大。而且随着线程数量的上升,线程之间的切换的开销成指数级增长。这种形式的并发量是有限的,远远不能满足现代成千上百万的并发量。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-23 11:09:45  更:2022-04-23 11:11:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 19:50:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码