网络编程(二)用C语言实现TCP多进程服务器与单进程客户端

一,实验内容

编写TCP多进程循环服务器程序与单进程客户端程序,实现以下主体功能:。客户端启动连接服务器之后,进入命令行交互模式。

  • 操作人员在命令行窗口输入一行字符并回车后,客户端进程立刻从命令行(本质即stdin))读取数据,并将该行信息发送给服务器。
  • 服务器收到该行信息后,会将该信息原封不动的返回给客户端,即所谓消息回声(Message Echo)。客户端收到服务器返回的消息回声后,将其打印输出至屏幕(本质即stdout )。
  • 客户端在从命令行收到EXIT指令后退出。
  • 若服务器启动时设定Established Queue的长度,即listen()第二个参数backlog为2,则最多可以有2个客户端同时连上服务器并开展交互,此时,再启动另一个客户端连接服务器,观察体验是什么现象,并尝试分析现象背后的底层逻辑。

二,实验环境

Linux:Ubuntu 22.04

gcc:11.3.0

三,代码实现

服务器(servwe.c)

// 由于实验指导没有给出异常处理信息,本代码没有设计异常处理
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#define BUFFER_SIZE 80

void usage(const char *str) {
    printf("%s  usage:<IP><PORT><veri_code>\n", str);
}

int sigint_flag = 0;
int sig_type = 0;

// 安装sigint信号处理器
void handle_sigint(int sig) {
    sigint_flag = 1;
}

// 安装sig_pipe信号量
void sig_pipe(int signo) {
    sig_type = signo;
    pid_t pid = getpid();
}

// 安装使用SIGCHLD信号处理器
void sig_chld(int signo) {
    pid_t pid_chld;
    int stat;
    while ((pid_chld = waitpid(-1, &stat, WNOHANG)) > 0)
        ;
}

// 主函数
int main(int argc, char *argv[]) {
    char *left = "(";
    char *right = ")";
    char *returns = "[ECH_REP]";
    int cid = 0;

    // 存放转换后的ip地址
    char ip_str[20] = {0};

    // 安装使用sigint信号处理器
    struct sigaction sa;
    sa.sa_flags = 0;
    sa.sa_handler = handle_sigint;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);

    // 使用sigchild
    struct sigaction new, old;
    new.sa_handler = sig_chld;
    sigemptyset(&new.sa_mask);
    new.sa_flags = 0;
    sigaction(SIGCHLD, &new, NULL);

    // 使用sig_pipe信号
    old.sa_handler = sig_pipe;
    sigemptyset(&old.sa_mask);
    old.sa_flags = 0;
    sigaction(SIGPIPE, &old, NULL);

    struct sockaddr_in serv_addr, clie_addr;
    int listenfd, connectfd;
    int ret;
    socklen_t clie_addr_len;

    // 判断输入的参数
    char *veri_code = argv[3];
    if (argc != 4) {
        usage(argv[0]);
        return 1;
    }

    // 建立套接字,返回监听套接字
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;                  // 地址族协议(IPv4orIPv6)
    serv_addr.sin_port = htons(atoi(argv[2]));       // 端口
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);  // IP地址

    // 使用inet_ntop函数转换ip
    inet_ntop(AF_INET, &serv_addr.sin_addr, ip_str, sizeof(ip_str));

    // 获取父进程
    pid_t parrent_pid = getpid();

    // 打印初始化信息
    printf("[srv](%d)[srv_sa](%s:%d)[vcd](%s) Server has initialize!\n", parrent_pid, ip_str, ntohs(serv_addr.sin_port), argv[3]);

    if (listenfd < 0) {
        perror("socket");
        return 1;
    }

    // 给套接字绑定一个端口和IP
    ret = bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    if (ret < 0) {
        perror("bind");
        return 1;
    }

    // 开始监听
    ret = listen(listenfd, 20);
    if (ret < 0) {
        perror("listen");
        return 1;
    }

    // 大循环
    while (!sigint_flag) {
        clie_addr_len = sizeof(clie_addr);

        // 要一个客户端进程进行处理,返回参数主要是有一个客户端通信描述符cfd
        // 程序就是通过这个cfd和客户端进行交互
        connectfd = accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);

        if (connectfd < 0) {
            if (errno == EINTR)
                continue;
            else
                perror("accept");
            return 1;
        } else {
            inet_ntop(AF_INET, &clie_addr.sin_addr, ip_str, sizeof(ip_str));
            printf("[srv](%d) [cli_sa][%s:%d] Client is accepted!\n", parrent_pid, ip_str, (int)ntohs(clie_addr.sin_port));
        }

        // 创建子进程
        if (!fork()) {
            // 获取子进程pid
            pid_t child_pid = getpid();

            // 打印信息
            printf("[chd](%d)[ppid](%d) Child process is created!\n", child_pid, parrent_pid);

            // 关闭监听
            close(listenfd);

            int n = 0;
            char buf[80] = {0};
            char *to_exit = "EXIT";

            while (1) {
                // 第一次读取,接收cid,只读取两个字节
                read(connectfd, buf, 2);
                int cid = ((unsigned char)buf[0] << 8) | (unsigned char)buf[1];

                memset(buf, 0, 80);

                char subStr[BUFFER_SIZE];

                // 第二次读取,读取全部内容
                n = read(connectfd, subStr, BUFFER_SIZE);

                // 判断客户端是否发送完毕
                int if_exit = strncmp(buf, to_exit, 4);
                if (n <= 0 || if_exit == 0) {
                    printf("[chd](%d)[ppid](%d)[cli_sa](%s:%d) Client is closed!\n", child_pid, parrent_pid, ip_str, (int)ntohs(clie_addr.sin_port));
                    break;
                }

                printf("[chd](%d)[cid](%d)[ECH_RQT] %s", getpid(), cid, subStr);

                char character[BUFFER_SIZE];
                memset(character, 0, BUFFER_SIZE);
                int vcd = atoi(veri_code);

                // 将数据拆分成高位和低位
                character[0] = (vcd >> 8) & 0xFF;  // 高位存储在数组的第一个元素中
                character[1] = vcd & 0xFF;         // 低位存储在数组的第二个元素中

                for (int i = 0; i < strlen(subStr); i++) {
                    character[i + 2] = subStr[i];
                }
                character[strlen(subStr) + 2] = '\0';

                // 向客户端发送信息
                write(connectfd, character, BUFFER_SIZE);

                memset(buf, 0, BUFFER_SIZE);
            }
            // 关闭连接
            close(connectfd);
            printf("[chd](%d)[ppid](%d) connfd is closed!\n", child_pid, parrent_pid);
            printf("[chd](%d)[ppid](%d) Child process is to return!\n", child_pid, parrent_pid);
            return getpid();
        }

        // 关闭连接
        close(connectfd);
    }
    close(listenfd);

    return 0;
}

客户端(client.c)

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <unistd.h>

#define MAXSIZE 80

void usage(const char *str) {
    printf("%s  usage:<IP><PORT><veri_code>\n", str);
}

int main(int argc, char *argv[]) {
    // 存放ip_str
    char ip_str[20] = {0};

    if (argc != 4) {
        usage(argv[0]);
        return 1;
    }
    // 客户端标识符
    int cid = atoi(argv[3]);

    // 进程号
    pid_t pid = getpid();

    struct sockaddr_in serv_addr;
    int sockfd;

    // 返回客户端的进程描述符
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[2]));
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);

    // connect表示客户端与服务端尝试建立连接
    // connect里面的输入参数有要连接服务端的地址
    // connect执行的时候就会被服务端listen检测到
    // 如果accept没有阻塞的话,这时候就会进行三次握手
    // 三次握手完成以后,服务端执行完accept,客户端执行完connect

    // 发起客户端请求连接
    int ret = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    if (ret < 0) {
        perror("connect");
        return 1;
    } else {
        // 转换IP地址
        inet_ntop(AF_INET, &serv_addr.sin_addr, ip_str, sizeof(ip_str));
        printf("[cli](%d)[srv_sa](%s:%d)Server is connected!\n", pid, ip_str, ntohs(serv_addr.sin_port));
    }

    char buf[MAXSIZE] = {0};
    char *to_exit = "EXIT";  // 退出字串

    while (1) {
        memset(buf, 0, 80);
        fgets(buf, 80, stdin);
        printf("[cli](%d)[cid](%d)[ECH_RQT] %s", pid, cid, buf);

        // 判断是否退出
        int if_exit = strncmp(buf, to_exit, 4);
        if (if_exit == 0)
            break;

        char result[MAXSIZE + 2];  // 总长度

        // 将数据拆分成高位和低位
        result[0] = (cid >> 8) & 0xFF;  // 高位存储在数组的第一个元素中
        result[1] = cid & 0xFF;         // 低位存储在数组的第二个元素中
        int j = 0;
        for (j = 0; j < 80; j++) {
            result[2 + j] = buf[j];
        }

        result[2 + j] = '\0';  // 添加字符串结尾的空字符

        // 向服务器发送信息
        write(sockfd, result, MAXSIZE + 2);
        memset(buf, 0, 80);

        // 读取服务器回显的信息,先读取两个字节获取vcd
        read(sockfd, &buf, 2);
        int vcd = ((unsigned char)buf[0] << 8) | (unsigned char)buf[1];
        char subStr[80];

        // 读取服务器回显的信息
        read(sockfd, subStr, 78);
        printf("[cli](%d)[vcd](%d)[ECH_REP] %s", getpid(), vcd, subStr);
    }

    // 关闭连接
    close(sockfd);
    printf("[cli](%d) connf is closed!\n", pid);
    printf("[cli](%d) Client is to return! \n", pid);
    return 0;
}

四,编译执行

gcc -o server.o server.c


编译客户端(client.c)

gcc -o client.o client.c


初始化服务器

 ./server.o 127.0.0.1 12345 8888 //127.0.0.1为ip地址,12345为端口号,8888为验证码


客户端连接至服务器

 ./client.o 127.0.0.1 12345


 

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值