循环服务器TCP

服务器代码

cpp 复制代码
/*
 * ============================================================================================
 * 文件名称:server.c
 * 创建者: mf
 * 创建日期:2024年05月20日
 * 描述:此程序实现了一个简易的TCP服务器,监听8888端口,接受客户端连接后,每隔一秒向客户端发送当前时间,
 *       直到写入操作失败或客户端断开连接。服务器通过忽略SIGPIPE信号来处理潜在的管道错误。
 * ============================================================================================
 */
#include <stdio.h>          // 标准输入输出定义
#include <sys/types.h>      // 基本系统数据类型定义
#include <sys/socket.h>     // 套接字编程相关函数声明
#include <netinet/in.h>     // 网络地址结构体定义
#include <netinet/ip.h>     // IP协议头文件
#include <arpa/inet.h>      // 地址转换函数声明
#include <unistd.h>         // 通用Unix函数声明,如close()
#include <signal.h>         // 信号处理函数声明
#include <time.h>           // 时间函数声明

#define PORT 8888           // 监听端口号

int main(int argc, char *argv[]) 
{
    int ret = -1;           // 返回值变量
    int sfd = -1;           // 服务器套接字描述符
    int cfd = -1;           // 客户端连接套接字描述符
    int opt = 1;            // 选项值,用于设置套接字选项

    // 创建TCP套接字
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd == -1) 
    {
        perror("socket");   // 错误处理
        return -1;
    }

    // 设置套接字选项,允许端口重用
    ret = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if (ret == -1) 
    {
        perror("setsockopt");
        return -1;
    }

    // 服务器地址结构体初始化
    struct sockaddr_in adds, addc;
    memset(&adds, 0, sizeof(adds));  // 清零
    adds.sin_family = AF_INET;       // 协议族为IPv4
    adds.sin_port = htons(PORT);     // 设置监听端口
    adds.sin_addr.s_addr = INADDR_ANY; // 监听所有IP地址

    // 绑定套接字到指定地址和端口
    ret = bind(sfd, (struct sockaddr*)&adds, sizeof(adds));
    if (ret == -1)
    {
        perror("bind");
        return -1;
    }

    // 开始监听,最多挂起10个连接请求
    ret = listen(sfd, 10);
    if (ret == -1) 
    {
        perror("listen");
        return -1;
    }

    // 忽略SIGPIPE信号,避免因写入已关闭的连接而退出
    signal(SIGPIPE, SIG_IGN);

    printf("等待客户端在端口 %d 上的连接...\n", PORT);

    // 循环等待并接受客户端连接
    while (1) 
    {
        socklen_t addrlen = sizeof(addc);
        cfd = accept(sfd, (struct sockaddr*)&addc, &addrlen);
        if (cfd == -1) 
        {
            perror("accept");
            continue; // 出错则继续等待下一个连接
        }

        // 打印客户端连接信息
        printf("连接来自 IP: %s 端口: %d\n", inet_ntoa(addc.sin_addr), ntohs(addc.sin_port));

        // 向客户端发送当前时间,每秒一次
        while (1) 
        {
            time_t tm = time(NULL); // 获取当前时间
            sprintf(buf, "%s", ctime(&tm)); // 将时间转换为字符串
            ret = write(cfd, buf, strlen(buf)); // 发送给客户端
            if (ret == -1) 
            {
                perror("write"); // 写入错误处理
                break; // 发生错误则跳出循环
            }
            sleep(1); // 暂停一秒
        }

        // 客户端连接结束,关闭连接套接字
        close(cfd);
        printf("客户端已断开连接。等待新连接...\n");
    }

    // 关闭服务器套接字,尽管这里可能永远不会执行到
    close(sfd);
    return 0;
}

客户端代码

cpp 复制代码
/*
 * ============================================================================================
 * 文件名称:client.c
 * 创建者: mf
 * 创建日期:2024年05月20日
 * 描述:此程序实现了一个简易的TCP客户端,用于连接指定IP地址和端口的服务器(此处为192.168.110.6:8888),
 *       并不断从服务器接收数据,打印至标准输出。若连接失败或读取数据时发生错误,则程序将终止。
 * ============================================================================================
 */
#include <stdio.h>          // 标准输入输出函数库
#include <sys/types.h>      // 基本系统数据类型定义
#include <sys/socket.h>     // 套接字编程所需函数声明
#include <netinet/in.h>     // 网络地址结构体定义
#include <arpa/inet.h>      // 地址转换函数声明
#include <unistd.h>         // 提供了POSIX操作系统API中的函数声明,如close()

#define IP   "192.168.110.6" // 服务器IP地址
#define PORT 8888           // 服务器端口号

int main(int argc, char *argv[]) 
{
    int fd = -1;           // 客户端套接字描述符
    int ret = -1;          // 函数返回值
    char buf[32] = {0};     // 用于接收服务器数据的缓冲区
    int opt = 1;           // 设置套接字选项的参数

    // 创建TCP套接字
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror("socket");   // 错误处理
        return -1;
    }

    // 设置套接字选项,允许端口重用(注意:此选项在客户端通常不是必须的)
    ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if (ret == -1) 
    {
        perror("setsockopt");
        return -1;
    }

    // 服务器地址结构体初始化
    struct sockaddr_in adds;
    memset(&adds, 0, sizeof(adds));  // 清零
    adds.sin_family = AF_INET;       // 协议族为IPv4
    adds.sin_port = htons(PORT);     // 设置服务器端口
    adds.sin_addr.s_addr = inet_addr(IP); // 设置服务器IP地址

    // 尝试连接服务器
    ret = connect(fd, (struct sockaddr*)&adds, sizeof(adds));
    if (ret == -1) 
    {
        perror("connect");
        return -1;
    }

    // 连接成功,开始循环读取服务器发送的数据
    printf("已连接到服务器,开始接收数据...\n");
    while(1) 
    {
        // 读取服务器发送的数据
        ret = read(fd, buf, sizeof(buf));
        if (ret == -1)
        {
            perror("read");  // 读取错误处理
            return -1;
        } else if (ret == 0) 
        {
            // 对端关闭连接,read返回0
            printf("服务器已关闭连接。\n");
            return -1;
        } else 
        {
            // 打印接收到的数据
            printf("%s", buf);
        }
    }

    // 正常情况下不会执行到此处,但为了规范依然关闭套接字
    close(fd);
    return 0;
}
相关推荐
泽虞6 分钟前
《LINUX系统编程》笔记p3
linux·运维·服务器·c语言·笔记·面试
苹果醋32 小时前
Java并发编程-Java内存模型(JMM)
java·运维·spring boot·mysql·nginx
dingcb1682 小时前
4090服务器无法sudo apt update 问题解决
运维·服务器
头发那是一根不剩了2 小时前
服务器硬盘进行分区和挂载
linux·运维·服务器
心一信息3 小时前
如何通过华为无线控制器添加一个名为yunwei的无线网络
运维·网络·华为
stark张宇3 小时前
Linux 零基础入门,看这一篇就够了
linux·服务器
TDengine (老段)3 小时前
TDengine IDMP 运维指南(4. 使用 Docker 部署)
运维·数据库·物联网·docker·时序数据库·tdengine·涛思数据
三婶儿3 小时前
在没有客户端的客户环境下,如何用 Python 一键执行 MySQL 与达梦数据库 SQL
运维·后端·python
xcs194053 小时前
AI 自动化编程 trae 体验2 帮我分析一个项目
运维·自动化
彬彬醤4 小时前
Mac怎么连接VPS?可以参考这几种方法
大数据·运维·服务器·数据库·线性代数·macos·矩阵