实践2 . 树莓派A20 TCP客户端服务端测试程序


1 说明

本篇是两个程序,分为客户端和服务端程序,作为工作中测试的一个通信程序例程。

2 客户端

#include 
#include 
#include 
#include 
#include 

#define SERVER_ADDR     "192.168.25.15"
#define SERVER_PORT     4819

#define SOCKET_BUF_LEN 1024


#define Tranverse32(X)                 ((((unsigned int)(X) & 0xff000000) >> 24) | \
                                                           (((unsigned int)(X) & 0x00ff0000) >> 8) |\
                                                           (((unsigned int)(X) & 0x0000ff00) << 8) |\
                                                           (((unsigned int)(X) & 0x000000ff) << 24))


int main()
{
    int fd = 0;
    int i = 0;
    struct sockaddr_in serveraddr;
    int socket_opt_val = 0;
    unsigned char buf[SOCKET_BUF_LEN] = {'\0'};
    unsigned char rcv_buf[SOCKET_BUF_LEN] = {0};
    unsigned int  i_value = 0;
    int len = 0;
     
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Cread Sock Error, Return");

        return -1;
    }
    printf("[%d]Cread Sock Ok\n", fd);

    //设置该套接字接口可以与已经在使用的地址绑定(BF548有多个套接字)
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, \
        &socket_opt_val, sizeof(socket_opt_val)) < 0) {
        perror("Setsockopt Error, Return");

        return -1;
    }
    printf("Setsockopt Ok\n");

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port   = htons(SERVER_PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);

    if (connect(fd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0) {
        perror("Connect Error, Return");

        return -1;
    }
    printf("Connect Ok\n");

    buf[0] = 0x06;
    buf[1] = 0x01;
    buf[2] = 0x00;
    buf[3] = 0x00;
    if (send(fd, buf, 4, 0) < 0) {
        perror("Send Error, Close This Channel, Return");
        close(fd);

        return -1;
    }
    printf("Send Ok\n");

    while (1) {
        memset(buf, 0, SOCKET_BUF_LEN);
        if ((len = recv(fd, rcv_buf, SOCKET_BUF_LEN, 0)) < 0) {
            perror("Recv Error, Break");

            break;
        } else {
            rcv_buf[len] = '\0';

            if((len % 4 == 0) && (len != 0 ))
            {    
                for(i = 0 ; i < len ; i = i + 4)
                {
                //  printf("0x%0x,0x%0x,0x%0x,0x%0x\n",rcv_buf[i+0],rcv_buf[i+1],rcv_buf[i+2],rcv_buf[i+3]);
                    //i_value = (rcv_buf[i] << 24) | (rcv_buf[i+1] << 16) | ((rcv_buf[i+2] << 8)) | ((rcv_buf[i+3] << 0));
                    i_value = (rcv_buf[i]) | (rcv_buf[i+1] << 8) | ((rcv_buf[i+2] << 16)) | ((rcv_buf[i+3] << 24));
    
                    printf("0x%08x\n",i_value);

                    //if(i_value == 0xc612c)
                    //  printf("---recv delay----------\n");

                    //if(i_value == 0x2135)
                    //  printf("------first channel------\n");

                    i_value = Tranverse32(i_value);

                    if((i_value >> (32-6)) == 15)
                    {
                        printf("-----row:%0x,col:%d,val:%d-----\n",(i_value>>21)&0x1f,(i_value>>16)&0x1f,i_value&0xff);
                    } 
                }
            }
        
        }
    }

    printf("Quit\n");
    close(fd);

    return 0;
}


3 服务端

3.1 简单的服务端程序

#include 
#include 
#include 
#include 
#include 

#define SERVER_ADDR     "192.168.25.15"
#define SERVER_PORT     4819

#define SOCKET_BUF_LEN 1024


int main(int argc,char *argv[])
{
    int server_fd = 0;
    int i = 0;
    struct sockaddr_in serveraddr;
    int socket_opt_val = 0;
    unsigned char buf[SOCKET_BUF_LEN] = {'\0'};
    unsigned char rcv_buf[SOCKET_BUF_LEN] = {0};
    unsigned int  i_value = 0;
    int len = 0;
     
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Cread Sock Error, Return");
        return -1;
    }
    printf("[%d]Cread Sock Ok\n", server_fd);

    //ctrl+c prevent bind already
     int mw_optval = 1;
     setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port   = htons(SERVER_PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);

     if(bind(server_fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)   {
        printf("----bind server error----\r\n");
        close(server_fd);
        return -1;
     }

    //listen the socket
    int listen_sock = listen(server_fd, 5);  
    if(listen_sock < 0)  {
        printf("---listen server socket error---\r\n");
        close(server_fd);
        return -1;
    }

    struct sockaddr_in peer;  
    socklen_t peer_len = sizeof(struct sockaddr_in); 
    
    int accept_fd = accept(server_fd, (struct sockaddr*)&peer, &peer_len);  
    if(accept_fd < 0)  
    {  
        printf("---accept client error:%d----\r\n",accept_fd);  
        close(server_fd);
        return -1;  
    }  
    else  
    {  
        printf("connected with ip: %s  and port: %d\n", inet_ntop(AF_INET,&peer.sin_addr, buf, 1024), ntohs(peer.sin_port));  
    }   
    while(1)  {
        memset(buf, '\0', sizeof(buf)); 
        ssize_t size = read(accept_fd, buf, sizeof(buf) - 1);  
        if(size > 0)  
        {  
            printf("client#: %s\n", buf);  
        }
        else if(size == 0)  
        {  
              printf("read is done...\n");  
              break;  
         }  
        else   
        {  
            printf("----read error----\r\n");  
            break;  
        }       
        printf("server please enter: ");  
        fflush(stdout);  
        size = read(0, buf, sizeof(buf) - 1);  
        if(size > 0)  
        {  
            buf[size - 1] = '\0';  
            printf("console:read:%s\r\n",buf);
        }  
        else if(size == 0)  
        {  
              printf("read is done...\n");  
              break;  
          }  
          else  
          {  
              perror("read");  
              break;  
          }     
          write(accept_fd, buf, strlen(buf));  
     }  
     
    close(server_fd);
        
    return 0;
}

服务端的测试,可以使用sokit工具来连接,做简单的消息交互。

sokit工具如下所示:


实践2 . 树莓派A20 TCP客户端服务端测试程序_第1张图片
sokit工具

3.2 使用select优化服务端程序

有这样的需求,服务器只需要接受一个客户端即可,但是,有这么一个情况:在开始的时候,客户端和服务器是连在一起的,但是,途中将网线拔掉了,接着客户端也关闭了。此时再连接网线,再次开启客户端,就会发现一般情况下,会发生连不上服务器的情况。

这种情况发生的原因主要在,第二次连接的时候,服务器的第一次连接还没有关闭。

问题点就在两次连接,产生了两个客户端。上边的程序是属于单客户端单服务器的情况。

所以,我们需要解决的问题点,那就变成了,在第二次连接的时候,服务端只需要关闭第一个客户端即可。

这里就要谈到在linux应用层上的select函数的使用。

select函数原型如下:

int select (int maxfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

解释:
select系统调用是用来监测多个文件句柄的状态变化的。错误返回-1。

最后一个参数,表明的是需要等待的时间:

struct timeval{        
  long tv_sec;   /*秒 */  
  long tv_usec;  /*微秒 */     
}  

若timeout=NULL的时候,表示等待无限长时间。可以被一个信号中断。
若tv_sec=0并且tv_usec=0,表示不等待。加入的描述符集都会被测试的。
若tv_sec!=0或者tv_usec!=0,表示等待指定的时间。

fd_set类型变量的每一位表示的是一个描述符。我摘抄一张图:

实践2 . 树莓派A20 TCP客户端服务端测试程序_第2张图片
fd_set结构描述符

控制fd_set变量,可以使用如下几个宏:

#include      
int FD_ZERO(int fd, fd_set *fdset);     
int FD_CLR(int fd, fd_set *fdset);     
int FD_SET(int fd, fd_set *fd_set);     
int FD_ISSET(int fd, fd_set *fdset); 

如何使用这几个宏?

首先需要将fd_set变量设置为0,也就需要使用FD_ZERO,然后可以设置我们需要关心的描述符,可以使用FD_SET,相对应的就要使用FD_CLR,最后是select函数返回,可以使用FD_ISSET来判断所关心的fd_set变量是否有变化。

关于这些内容,我们可以直接参考链接: 链接

到这里,可以直接贴上一部分简单的程序:

#include 
#include 
#include 
#include 
#include 

#define SERVER_ADDR     "192.168.25.15"
#define SERVER_PORT     4819

#define SOCKET_BUF_LEN 1024


unsigned char recv_data[SOCKET_BUF_LEN];

typedef struct _CLIENT{    
    int       fd;        
    struct sockaddr_in addr;                              
} CLIENT;


int main(int argc,char *argv[])
{
    int server_fd = 0;
    int i = 0;
    struct sockaddr_in serveraddr;
    int socket_opt_val = 0;
    unsigned char buf[SOCKET_BUF_LEN] = {'\0'};
    unsigned char rcv_buf[SOCKET_BUF_LEN] = {0};
    unsigned int  i_value = 0,sockfd,bytes_received;
    int len = 0;
     
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Cread Sock Error, Return");
        return -1;
    }
    printf("[%d]Cread Sock Ok\n", server_fd);

    //ctrl+c prevent bind already
     int mw_optval = 1;
     setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port   = htons(SERVER_PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);

     if(bind(server_fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)   {
        printf("----bind server error----\r\n");
        close(server_fd);
        return -1;
     }

    //listen the socket
    int listen_sock = listen(server_fd, 5);  
    if(listen_sock < 0)  {
        printf("---listen server socket error---\r\n");
        close(server_fd);
        return -1;
    }

    struct sockaddr_in peer;  
    socklen_t peer_len = sizeof(struct sockaddr_in); 
    int count = 0;


    fd_set server_fd_set;    
    int max_fd = -1;    
    while(1){
        FD_ZERO(&server_fd_set); 
        FD_SET(server_fd, &server_fd_set);    
        max_fd = server_fd;
        CLIENT client[5];
        int     nready;

        for (i = 0; i < 5; i++) {
            client[i].fd = -1;  
        }       
        

        while(1){
            nready = select(max_fd+1, &server_fd_set, NULL, NULL, NULL);     

            /* select the server fd to check if a new connection is avaliable...*/
            if (FD_ISSET(server_fd, &server_fd_set)) {
                int accept_fd = accept(server_fd, (struct sockaddr*)&peer, &peer_len);  

                if(accept_fd == -1){
                    printf("---accept error----\r\n");
                    continue;
                }
                else  
                {  
                    printf("connected with ip: %s  and port: %d\n", inet_ntop(AF_INET,&peer.sin_addr, buf, 1024), ntohs(peer.sin_port));  
                }   
                count ++;

            }
        }
    }
    close(server_fd);   
    return 0;
}

一些说明

在这个程序里,测试的时候,可以先运行程序,然后在一切正常的情况下,使用sokit连接服务器,然后拔掉网线,断开客户端tcp连接,再接上网线,连接客户端,然后拔掉网线,断掉客户端tcp连接。然后再连接网线,开启客户端。。。测试结果如下:

实践2 . 树莓派A20 TCP客户端服务端测试程序_第3张图片
程序测试结果

所以,我们可以得到一个结论,使用select可以监测到server_fd的变化的。在此基础上,我们在检测到第二个连接的时候,断开第一个连接即可。

同样,也是直接贴上代码:

#include 
#include 
#include 
#include 
#include 

#define SERVER_ADDR     "192.168.1.16"
#define SERVER_PORT     4819

#define SOCKET_BUF_LEN 1024

unsigned char recv_data[SOCKET_BUF_LEN];
typedef struct _CLIENT{    
    int       fd;        
    struct sockaddr_in addr;                              
} CLIENT;


int main(int argc,char *argv[])
{
    int server_fd = 0;
    int i = 0;
    struct sockaddr_in serveraddr;
    int socket_opt_val = 0;
    unsigned char buf[SOCKET_BUF_LEN] = {'\0'};
    unsigned char rcv_buf[SOCKET_BUF_LEN] = {0};
    unsigned int  i_value = 0,sockfd,bytes_received;
    int len = 0;
     
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Cread Sock Error, Return");
        return -1;
    }
    printf("[%d]Cread Sock Ok\n", server_fd);

    //ctrl+c prevent bind already
     int mw_optval = 1;
     setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port   = htons(SERVER_PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);

     if(bind(server_fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)   {
        printf("----bind server error----\r\n");
        close(server_fd);
        return -1;
     }

    //listen the socket
    int listen_sock = listen(server_fd, 5);  
    if(listen_sock < 0)  {
        printf("---listen server socket error---\r\n");
        close(server_fd);
        return -1;
    }

    struct sockaddr_in peer;  
    socklen_t peer_len = sizeof(struct sockaddr_in); 
    int count = 0;


    fd_set server_fd_set;    
    int max_fd = -1;    
    while(1){

        CLIENT client;
        int    nready;

        client.fd = -1;         
        
        while(1){
            FD_ZERO(&server_fd_set); 
            FD_SET(server_fd, &server_fd_set);    
            max_fd = server_fd;

            if(server_fd > max_fd)
                max_fd = server_fd;

            if(client.fd!=-1){
                FD_SET(client.fd, &server_fd_set);
                if(client.fd > max_fd)
                    max_fd = client.fd;
            }
            
            nready = select(max_fd+1, &server_fd_set, NULL, NULL, NULL);     

            /* select the server fd to check if a new connection is avaliable...*/
            if (FD_ISSET(server_fd, &server_fd_set)) {
                int accept_fd = accept(server_fd, (struct sockaddr*)&peer, &peer_len);  

                if(accept_fd == -1){
                    printf("---accept error----\r\n");
                    continue;
                }
                else  
                {  
                    printf("port: %d\n", ntohs(peer.sin_port));
                    //printf("connected with ip: %s  and port: %d\n", inet_ntop(AF_INET,&peer.sin_addr, buf, 1024), ntohs(peer.sin_port));  
                }   
                count ++;

                /*if client exists,clear the last client fd*/
                if(client.fd != -1){
                    close(client.fd);
                    client.fd = -1;
                }
                /* add a fd to client*/
                client.fd = accept_fd;
            }

            if(client.fd != -1){
                /* poll the client status*/
                if (FD_ISSET(client.fd, &server_fd_set))
                {
                    int byte_num = recv(client.fd,rcv_buf,SOCKET_BUF_LEN,0);    
                    if(byte_num>0){
                        if(byte_num>SOCKET_BUF_LEN){
                            byte_num=SOCKET_BUF_LEN;
                        }
                        rcv_buf[byte_num]='\0';
                        printf("client:%s\n",rcv_buf);
                    }
                    else if(byte_num < 0)
                    {
                        printf("client error.\n");
                    }
                    else
                    {
                        FD_CLR(client.fd,&server_fd_set);
                        close(client.fd);
                        client.fd=-1;
                        printf("client quit\n");
                    }
                }
            }
            
        }
    }
    close(server_fd);   
    return 0;
}

实现的效果如下图所示:


实践2 . 树莓派A20 TCP客户端服务端测试程序_第4张图片
实现结果

实践2 . 树莓派A20 TCP客户端服务端测试程序_第5张图片
sokit发送的数据

我做一个基本的简单的讲解:
1.在第二个while(1)中,先清除所有检测的值,这是因为,在第二个客户端连接的时候,第一个客户端就关闭了,这个时候检测的set集合就改变了,我们能清除第一个客户端在集合中的值,但是并没有办法改变max的值,所以清除所有的要检测的值,然后重新设置值,是一个办法。
2.select检测到服务端有变化,就知道有客户端连接上了,所以获取到accept_fd,然后才能置位set集合,也就能检测客户端是否接收数据了。
3.客户端接收数据,处理数据。

一点注意:

我前面的测试过程,都是在树莓派A20上进行的,在使用inet_ntop的时候,并没有出错。但是在使用64位主机的时候,最好还是加上头文件:

#include 

错误信息如下所示:

实践2 . 树莓派A20 TCP客户端服务端测试程序_第6张图片
core dump

加上头文件后,发送数据:

实践2 . 树莓派A20 TCP客户端服务端测试程序_第7张图片
主机端接收数据并打印消息
实践2 . 树莓派A20 TCP客户端服务端测试程序_第8张图片
客户端发送数据

4 使用QtCreator创建客户端

你可能感兴趣的:(实践2 . 树莓派A20 TCP客户端服务端测试程序)