网络编程基础之TCP循环服务器

循环服务器的概念

在网络编程中,循环服务器(Iterative Server) ,有时也被称为串行服务器 ,是一种最基础、最简单的服务器模型。它的核心思想非常直接:服务器在同一时间只处理一个客户端连接,必须等这个客户端的所有请求都处理完毕并断开连接后,才会去接受并处理下一个客户端的连接。

这种服务器结构简单,资源消耗低,但是该模型无法实现并发,所以不会被应用于工业生产中

循环服务器代码实现

cpp 复制代码
/*************************************************************************
 * 程序名称:TCP循环服务器(串行处理)
 * 功能描述:
 *  1. 服务器监听端口,阻塞等待客户端连接;
 *  2. 仅能串行处理单个客户端的TCP数据收发,处理完一个再接收下一个;
 *  3. 无并发能力,同一时间仅支持一个客户端通信;
 *  4. 逻辑简单,无需创建子进程、无需回收僵尸进程。
 * 编译命令:g++ TCPLoopServer.cpp -o TCPLoopServer
 * 运行命令:./TCPLoopServer
 *************************************************************************/
#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地址

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

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){
        //阻塞等待客户端连接
        //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地址 客户端端口号


        //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 -1;
            }
            printf("发送成功!\n");
        }


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

    //关闭监听套接字
    close(sfd);

    return 0;
}
相关推荐
想成为优秀工程师的爸爸几秒前
车载以太网之要火系列 - 第33篇:郭大侠学UDS(10服务)- 桃花岛内规矩多,模式切换要会说
网络·笔记·网络协议·信息与通信·车载以太网
时光之源7 分钟前
安装WSL2后在其中安装Ubuntu24.04.4再安装OpenClaw全流程傻瓜式教学:WSL2 + Ubuntu 24.04 + OpenClaw
linux·运维·ubuntu·openclaw·龙虾
计算机安禾7 分钟前
【计算机网络】第7篇:IP寻址体系的演进——从分类编址到CIDR的无类域间路由
tcp/ip·计算机网络·智能路由器
eastyuxiao11 分钟前
流程图 + 配置清单 在团队 / 公司项目管理场景的落地应用
大数据·运维·人工智能·流程图
杨云龙UP15 分钟前
Windows Server 2012 环境下 Oracle 11.2 使用 expdp 实现自动备份、异地复制与定期清理_20260504
服务器·数据库·windows·mysql·docker·oracle·容器
ACP广源盛1392462567316 分钟前
一芯搞定多屏高清@ACP#GSV1221 DP 1.4 MST 转 HDMI 1.4 高集成转换芯片
网络·人工智能·嵌入式硬件·计算机外设·电脑
小风吹啊吹~27 分钟前
vscode的tunnel链接(Linux 服务器 + Windows 本地电脑版本)
服务器·vscode·microsoft·远程工作
Empty-Filled28 分钟前
AI Agent 测试入门:从回答问题到执行任务
网络·人工智能
Jinkxs35 分钟前
LoadBalancer- 常见负载均衡算法:轮询 / 加权轮询 / 最少连接等基础实现
运维·算法·负载均衡
eastyuxiao39 分钟前
流程图 + 配置清单 在团队 / 公司运维场景的落地应用方法
运维·人工智能·流程图