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\n", 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\n", 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\n解析转发数据:%s    %s\n", 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\n", 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\n", 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\n", tmp->valueint);
        tmp = cJSON_GetObjectItem(arrayObj, "ip");
        printf("%s\n", tmp->valuestring);
        tmp = cJSON_GetObjectItem(arrayObj, "userid");
        printf("%s\n", tmp->valuestring);
    }
    
    printf("%s\n", buffer);
    
}

 

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

posted @ 2020-03-25 10:21  七章啊  阅读(244)  评论(0编辑  收藏  举报