基于网络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;
}
相关推荐
丰锋ff2 小时前
2014 年真题配套词汇单词笔记(考研真相)
学习·考研
RaLi和夕2 小时前
嵌入式学习笔记4.STM32中断系统及外部中断EXTI
笔记·stm32·单片机·学习
yenggd2 小时前
QoS之流量整形配置方法
网络·数据库·华为
key062 小时前
《数据出境安全评估办法》企业应对策略
网络·人工智能·安全
jc06203 小时前
项目实战5:聊天室
c++
草莓熊Lotso3 小时前
《回溯 C++98:string 核心机制拆解 —— 从拷贝策略到高效 swap》
开发语言·c++
AORO20253 小时前
遨游科普:什么是对讲机?没有网络的山区对讲机可以用吗?
网络·5g·安全·信息与通信
2401_831501733 小时前
Python学习之day01学习(变量定义和数据类型使用)
开发语言·python·学习
fei_sun3 小时前
【复习】计网强化第一章
运维·服务器·网络