zoukankan      html  css  js  c++  java
  • 2.TCP通信

    1.TCP的特点:

      需要连接,使用可靠的传输协议,用于对数据安全要求较高,传输大型数据,实时性差。

    2.套接字 socket

      socket--应用程序与TCP/UDP通信协议的中间层。

    3.TCP通信流程

     4.通信的实现,先是服务端

      (1)创建套接字函数  socket

       (2)绑定端口IP地址  bind

       这一步所要用到的结构体

       (3)监听  listen

       (4)如果有客户端连接就接受连接  accept

       (5)之后就是接受和发送数据了

      接收/读取  recv,read

      写入/发送  send/write

    然后是客户端

      (1)连接服务器  connect

       (2)之后也是通过recv,read,write,send四个函数进行消息传输了。

    服务器端的代码如下:

    #include <stdio.h>
    //#include <linux/in.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    void *recv_run(void *);
    
    int main(int argc, char **argv)
    {
        //1.创建套接字
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0)
        {
            perror("socket fail");
            return -1;
        }
    
        //2.连接服务器
        struct sockaddr_in saddr;
        memset(&saddr, 0, sizeof(saddr));
        saddr.sin_family=AF_INET;
        saddr.sin_port = htons(atoi(argv[2]));
        saddr.sin_addr.s_addr = inet_addr(argv[1]);
        int ret = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
        if(ret < 0)
        {
            perror("connect fail");
            return -1;
        }
    
        //创建线程
        pthread_t id = 0;
        ret = pthread_create(&id, NULL, recv_run, (void*)sockfd);
    
    
    
        char buffer[1024]={0};
        while(1)
        {
            scanf("%s", buffer);
            write(sockfd, buffer, strlen(buffer));
        }
    
    
        close(sockfd);
    
        return 0;
    }
    
    void *recv_run(void *arg)
    {
        int sockfd = (int)arg;
        char buffer[128];
        while(1)
        {
            memset(buffer, 0, 128);
            int ret = read(sockfd, buffer, 128);
            if(ret <= 0)
            {
                perror("read fail");
                break;
            }
    
            printf("%s
    ", buffer);
        }
    }

    客户端的代码如下

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    #define MAXCLIENT 10
    
    struct client_info
    {
        int clientfd;
        struct sockaddr_in addr;
        char userid[32];
    };
    
    struct client_info  infos[MAXCLIENT];//保存客户端信息结构体数组
    
    void *client_run(void *);
    
    
    int main(int argc, char **argv)
    {
        //1.创建套接字 
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0)
        {
            perror("socket fail");
            exit(1);
        }
    
        //2.绑定
        struct sockaddr_in saddr;
        memset(&saddr, 0, sizeof(saddr));
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = htonl(INADDR_ANY);
        int ret = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
        if(ret < 0)
        {
            perror("bind fail");
            exit(1);
        }
    
        //3.监听
        ret  = listen(sockfd, 5);
        if(ret < 0)
        {
            perror("listen fail");
            exit(1);
        }
    
        //4.接受连接、
        struct sockaddr_in caddr;
        socklen_t len = sizeof(caddr);
    
        int i=0;
        while(1)
        {
            int clientfd = accept(sockfd, (struct sockaddr*)&caddr, &len);
            if(clientfd < 0)
            {
                perror("accept fail");
                continue;
            }
            //保存客户端信息
            while((i<MAXCLIENT) && (infos[i].clientfd != 0))i++;
    
            if(i<MAXCLIENT)
            {
                infos[i].clientfd = clientfd;
                infos[i].addr = caddr;
            }
    
    
            //连接成功,创建线程
            pthread_t id = 0;
            int pret  = pthread_create(&id, NULL, client_run, (void*)clientfd);
            if(pret < 0)
            {
                perror("pthread_create fail");
                continue;
            }
    
            //线程分离
            pthread_detach(id);
        }
    }
    
    
    struct client_info *get_info(int clientfd)
    {
        for(int i=0; i<MAXCLIENT; i++)
        {
            if(infos[i].clientfd == clientfd)
                return &infos[i];
        }
        return NULL;
    }
    
    struct client_info *get_info_id(const char *userid)
    {
        for(int i=0; i<MAXCLIENT; i++)
        {
            if(strcmp(infos[i].userid,userid)==0)
                return &infos[i];
        }
        return NULL;
    }
    
    
    //线程处理客户端
    void *client_run(void *arg)
    {
        int clientfd = (int)arg;
        //读取客户端数据
        char buffer[128]={0};
        while(1)
        {
            memset(buffer, 0 , 128);
            int ret = read(clientfd, buffer, sizeof(buffer));
            if(ret <= 0)
            {
                perror("客户端掉线");
                struct client_info *cinfo = get_info(clientfd);
                if(cinfo != NULL)
                {
                    //清空客户端信息
                    memset(cinfo, 0, sizeof(struct client_info));
                }
                break;
            }
    
            //解析接收的数据是否为userid  login:userid
            if(strstr(buffer, "login:"))
            {
                char userid[32];
                sscanf(buffer, "login:%s", userid);
                struct client_info *cinfo = get_info(clientfd);
                if(cinfo != NULL)
                {
                    printf("login解析:%s
    ", userid);
                    strcpy(cinfo->userid, userid);
                    continue;
                }
            }
    
    
            //服务器处理客户端的数据(请求)//转发数据协议TO:userid:Data 把data给userid
            if(strstr(buffer,"TO:"))
            {
                char userid[32]={0};
                char data[128]={0};
                sscanf(buffer, "TO:%[^:]:%s",userid, data);
                
                printf("%s
    解析转发数据:%s    %s
    ", buffer, userid, data);
                struct client_info *cinfo = get_info_id(userid);
                if(cinfo != NULL)
                {
                    write(cinfo->clientfd, data, strlen(data));
                }
            }
        }
        close(clientfd);
    }

    5.多路复用机制

      (1)select

      select可以接听多个文件描述符,当所有文件描述符都没有响应的时候,select会处于阻塞(休眠状态),当其中一个或者多个文件描述符有响应的时候,select函数就会被唤醒。

    #include <sys/select.h>
           #include <sys/time.h>
           #include <sys/types.h>
           #include <unistd.h>
    
           int select(int nfds, fd_set *readfds, fd_set *writefds,
                      fd_set *exceptfds, struct timeval *timeout);
    
    参数:int nfds 最大文件描述符加1
          fd_set *readfds ---读文件描述符集合
      fd_set *writefds---写文件描述符集合
          fd_set *exceptfds---错误文件描述符集合
    struct timeval *timeout  --设置超时,如果设置为NULL一直等待响应
    
           void  FD_CLR(int fd, fd_set *set); 从set集合中清除fd
           int   FD_ISSET(int fd, fd_set *set); 判断set中响应的文件描述符是否是fd
           void  FD_SET(int fd, fd_set *set);  把fd添加到set集合中
           void  FD_ZERO(fd_set *set);清空集合

    通过select来实现的TCP客户端代码如下:

    #include <stdio.h>
    //#include <linux/in.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/mman.h>
    
    
    int init_lcd(unsigned int **mp, const char *dev)
    {
        int fd = open(dev, O_RDWR);
        *mp =(unsigned int *) mmap(NULL, 800*480*4, PROT_READ|PROT_WRITE, MAP_SHARED,fd, 0);
        return fd;
    }
    
    void set_color(unsigned int *mp, unsigned int color)
    {    
        for(int i=0 ;i<800*480; i++)
        {
            mp[i] = color;
        }
    }
    
    void destroy_lcd(int fd, unsigned int *mp)
    {
        munmap(mp, 800*480*4);
        close(fd);
    }
    
    int main(int argc, char **argv)
    {
        //1.创建套接字
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0)
        {
            perror("socket fail");
            return -1;
        }
    
        //2.连接服务器
        struct sockaddr_in saddr;
        memset(&saddr, 0, sizeof(saddr));
        saddr.sin_family=AF_INET;
        saddr.sin_port = htons(atoi(argv[2]));
        saddr.sin_addr.s_addr = inet_addr(argv[1]);
        int ret = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
        if(ret < 0)
        {
            perror("connect fail");
            return -1;
        }
        
    
        //定义读文件描述符集合
        fd_set readfds;
    
        //lcd
        unsigned int *mp = NULL;
        int lcdfd = init_lcd(&mp, "/dev/ubuntu_lcd");
    
        while(1)
        {
            FD_ZERO(&readfds);
            //把要监听的文件描述符添加到集合中
            FD_SET(sockfd, &readfds);
            FD_SET(0, &readfds);
            //select
            ret = select(sockfd+1, &readfds, NULL, NULL, NULL);//阻塞
            if(ret < 0)
            {
                perror("select fail");
            }
            //查看是那个文件描述符有相应(有数据可读)
            if(FD_ISSET(0, &readfds))
            {    
                char buffer[1024]={0};
                scanf("%s", buffer);
                write(sockfd, buffer, strlen(buffer));
            }
        
            if(FD_ISSET(sockfd, &readfds))
            {
                char buffer[128];
                memset(buffer, 0, 128);
                int ret = read(sockfd, buffer, 128);
                if(ret <= 0)
                {
                    perror("read fail");
                    break;
                }
                printf("%s
    ", buffer);    
                if(strcmp(buffer,"red") ==0)
                {
                    set_color(mp,0x00ff0000);
                }else if(strcmp(buffer, "blue")==0)
                {
                    set_color(mp,0x000000ff);
                }
    
            }
        }
        destroy_lcd(lcdfd, mp);
        return 0;
    }

      (2)epoll机制

      系统提供的接口:

        1.创建epoll实例(句柄)

    #include <sys/epoll.h>
    int epoll_create(int size);
    参数: size---监听文件描述符的大小
    返回值:epoll句柄(类似与文件描述符), 成功>0, 失败-1

        2.添加/修改/删除要监听的文件描述符

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    参数:int epfd---epoll句柄
          int op---操作EPOLL_CTL_ADD/MOD/DEL
          int fd---要添加被监听的文件描述符
          struct epoll_event *event----epoll事件
    
    typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
    } epoll_data_t;
    
    struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
    };
    
    events:事件类型(EPOLLIN--输入, EPOLLOUT-输出, EPOLLERR--出错)
    epoll_data_t  data;  设置联合体为fd
    返回值:成功返回0, 失败-1

        3.监听文件描述符--“阻塞”

    int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
    参数:int epfd--epoll句柄
          struct epoll_event *events---用存储触发的事件(触发的事件有可能是多个)
                  int maxevents --events所指向空间的长度
                  int timeout 》0  设置多毫秒后跳出函数
                            ==0立即返回
                            ==-1一直等待直到有事件发生
            返回值:>0  返回多个个文件描述符有数据可读(事件数)
                    ==0 没有可以数据文件描述符,超时时间到没有任何文件描述符有数据可读
    ==-1出错

      触发方式:(1)沿边触发  EPOLLET;(2)水平触发  EPOLLEL

    6.非阻塞

      1.打开文件时候设置为非阻塞打开  O_NONBLOCK 

      2.通过fcntl函数设置非阻塞

    (1)    int flag = fcntl(fd, F_GETFL);
    (2)    flag |=O_NONBLOCK
    (3)    fcntl(fd, F_SETFL, flag);

    7.心跳包:检测客户端是否在线

      epoll_server_x跳包

    8.协议(数据协议包----json)

      键值形式---优点方便协议升级

      对象{}  -----对象中存储的是键值对, 多个键值对用(,)隔开

      数组[]  ----数组中存储的同类型数据(对象, 数组, 字符串, 数值)

      键值对 key:value ---key只能字符串, value--可以是对象, 数组, 字符串, 数字

    {
        "clientfd": [{
            "sockfd": 5,
            "ip": "192.168.1.44",
            "userid": "aaaa"
        }, {
            "sockfd": 6,
            "ip": "192.168.1.45",
            "userid": "bbbb"
        }]
    }

      json数据的打包如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <string.h>
    #include "cJSON.h"
    
    
    int main(void)
    {
        char buffer[] = "{
        "clientfd": [{
            "sockfd": 5,
            "ip": "192.168.1.44",
            "userid": "aaaa"
        }]}";
        
        
        //创建一个对象{"sockfd": 5,"ip": "192.168.1.44","userid": "aaaa"}
        cJSON *arrayOBj = cJSON_CreateObject();
        cJSON_AddItemToObject(arrayOBj,"sockfd", cJSON_CreateNumber(5));
        cJSON_AddItemToObject(arrayOBj,"ip", cJSON_CreateString("192.168.1.44"));
        cJSON_AddItemToObject(arrayOBj,"userid", cJSON_CreateString("aaaa"));
        
        //创建一个数组[{"sockfd": 5,"ip": "192.168.1.44","userid": "aaaa"}]
        cJSON *array = cJSON_CreateArray();
        cJSON_AddItemToArray(array, arrayOBj);
        
        
        //创建最外层对象{"clientfd":[{"sockfd": 5,"ip": "192.168.1.44","userid": "aaaa"}]}
        cJSON *root = cJSON_CreateObject();
        cJSON_AddItemToObject(root,"clientfd", array);
        
        //转字符串
        char *str = cJSON_Print(root);
        printf("%s
    ", str);
        return 0;
    }

      这是解析上面的数据:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <string.h>
    #include "cJSON.h"
    
    
    int main(void)
    {
        char buffer[] = "{
        "clientfd": [{
            "sockfd": 5,
            "ip": "192.168.1.44",
            "userid": "aaaa"
        }, {
            "sockfd": 6,
            "ip": "192.168.1.45",
            "userid": "bbbb" 
        }],"my":"ddd"}";
        
        char send_buffer[]="{
        "clientfd": [{
            "sockfd": 5,
            "ip": "192.168.201.130",
            "name": "name",
            "password": "password"
        }]}";
        
        //创建一个json解析对象
        cJSON *root = cJSON_Parse(buffer);
        
        //根据clientfd键获取值--》数组
        cJSON *array = cJSON_GetObjectItem(root, "clientfd");
        
        //获取数组长度
        int len = cJSON_GetArraySize(array);
        
        //遍历数组--得到对象
        for(int i=0; i<len; i++)
        {
            cJSON *arrayObj = cJSON_GetArrayItem(array, i); 
            cJSON *tmp = cJSON_GetObjectItem(arrayObj, "sockfd");
            printf("%d
    ", tmp->valueint);
            tmp = cJSON_GetObjectItem(arrayObj, "ip");
            printf("%s
    ", tmp->valuestring);
            tmp = cJSON_GetObjectItem(arrayObj, "userid");
            printf("%s
    ", tmp->valuestring);
        }
        
        printf("%s
    ", buffer);
        
    }

    PS:哪里写错了请指正,互相学习。

  • 相关阅读:
    xdebug安装教程
    如何查看Linux操作系统的位数
    getconf命令【一天一个命令】
    redis 数据类型详解 以及 redis适用场景场合
    Redis和Memcache对比及选择
    无交换机实现集群网络互联
    性能调优攻略
    Chrome 插件集推荐
    在 Linux 下将 PNG 和 JPG 批量互转的四种方法
    Flashback for MySQL 5.7
  • 原文地址:https://www.cnblogs.com/smallqizhang/p/12564231.html
Copyright © 2011-2022 走看看