基于网络io的多线程TCP服务器

1.基础介绍

网络io是指使用套接字(socket)进行网络连接后,实现输入输出的操作(I nput,Output),是网络通信的基础。

服务器是可以接收和发出信息的载体。

2.实现流程

2.1搭建基础服务器

创建套接字

cpp 复制代码
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
//创建失败的处理
if(-1 == sockfd){
    printf("sockfd: %s\n", strerror(errno));
    exit(1);
}

初始化服务器配置

cpp 复制代码
//存储地址信息的结构体
struct sockaddr_in servaddr;
//使用IPV4协议
servaddr.sin_family = AF_INET; 
//允许本机任意IP地址访问
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//设定端口号,0-1023需要root权限
servaddr.sin_port = htons(8080);

将套接字与服务器信息绑定

cpp 复制代码
//通过返回值判断绑定是否成功
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
    printf("bind failed: %s\n", strerror(errno));
    exit(1);
}

开启监听

cpp 复制代码
listen(sockfd, 10);
//监听成功提示
printf("listen finished\n");

程序结束

cpp 复制代码
//通过读取字符函数,暂停程序,防止过早结束
getchar();
//退出提示
printf("exit\n");

运行程序,输出如下(监听功能正常)

listen finished

使用 netstat -anop | grep 8080指令可以显示与指定端口相关的网络连接信息,如下

tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 18353/./2.1.1io off (0.00/0/0)

成功创建一个不限制客户端ip的tcp服务器,且处于监听状态

使用网络助手建立对应ip和端口的连接后,再次检查端口相关信息,如下

tcp 1 0 0.0.0.0:8080 0.0.0.0:* LISTEN 18353/./2.1.1io off (0.00/0/0)

tcp 32 0 192.168.147.130:8080 192.168.147.1:56200 ESTABLISHED - off (0.00/0/0)

成功建立tcp连接,且发送了32字节的数据

2.2实现接收和返回数据功能

声明客户端地址变量

cpp 复制代码
struct sockaddr_in clientaddr;
//计算长度用于接收数据函数的参数
socklen_t len = sizeof(clientaddr);

等待接收客户端连接请求

cpp 复制代码
//提示开始接收
printf("accept\n");
//accept函数为监听套接字接收一个客户端的连接请求
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
//接收失败的处理
if(-1 == clientfd){
    printf("accept: %s\n", strerror(errno));
    exit(1);
}
//提示接收成功
printf("accept finished\n");

接收并打印数据

cpp 复制代码
//存储接收数据的缓冲区变量
char buffer[1024] = {0};
//从客户端套接字中读取数据并存储到缓冲区中
int count = recv(clientfd, buffer, 1024, 0);    
//打印数据
printf("RECV: %s\n", buffer);

再向客户端返回数据

cpp 复制代码
//返回接收数据长度的数据,防止发送错误
count = send(clientfd, buffer, count, 0);
//打印发送长度
printf("send: %d\n", count);

运行程序

程序启动时输出

listen finished

客户端进行连接后输出

accept

accept finished

客户端发送数据后输出

RECV: http://www.baidu.com

send: 20

客户端页面也接受到同样数据,接收和返回数据功能正常运行

2.2完善功能

2.2.1接收多个连接请求

将接受连接请求到返回数据加入while循环中

cpp 复制代码
while(1){
        //提示开始接收
        printf("accept\n");
        ...
        //打印发送长度
        printf("send: %d\n", count);
        
    }

客户端发起两个连接
netstat -anop | grep 8080指令查看

tcp 0 0 0.0.0.0:8010 0.0.0.0:* LISTEN 18731/./2.1.1io off (0.00/0/0)

tcp 20 0 192.168.147.130:8080 192.168.147.1:58633 ESTABLISHED 18731/./2.1.1io off (0.00/0/0)

tcp 0 0 192.168.147.130:8080 192.168.147.1:58631 ESTABLISHED 18731/./2.1.1io off (0.00/0/0)

服务器输出结果,开启监听后,两次接收连接请求以及接收和返回数据,并等待下一次连接请求

listen finished

accept

accept finished

RECV: http://www.baidu.com

send: 20

accept

accept finished

RECV: http://www.baidu.com

send: 20

accept

2.2.2数据处理受客户端连接顺序影响

在建立连接后,若客户端没有按照连接顺序发送数据,则第一个客户端在while循环中的流程没有完全结束,导致后续客户端的数据服务器无法合理接收和返回

通过创建线程独立处理每个客户端请求

cpp 复制代码
//存储线程编号的变量
pthread_t thid;
//创建线程,执行对数据的接收和返回操作
pthread_create(&thid, NULL, client_thread, &clientfd);

将对客户端数据的处理封装到函数中

cpp 复制代码
void *client_thread(void *arg){

    //接收客户端套接字
    int clientfd = *(int *)arg;

    //循环接收多次数据
    while(1)
    {
        char buffer[1024] = {0};
        int count = recv(clientfd, buffer, 1024, 0);    
        printf("RECV: %s\n", buffer);

        count = send(clientfd, buffer, count, 0);
        printf("send: %d\n", count);
    }
}

运行程序,输出结果

listen finished

accept

accept finished

accept

accept finished

accept

RECV: http://www.baidu.com

send: 20

RECV: http://www.baidu.com

send: 20

RECV: http://www.baidu.com

send: 20

客户端数据处理互不影响,且可以多次发送

3.总结

3.1概述

使用io进行服务器与客户端的数据交互操作,流程可概括为:创建套接字和服务器,用 bind 绑定,listen 开启监听,循环 accept 接收客户端连接请求,通过 pthread_create 创建线程独立处理多个连接,数据处理 recv 接收数据,send 返回数据。

3.2优化方向

每个连接都独立创建线程对资源消耗大,且线程数量受硬件性能限制。


完整代码

cpp 复制代码
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<pthread.h>
#include<unistd.h>

void *client_thread(void *arg){

    //接收客户端套接字
    int clientfd = *(int *)arg;

    //循环接收多次数据
    while(1)
    {
        //存储接收数据的缓冲区变量
        char buffer[1024] = {0};
        //从客户端套接字中读取数据并存储到缓冲区中
        int count = recv(clientfd, buffer, 1024, 0);    
        //打印数据
        printf("RECV: %s\n", buffer);

        //返回接收数据长度的数据,防止发送错误
        count = send(clientfd, buffer, count, 0);
        //打印发送长度
        printf("send: %d\n", count);
    }
}

int main(){

    //创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    //创建失败的处理
    if(-1 == sockfd){
        printf("sockfd: %s\n", strerror(errno));
        exit(1);
    }

    //初始化服务器信息
    struct sockaddr_in servaddr;
    //使用IPV4协议
    servaddr.sin_family = AF_INET; 
    //允许本机任意IP地址访问
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //设定端口号,0-1023需要root权限
    servaddr.sin_port = htons(8080);

    //通过返回值判断绑定是否成功
    if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
        printf("bind failed: %s\n", strerror(errno));
        exit(1);
    }

    //开启监听
    listen(sockfd, 10);
    //监听成功提示
    printf("listen finished\n");

    //声明客户端地址变量
    struct sockaddr_in clientaddr;
    //计算长度用于接收数据函数的参数
    socklen_t len = sizeof(clientaddr);

    while(1){
        //提示开始接收
        printf("accept\n");
        //accept函数为监听套接字接收一个客户端的连接请求
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        //接收失败的处理
        if(-1 == clientfd){
            printf("accept: %s\n", strerror(errno));
            exit(1);
        }
        //提示接收成功
        printf("accept finished\n");

        //存储线程编号的变量
        pthread_t thid;
        //创建线程,执行对数据的接收和返回操作
        pthread_create(&thid, NULL, client_thread, &clientfd);

    }

    //通过读取字符函数,暂停程序,防止过早结束
    getchar();
    //退出提示
    printf("exit\n");

    return 0;
}
相关推荐
好奇龙猫13 小时前
【AI学习-comfyUI学习-第三十节-第三十一节-FLUX-SD放大工作流+FLUX图生图工作流-各个部分学习】
人工智能·学习
saoys14 小时前
Opencv 学习笔记:图像掩膜操作(精准提取指定区域像素)
笔记·opencv·学习
电子小白12315 小时前
第13期PCB layout工程师初级培训-1-EDA软件的通用设置
笔记·嵌入式硬件·学习·pcb·layout
sunfove15 小时前
光网络的立交桥:光开关 (Optical Switch) 原理与主流技术解析
网络
恋爱绝缘体115 小时前
2020重学C++重构你的C++知识体系
java·开发语言·c++·算法·junit
唯情于酒15 小时前
Docker学习
学习·docker·容器
Z1Jxxx15 小时前
加密算法加密算法
开发语言·c++·算法
乌萨奇也要立志学C++16 小时前
【洛谷】递归初阶 三道经典递归算法题(汉诺塔 / 占卜 DIY/FBI 树)详解
数据结构·c++·算法
️停云️16 小时前
【滑动窗口与双指针】不定长滑动窗口
c++·算法·leetcode·剪枝·哈希
charlie11451419116 小时前
嵌入式现代C++教程: 构造函数优化:初始化列表 vs 成员赋值
开发语言·c++·笔记·学习·嵌入式·现代c++