网络编程基础之TCP多线程并发服务器

多线程的概念

多线程(Multithreading) :是一种程序设计模型,它允许一个进程(应用程序)内部同时拥有多个独立执行的控制流,这些控制流被称为线程。每个线程在共享同一个进程资源(如内存、文件描述符)的同时,可以并发或并行地执行不同的任务

**多线程并发服务器:**服务器在接收客户端连接时,为每个客户端连接创建一个单独的处理线程,让所有客户端都能被同时(并发)服务,而主线程则立即返回继续监听新的连接

多线程并发服务器代码实现

cpp 复制代码
/*************************************************************************
 * 程序名称:TCP多线程并发服务器
 * 功能描述:
 *  1. 服务器监听端口,阻塞等待客户端连接;
 *  2. 每接一个客户端连接,创建独立分支线程处理通信,主线程继续等待新连接;
 *  3. 支持多客户端并发通信,解决循环服务器串行处理的问题;
 *  4. 线程设置为分离态,退出后系统自动回收资源,无需手动join。
 * 编译命令:g++ TCPSerThread.cpp -o TCPSerThread -lpthread    //编译命令要加-lpthread,告知线程相关的函数库
 * 运行命令:./TCPSerThread
 *************************************************************************/
#include <stdio.h>    // printf
#include <iostream>    // std::cout
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>    //定义socket相关的函数和数据结构
#include <sys/types.h>    //系统基础数据类型定义头文件(定义Unix/Linux 系统中用于描述「对象类型」的基本数据类型)
#include <string.h>    //调strcat bzero strlen函数
#include <unistd.h>    //调close函数

//服务器常量
#define SERVER_PORT 8888    //服务器端口号
#define SERVER_IP "192.168.23.128"    //服务器IP地址


//定义用于传送数据的结构体类型
struct Info{
    int newfd;    //套接字文件描述符
    struct sockaddr_in cin;    //客户端套接字地址信息结构体
};

//分支线程:用于跟客户端进行通信
void *deal_cil_msg(void *arg){
    //解析传过来的数据
    int newfd = ((struct Info *)arg)->newfd;    //解析套接字文件描述符
    struct sockaddr_in cin = ((struct Info*)arg)->cin;    //ip地址和端口号结构体

    //5.数据收发
    char rbuf[128] = "";    //数据容器
    while(1){
        //清空容器中的内容
        bzero(rbuf,sizeof(rbuf));

        //从套接字中读取消息
        //recv函数
        //参数1:通信的套接字文件描述符
        //参数2:要存放数据的起始地址
        //参数3:读取的数据的大小
        //参数4:读取标识位,是否阻塞读取(0表示阻塞等待,MSG_DONTWAIT表示非阻塞)
        //返回值:大于0表示成功读取的字节个数,等于0表示对端已下线,等于-1表示失败,并置位错误码
        int res = recv(newfd, rbuf, sizeof(rbuf), 0);
        if(res == 0){
            printf("对端已下线\n");
            break;
        }
        printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf);    //客户端IP地址 客户端端口号

        //处理收到的数据并送回给客户端
        strcat(rbuf, "*_*");

        //将消息发送给客户端
        //send函数
        //参数1:通信的套接字文件描述符
        //参数2:要发送的数据的起始地址
        //参数3:发送的数据的大小
        //参数4:读取标识位,是否阻塞读取(0表示阻塞等待,MSG_DONTWAIT表示非阻塞)
        //返回值:大于0表示成功发送的字节个数,等于0表示对端已下线,等于-1表示失败,并置位错误码
        if(send(newfd, rbuf, strlen(rbuf), 0) == -1){
            perror("send error");
            return NULL;
        }
        printf("发送成功!\n");
    }


    //6.关闭当前客户端的通信套接字
    //close函数
    //参数:要关闭的套接字文件描述符,类型为int
    //返回值:成功返回0,失败返回-1并置位错误码
    close(newfd);

    //退出分支线程
    pthread_exit(NULL);
}

/**********************************主程序***********************************/

int main(){
    //1.创建TCP监听套接字--socket
    //socket函数
    //参数1:AF_INET表示使用IPv4协议族
    //参数2:SOCK_STREAM表示使用面向连接的TCP协议
    //参数3:协议类型,通常设置为0,表示使用默认协议(参数2指定TCP时参数3填0即可)
    //返回值:成功返回sfd(套接字文件描述符),失败返回-1
    int sfd = socket(AF_INET,SOCK_STREAM,0);    //sfd全称socket file descriptor(套接字文件描述符)
    if(sfd == -1){
        perror("socket error");
        return -1;
    }
    printf("socket success sfd = %d\n",sfd);


    //2.绑定ip地址和端口号--bind
    //2.1填充要绑定的ip地址和端口号结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;    //通信域
    sin.sin_port = htons(SERVER_PORT);    //端口号(使用htons将2字节整数转换为网络字节序)
    sin.sin_addr.s_addr = inet_addr(SERVER_IP);    //IP地址(使用inet_addr将点分十进制字符串转换为网络字节序)
    
    //2.2绑定工作
    //bind函数
    //参数1:要被绑定的套接字文件描述符
    //参数2:要绑定的地址信息结构体,需强制类型转换为sockaddr*避免警告
    //参数3:参数2的结构体大小
    //返回值:成功返回0,失败返回-1并置位错误码
    if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))==-1){
        perror("bind error");
        return -1;
    }
    printf("bind success\n");


    //3.启动监听
    //listen函数
    //参数1:要启动监听的套接字文件描述符
    //参数2:半连接队列的最大长度(Linux内核默认最大值为128)
    //返回值:成功返回0,失败返回-1并置位错误码
    if(listen(sfd, 128) == -1){
        perror("listen error");
        return -1;
    }
    printf("listen success\n");


    //4.阻塞等待客户端的连接请求
    //定义变量用于接收客户端地址信息结构体
    struct sockaddr_in cin;    //用于接收地址信息结构体
    socklen_t socklen = sizeof(cin);    //用于接收地址信息的长度

    while(1){
        //4.1阻塞等待客户端连接
        //accept函数(当程序执行到该函数时,会给当前客户端预分配一个最小未使用的文件描述符)
        //参数1:服务器监听套接字文件描述符
        //参数2:接收客户端地址信息的结构体容器,不接收可填NULL
        //参数3:接收参数2的结构体大小,参数2为NULL时填NULL
        //返回值:成功返回newfd(与该客户端通信的专属套接字),失败返回-1并置位错误码
        int newfd = accept(sfd, (struct sockaddr*)&cin, &socklen);    //新的用于通信的套接字文件描述符
        if(newfd == -1){
            perror("accept error");
            return -1;
        }
        printf("[%s:%d]已连接成功,newfd = %d!\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd);    //客户端IP地址 客户端端口号

        //4.2填充要传输给分支线程的数据结构体变量
        struct Info buf = {newfd, cin};

        //4.3创建分支线程,用于处理客户端的操作
        //pthread_create函数
        //参数1:指向线程ID变量的指针,用于存储新创建线程的ID
        //参数2:线程属性,NULL表示使用默认属性
        //参数3:分支线程要执行的函数指针(deal_cil_msg)
        //参数4:传递给分支线程函数的参数(struct Info*)
        //返回值:成功返回0,失败返回非0错误码
        pthread_t tid = -1;
        if(pthread_create(&tid, NULL, deal_cil_msg, &buf) != 0){
            printf("pthread_create error\n");
            return -1;
        }

        //4.4完成对分支线程资源的处理
        //pthread_join(tid, NULL);    //不能使用该函数回收,因为该函数是阻塞函数,会卡住主线程接收新连接
        //pthread_detach函数:将线程设置成分离态,执行结束后,系统自动回收资源
        //参数:要设置成分离态的线程ID
        //返回值:成功返回0,失败返回非0错误码
        pthread_detach(tid);    
    }
    
    //关闭监听套接字
    close(sfd);

    return 0;
}
相关推荐
德迅云安全-小潘1 小时前
如何保护服务器的安全
运维·服务器·安全
李可以量化1 小时前
QMT 量化交易:北交所数据获取与实时涨跌统计完整教程
linux·服务器·windows
凯勒姆1 小时前
华为设备软考网工模板
服务器·网络·华为
Cat_Rocky1 小时前
Ingress-Nginx 全局超时配置及生效方式
java·服务器·nginx
计算机安禾2 小时前
【计算机网络】第24篇:TCP性能瓶颈的定量诊断——重传类型、RTT波动与带宽时延积
网络协议·tcp/ip·计算机网络
lolo大魔王3 小时前
Linux列出文件和目录
linux·运维·服务器
H Journey3 小时前
网络编程:Boost.Asio实现跨平台的TCP服务器
服务器·网络·tcp/ip·boost.asio
j_xxx404_3 小时前
Linux命名管道:跨进程通信实战指南|附源码
linux·运维·服务器·人工智能·ai
WiChP3 小时前
【V0.1B8】从零开始的2D游戏引擎开发之路
服务器·数据库·mysql