详细介绍:基于网络io的多线程TCP服务器

1.基础介绍

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

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

2.实现流程

2.1搭建基础服务器

        创建套接字

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

        程序结束

//通过读取字符函数,暂停程序,防止过早结束
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实现接收和返回数据功能

        声明客户端地址变量

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

        等待接收客户端连接请求

//提示开始接收
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");

        接收并打印数据

//存储接收数据的缓冲区变量
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

        客户端发送数据后输出

RECV: http://www.baidu.com

send: 20

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

2.2完善功能

2.2.1接收多个连接请求

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

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循环中的流程没有完全结束,导致后续客户端的数据服务器无法合理接收和返回

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

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

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

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优化方向

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


完整代码

#include
#include
#include
#include
#include
#include
#include
#include
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;
}

posted on 2025-10-29 08:20  blfbuaa  阅读(6)  评论(0)    收藏  举报