• 第13章 TCP编程(3)_基于自定义协议的多进程模型


    5. 自定义协议编程

    (1)自定义协议:MSG

    //自定义的协议(TLV:Type length Value)
    typedef struct{
        //协议头部
        char head[10];//TLV中的T
        unsigned int checkNum; //校验码
        unsigned int cbSizeContent; //协议体的长度
        //协议体部
        char buff[512]; //数据
    }MSG;

    (2)自定义读写函数

      ①extern int write_msg(int sockfd, char* buff, size_t len); //发送一个基于自定义协议的message,发送的数据存放在buff中

      ②extern int read_msg(int sockfd, char* buff, size_t len); //读取一个基于自定义协议的message,读取的数据存放在buff中

      ③static int recvData(int sockfd, char* buff, int nBytes);//接收任意长度的数据

      ④static int sendData(int sockfd, char* buff, int nBytes);//发送任意长度的数据

      ⑤static unsigned int msg_check(MSG* msg); //生成校验码

    6. 基于自定义协议的多进程模型

    (1)服务端编程

      ①主进程负责监听客户端连接

      ②当接受客户端连接后,创建子进程来服务客户端,以处理多客户端的并发访问。

      ③服务端接到的客户端信息后,回显给客户端

    (2)客户端编程

      ①从键盘输入信息,并发送给服务端

      ②接收来自服务端的信息

    //msg.h自定义协议

    #ifndef __MSG_H__
    #define __MSG_H__
    #include <sys/types.h>
    
    //求结构体中成员变量的偏移地址
    #define   OFFSET(TYPE, MEMB)   ((size_t) &((TYPE *)0)->MEMB)
    
    //自定义的协议(TLV:Type length Value)
    typedef struct{
        //协议头部
        char head[10];//TLV中的T
        unsigned int checkNum; //校验码
        unsigned int cbSizeContent; //协议体的长度
        //协议体部
        char buff[512]; //数据
    }MSG;
    
    //发送一个基于自定义协议的message,发送的数据存放在buff中
    extern int write_msg(int sockfd, char* buff, size_t len);
    
    //读取一个基于自定义协议的message,读取的数据存放在buff中
    extern int read_msg(int sockfd, char* buff, size_t len);
    
    #endif

    //msg.c

    #include "msg.h"
    #include <unistd.h>
    #include <string.h>
    #include <memory.h>
    #include <sys/types.h>
    
    //计算校验码
    static unsigned int msg_check(MSG* msg)
    {
        unsigned int s = 0;
        int i = 0;
        for(; i<sizeof(msg->head); i++){
            s += msg->head[i];
        }
    
        for(i=0; i<msg->cbSizeContent; i++){
            s += msg->buff[i];
        }
    
        return s;
    }
    
    //接收规定字节的数据,如果缓冲区的数据不够则等待
    //返回:0,连接中断或发生错误
    //      非0:实际接收到的字节数
    static int recvData(int sockfd, char* buff, int nBytes)
    {
        size_t nRecv = 0;
    
        do
        {
            size_t nLen = 0;
            //接收数据,直到收到指定的字节数
            nLen =  read(sockfd, buff + nRecv, nBytes - nRecv);
            nRecv += nLen;
    
            if(nLen == 0){ //读完或对方的写端己关闭
                nRecv = -1;
                break;
            }
    
        }while(nRecv < nBytes);
    
        return nRecv;
    }
    
    //发送指定字节数(数据量较大)的数据(采取分批发送的方法)
    //当要发送的字节数超过send缓冲区大小时,要分批发送
    static int sendData(int sockfd, char* buff, int nBytes)
    {
        size_t nSend = 0;
    
        do
        {
            size_t nLen = 0;
            //接收数据,直到收到指定的字节数
            nLen = write(sockfd, buff + nSend, nBytes - nSend);
            nSend += nLen;
            if(nLen < 0 || nLen == 0){ //当写完或对方读端己关闭
                if(nLen == 0) 
                    nSend = -1;
                break;
            }
    
        }while(nSend < nBytes);
        
        return nSend;
    }
    
    //发送一个基于自定义协议的message,发送的数据存放在buff中
    int write_msg(int sockfd, char* buff, size_t len)
    {
        /*封装数据成自定义的格式*/
        MSG msg;
        memset(&msg, 0, sizeof(msg));
        strcpy(msg.head, "msghead");
        memcpy(msg.buff, buff, len);
        msg.cbSizeContent = len;
        msg.checkNum = msg_check(&msg);
    
        /*发送自定义消息*/
        int nRet = sendData(sockfd, (char*)&msg, sizeof(msg));
    
        return nRet;
    }
    
    //读取一个基于自定义协议的message,读取的数据存放在buff中
    int read_msg(int sockfd, char* buff, size_t len)
    {
        MSG  msg;
        memset(&msg, 0, sizeof(msg));
        size_t size = 0;
        
        //读取结构体
        size = recvData(sockfd, (char*)&msg, sizeof(msg));
        if(size == -1){ //另一方socket被关闭
            return 0;   
        }else if(size != sizeof(msg)){
            return -1;
        }
    
        //进行校验码验证
        unsigned int s = msg_check(&msg);
        if((s == (unsigned int)msg.checkNum) 
                && (!strcmp("msghead", msg.head))){
            memcpy(buff, msg.buff, len);
            return size;
        }
    
        return -1;
    }

    //echo_tcp_server.c ——服务端程序

    #include <netdb.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <time.h>
    #include <signal.h>
    #include <errno.h>
    #include "msg.h"
    
    /*基于自定义协议的多进程服务端和客户端通信*
    测试:telnet 127.0.0.1 xxxx 
          http://xxx.xxx.xxx.xxx:端口号
    注意:演示时可关闭服务器的防火墙,防火墙口被过滤
          #service iptables status     查看防火墙
          #service iptables stop       关闭防火墙
    */
    
    int sockfd;
    int bStop = 0;
    
    void sig_handler(int signo)
    {
        if(signo == SIGINT){
            bStop = 1;
            printf("server close
    ");
    
            exit(1);
        }
    
        if(signo == SIGCHLD){
            printf("child process deaded...
    ");
            wait(0);
        }
    }
    
    //显示客户端信息
    void out_addr(struct sockaddr_in* addr)
    {
        //将端口从网络字节序转换成主机字节序
        int port = ntohs(addr->sin_port);
        //获得IP地址
        char ip[16] ={0};
        //将ip地址从网络字节序转换成点分十分制
        inet_ntop(AF_INET, &addr->sin_addr.s_addr, ip, sizeof(ip));
    
        printf("Client: %s(%d) connected
    ", ip, port);
    }
    
    //服务程序
    void do_service(int fd)
    {
        /*服务端和客户端进行读写操作(双向通信)*/
        char buff[512];
        while(1){
           memset(buff, 0, sizeof(buff));
           printf("start read and write...
    ");
           size_t size;
           
           //读取客户端发送过来的消息
           if((size = read_msg(fd, buff, sizeof(buff))) < 0){
               printf("%s
    ", buff); //测试
               perror("protocol error");
               break;
           }else if(size == 0){
               //当客户端断开连接时而服务器试图去读取其
               //数据,socket就类似管道,相当于客户端写端被关闭,这里read_msg
               //会返回0
               printf("%s
    ", buff); //测试
               break;
           }else{
               printf("%s
    ", buff);//显示客户端发送的消息
               //写回客户端(回显功能)
               if(write_msg(fd, buff, sizeof(buff)) < 0){
                   perror("protocol error");
                   if(errno == EPIPE){
                      //如果客户端己被关闭(相当于管道的读端关闭),会产生SIGPIPE信号
                      //将并errno设置为EPIPE
                       break;
                   }
               }
           }
        }
    }
    
    int main(int argc, char* argv[])
    {
        if(argc < 2){
            printf("usage: %s port
    ", argv[0]);
        }
    
        //按ctrl-c时中止服务端程序
        if(signal(SIGINT, sig_handler) == SIG_ERR){
            perror("signal sigint error");
            exit(1);
        }
        //回收子进程
        if(signal(SIGCHLD, sig_handler) == SIG_ERR){
            perror("signal sigchld error");
            exit(1);
        }
    
    
        /*步骤1:创建socket(套接字)
         *注:socket创建在内核中,是一个结构体
         *AF_INET:IPv4
         *SOCK_STREAM:tcp协议
         */
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
        /*步骤2:将sock和地址(包括ip、port)进行绑定*/
        struct sockaddr_in servAddr; //使用专用地址结构体
        memset(&servAddr, 0, sizeof(servAddr));
        //往地址中填入ip、port和Internet地址族类型
        servAddr.sin_family = AF_INET;//IPv4
        servAddr.sin_port = htons(atoi(argv[1])); //port
        servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP
    
        if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) <0 ){
            perror("bind error");
            exit(1);
        }
    
        /*步骤3:调用listen函数启动监听
         *       通知系统去接受来自客户端的连接请求
         */
        if(listen(sockfd, 10) < 0){  //队列中最多允许10个连接请求
            perror("listen error");
            exit(1);
        }
    
        /*步骤4:调用accept函数,从请求队列中获取一个连接
         *       并返回新的socket描述符
         * */
        struct sockaddr_in clientAddr;
        socklen_t clientAddr_len = sizeof(clientAddr);
        while(!bStop){
            //如果没有客户端连接,调用此函数后会阻塞,直至获得一个客户端连接
            int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &clientAddr_len);
           
            if(fd < 0){
                perror("accept error");
                continue;
            }
            
            /*步骤5:启动子进程去和客户端进行双向通信*/
            pid_t pid = fork();
            if(pid < 0){
                continue;
            }else if(pid == 0){//child process
                //输出客户端信息
                out_addr(&clientAddr);
                //处理客户端请求
                do_service(fd);
                close(fd);
                break;
            }else{  //parent process
                /*步骤6: 关闭fd套接字*/
                close(fd);
            }
        }
    
        close(sockfd);
    
        return 0;
    }
    /*输出结果
     * [root@localhost 13.TCP]# bin/echo_tcp_server 8888 
     * Client: 127.0.0.1(40664) connected
     * start read and write...
     * abcdefg
     * start read and write...
     * 1234567890
     * start read and write...
     * ^Cserver close
     * child process deaded...
     * server close
     */

    //echo_tcp_client.c ——客户端程序

    #include <netdb.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include "msg.h"
    
    int main(int argc, char* argv[])
    {
        if(argc < 3){
            printf("usage: %s ip port
    ", argv[0]);
            exit(1);
        }
    
        /*步骤1: 创建socket(套接字)*/
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0){
            perror("socket error");
        }
    
        //往servAddr中填入ip、port和地址族类型
        struct sockaddr_in servAddr;
        memset(&servAddr, 0, sizeof(servAddr));
        servAddr.sin_family = AF_INET;
        servAddr.sin_port = htons(atoi(argv[2]));
        //将ip地址转换成网络字节序后填入servAdd中
        inet_pton(AF_INET, argv[1], &servAddr.sin_addr.s_addr);
    
        /*步骤2: 客户端调用connect函数连接到服务器端*/
        if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){
            perror("connect error");
            exit(1);
        }
    
        /*步骤3: 调用自定义的协议处理函数和服务端进行双向通信*/
        char buff[512];
        size_t size;
        char* prompt = ">";
    
        while(1){
            memset(buff, 0, sizeof(buff));
            write(STDOUT_FILENO, prompt, 1);
            size = read(STDIN_FILENO, buff, sizeof(buff));
            if(size < 0) continue;
    
            buff[size-1] = '';
            //将键盘输入的内容发送到服务端
            if(write_msg(sockfd, buff, sizeof(buff)) < 0){
                perror("write msg error");
                continue;
            }else{
                memset(buff, 0, sizeof(buff));
                //读取来自服务端的消息
                if(read_msg(sockfd, buff, sizeof(buff)) < 0){
                    perror("read msg error");
                    continue;
                }else{
                    printf("%s
    ", buff);
                }
            }
        }
    
        /*关闭套接字*/
        close(sockfd);
    }
    /*输出结果
     * [root@localhost 13.TCP]# bin/echo_tcp_client 127.0.0.1 8888
     * >abcdefg
     * abcdefg
     * >1234567890
     * 1234567890
     * >^C
     */
  • 相关阅读:
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    微信小程序TodoList
    C语言88案例-找出数列中的最大值和最小值
    C语言88案例-使用指针的指针输出字符串
  • 原文地址:https://www.cnblogs.com/5iedu/p/6671046.html
Copyright © 2020-2023  润新知