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


1 说明

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

2 客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

#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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

#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工具如下所示:


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类型变量的每一位表示的是一个描述符。我摘抄一张图:

fd_set结构描述符

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

#include <sys/select.h>     
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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

#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连接。然后再连接网线,开启客户端。。。测试结果如下:

程序测试结果

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

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

#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;
}

实现的效果如下图所示:


实现结果

sokit发送的数据

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

一点注意:

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

#include <arpa/inet.h>

错误信息如下所示:

core dump

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

主机端接收数据并打印消息
客户端发送数据

4 使用QtCreator创建客户端

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,569评论 18 139
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    y角阅读 2,369评论 2 11
  • 以记忆里的歌为线,写记忆里的故事。我把我记忆里的事讲给你听。 如果这一段应该有音乐背景的话,我想应该是秦腔,对,那...
    春花红阅读 151评论 0 0
  • 中国人凡事讲个吉利,一谈到关于“死亡”,总觉得晦气,大概中国人也是很懂得“墨菲定律”的,害怕说什么就来什么,不说,...
    李姐有解药阅读 1,182评论 12 17