当前位置: 首页 > news >正文

嵌入式自学之网络编程汇总(6.3-6.6 ,6.9)

由于不同进程内存空间相互隔绝,变量不共享,所以无法直接传递数据。

为实现进程间通信,可以在内核空间开辟一个共享空间,实现不同进程的数据共享。

1、古老的通信方式:无名管道、有名管道、信号(异步通信)

(1)无名管道:只能给有亲缘关系进程通信。有名管道:可以给任意单机进程通信

        *管道的特性(底层逻辑就是一个队列):
                1)管道是 半双工的工作模式:两个进程都可以收发,但同一时刻只能收或者发。
                2)所有的管道都是特殊的文件不支持定位操作。lseek->> fd  fseek ->>FILE* 
                3) 管道是文件,可以读写使用文件IO。fgets,fread,fgetc,
                open,read,write,close;

(2)读端存在,F一直向管道中去写,超过64k(man 7 pipe在总体描述查看capacity容量),写会阻塞,等读取一定的数据,比如一页4k读完,写端阻塞才会解除。 

写端是存在的,C读管道,如果管道为空的话,读会阻塞。

管道破裂,C读端关闭,F写管道。


读到0,F写端关闭,如果管道没有内容,C读端会read 0 ;

(3)管道使用方法:创建管道 ==》读写管道 ==》关闭管道

        1)创建并打开管道: pipe函数
        #include <unistd.h>
        int pipe(int pipefd[2]);
        功能:创建并打开一个无名管道
        参数:pipefd[0] ==>无名管道的固定读端
                  pipefd[1] ==>无名管道的固定写端
        返回值:成功 0,失败 -1;

        2)无名管道的读写:===》文件IO的读写方式。
        读: read()  ,写: write()
        3)关闭管道: close()

无名管道创建要在fork前,这样父子进程就用的同一个管道

        例:利用管道实现数据共享

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>int main()
{int fd[2];int ret = pipe(fd);if(-1 == ret){fprintf(stderr,"pipe");return -1;}pid_t pid = fork();if(pid > 0){close(fd[0]);write(fd[1], "hell\n", 5);close(fd[1]);}else if(0 == pid){close(fd[1]);char buf[10] = {0};read(fd[0],buf,10);printf("%s",buf);close(fd[0]);}else{perror("fork");return 0;}return 0;
}

验证管道四种状况:

F一直写,C不读,等管道满了(64k)就会写阻塞

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include<string.h>
int main()
{int fd[2];int ret = pipe(fd);if(-1 == ret){fprintf(stderr,"pipe");return -1;}pid_t pid = fork();if(pid > 0){close(fd[0]);char buf[1024];memset(buf,'a',1024);int i = 1;while(1){write(fd[1], buf, 1024);printf("%d\n",i++);}close(fd[1]);}else if(0 == pid){close(fd[1]);while(1);close(fd[0]);}else{perror("fork");return 0;}return 0;
}

C一直读,F不写,等管道空了就会读阻塞

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>int main()
{int fd[2];int ret = pipe(fd);if(-1 == ret){fprintf(stderr,"pipe");return -1;}pid_t pid = fork();if(pid > 0){close(fd[0]);while(1)close(fd[1]);}else if(0 == pid){close(fd[1]);char buf[10] = {0};read(fd[0],buf,10);printf("%s",buf);close(fd[0]);}else{perror("fork");return 0;}return 0;
}

当C把读也关闭,F再往管道写内容会导致管道破裂、

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>int main()
{int fd[2];int ret = pipe(fd);if(-1 == ret){fprintf(stderr,"pipe");return -1;}pid_t pid = fork();if(pid > 0){close(fd[0]);write(fd[1], "hell\n", 5);close(fd[1]);}else if(0 == pid){close(fd[1]);close(fd[0]);}else{perror("fork");return 0;}return 0;
}

如果F把写关闭,管道没有内容,C继续读会读到0

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int fd[2];int ret = pipe(fd);if (-1 == ret){fprintf(stderr, "pipe");return -1;}pid_t pid = fork();if (pid > 0){close(fd[0]);write(fd[1], "aaa", 3);close(fd[1]);exit(0);}else if (0 == pid){close(fd[1]);sleep(3);char buf[10] = {0};while (1){int ret = read(fd[0], buf, 10);if (0 == ret){printf("read 0");break;}printf("%s", buf);}close(fd[0]);}else{perror("fork");return 0;}return 0;
}

综合应用:利用管道实现文件复制

#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int fd[2];int ret = pipe(fd);if (-1 == ret){fprintf(stderr, "pipe");return -1;}pid_t pid = fork();if (pid > 0){close(fd[0]);int fd1 = open("pipe.c", O_RDONLY);char buf[1024] = {0};int ret = read(fd1, buf, 1024);write(fd[1], buf, ret);close(fd[1]);close(fd1);}else if (0 == pid){close(fd[1]);char buf[1024] = {0};int ret = read(fd[0], buf, 1024);int fd1 = open("1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);write(fd1, buf, ret);close(fd[0]);close(fd1);}else{perror("fork");return 0;}return 0;
}

(4)验证如下问题:
*父子进程是否都有fd[0] fd[1], 如果在单一进程中写fd[1]能否直接从fd[0]中读到。
   可以,写fd[1]可以从fd[0]读

*管道的数据存储方式是什么样的,数据是否一直保留?
 队列形式存储 读数据会剪切取走数据不会保留

*管道的数据容量是多少,有没有上限值。
操作系统的建议值: 512* 8 = 4k
代码测试实际值:   65536byte= 64k

*管道的同步效果如何验证?读写同步验证。
读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止 
写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞

结论:读写端必须同时存在,才能进行
  管道的读写。


*固定的读写端是否就不能互换?
能否写fd[0] 能否读fd[1]?   不可以,是固定读写端。

(5)有名管道:给创建的管道用一个文件名关联,与无名管道不同的是进程都用close关闭管道文件描述符并且程序结束,管道文件不会消失,需要手动删除。

        1)有名管道使用:创建有名管道 ==》打开有名管道 ==》读写管道==》关闭管道  ==》卸载有名管道

        *1、创建:mkfifo,由于两个进程都要创建管道,而其中一个创建后管道文件就存在了,所以要在创建后根据返回值来加一个判断语句,错误一般是EEXIST文件已存在,配合错误号errno使用。man  mkfifo 查看errors
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路径+名称下创建一个权限为mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
  mode  8进制文件权限,0666。
返回值:成功 0
失败  -1;
fifo大小一直为0,因为内容是写在内核空间的。

        *2、打开有名管道 open,会在此阻塞,等待读写端都打开文件。
注意:该函数使用的时候要注意打开方式,因为管道是半双工模式,所有打开方式直接决定当前进程的读写方式。
一般只有如下方式:
O_RDONLY        文件是固定读端
O_WRONLY        文件是固定写端
逻辑上不能是 O_RDWR 方式打开文件,如果采取这种模式,就没有阻塞了。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数

        *3、管道的读写: 文件IO,读: read,写: write

        *4、关闭管道:close(fd);

        *5、卸载管道:remove(“文件名");

例:有名管道实现文件复制

文件1:写端(类似无名F端)

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>#include <fcntl.h>
#include <unistd.h>int main()
{int ret = mkfifo("fifo", 0666);if(-1 == ret){if(EEXIST != errno){perror("mkfifo");return -1;}}int fd = open("fifo",O_WRONLY);if(fd < 0){perror("open");return -1;}int fd1 = open("pipecp.c", O_RDONLY);char buf[1024];int ret1 = read(fd1,buf,sizeof(buf));write(fd, buf, ret1);close(fd);close(fd1);
}

读端(类似无名C)

从先后顺序来看先写再读,所以FIFO文件在代码中从读端删除即可。

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>#include <fcntl.h>
#include <unistd.h>int main()
{int ret = mkfifo("fifo", 0666);if(-1 == ret){if(EEXIST != errno){perror("mkfifo");return -1;}}int fd = open("fifo",O_RDONLY);if(fd < 0){perror("open");return -1;}char buf[1024] = {0};int ret1 = read(fd, buf, sizeof(buf));int fd1 = open("1.txt",O_WRONLY | O_TRUNC);write(fd1,buf,ret1);close(fd);close(fd1);remove("fifo");
}

        *1、是否需要同步,以及同步的位置。

  有名管道执行过程过必须有读写端同时存在。
  如果有一端没有打开,则默认在open函数部分阻塞。

        *2、有名管道是否能在fork之后的亲缘关系进程中使用。
结论: 可以在有亲缘关系的进程间使用。
注意: 启动的次序可能会导致其中一个稍有阻塞。

        *3、能否手工操作有名管道实现数据的传送。

终端创建管道:mkfifo  管道名

利用cat命令尝试(还有其他命令也可以演示)

终端1:读: cat  fifoname

终端2:写:cat > fifoname

效果:在写端输入数据,在读端会输出

(6)信号 signal (用于异步通信,例如:程序崩溃、ctrl c)

        1)查看linux所有信号:kill -l  ,共64个

进程接收到信号后几种响应(man 7 signal,里面就有下面几种状况以及信号属于下面哪种。):

 Term :  Default action is to terminate the process.结束进程,停在异常处。:段错误、ctrl c、ctrl \

       Ign  :  Default action is to ignore the signal.忽略,即接收后没反应
   wait
   
       Core:   Default action is to  terminate  the  process  and  dump  core  (see
              core(5)).结束进程后保留关键信息,停在异常处。
gdb a.out -c core
       

Stop  : Default action is to stop the process.进程暂停

       Cont  : Default  action  is  to  continue  the  process  if  it is currently
              stopped.进程继续

例:

上例底层逻辑:每个进程的pcb块里面都有信号对应函数,当接收到终端发来的信号,会立刻停止程序运行并执行信号对应的函数,如果信号不是结束程序就在信号执行完继续程序运行。

(7)进程间发信号kill

kill      -xx     xxxx
发送进程  信号    接收进程
kill -9 1000
a.out  9 1000
1、发送端
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:通过该函数可以给pid进程发送信号为sig的系统信号。
参数:pid 要接收信号的进程pid
  sig 当前程序要发送的信号编号
返回值:成功 0
失败  -1;

例:

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>#include <stdlib.h>int main(int argv,char *argc[])
{if(argv < 3){puts("a.out pid sig");return -1;}pid_t pid = atoi(argc[1]);int sig = atoi(argc[2]);int ret = kill(pid,sig);if(-1 == ret){perror("kill");return 1;}return 0;
}

(8)其他发信号函数:

int raise(int sig)== kill(getpid(),int sig);
功能:给进程自己发送sig信号

unsigned int alarm(unsigned int seconds);
功能:定时由系统给当前进程发送信号SIGALAM,程序结束
 

#include <unistd.h>
#include<stdio.h>
int main()
{alarm(5);while(1){puts("hello");sleep(1);}return 0;
}


int pause(void);
功能:进程暂停,不再继续执行,除非
  收到其他信号。

#include <unistd.h>
#include<stdio.h>
int main()
{int i = 0;while(1){if(i == 5){pause();}printf("ha\n");sleep(1);++i;}return 0;
}

(9)接收端
每个进程都会对信号作出默认响应,但不是唯一响应。
一般如下三种处理方式:
1、系统默认处理(SIG_DFL)
2、忽略处理(SIG_IGN) ,只有9,19不能忽略
3、自定义处理、 捕获(9,19不能捕获):signal(sig,handle);

即下图,把信号编号对应功能更改



以上三种方式的处理需要在如下函数上实现。

信号注册函数原型:
 void ( *signal(int signum, void (*handler)(int)) ) (int);

 typedef void (*sighandler_t)(int);
 ===》void (*xx)(int); == void fun(int);
 ===》xx是 void fun(int) 类型函数的函数指针
 ===》typedef void(*xx)(int)   sighandler_t; ///错误
  typedef int   myint;

 ===>sighandler_t signal(int signum, sighandler_t handler);
 ===> signal(int sig, sighandler_t fun);
 ===> signal(int sig, xxx fun);
 ===>fun 有三个宏表示:SIG_DFL 表示默认处理
   SIG_IGN 表示忽略处理
   fun     表示自定义处理

例:自定义处理:让原本5秒后发送的SIGALRM信号改为调用函数handler

#include <signal.h>
#include <stdio.h>
#include <unistd.h>int flag = 0;void handler(int num){flag = 1; 
}
int main()
{signal(SIGALRM, handler);alarm(5);while (1){if (0 == flag){puts("hello");}else{puts("hi");}sleep(1);}return 0;
}

捕获:即捕获kill发送的信号,当你不加signal进行信号捕获,程序因pause暂停后,你直接发18号信号没有作用。

#include <unistd.h>
#include<stdio.h>
#include<signal.h>
void handler(int num)
{}int main()
{signal(SIGCONT,handler);int i = 0;while(1){if(i == 5){pause();}printf("pid:%d\n",getpid());sleep(1);++i;}return 0;
}

SIGUSER1和SIGUSER2是留给用户自定义的信号,后者一般比前者强制一些,两个信号默认是终止程序。

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>void handle1(int num)
{static int i = 0;printf("老爸叫你...\n");i++;if(i ==3){signal(SIGUSR1,SIG_IGN); //忽略信号}}
void handle2(int num)
{static int i = 0;printf("老妈叫你...\n");i++;if(i ==3){signal(SIGUSR2,SIG_DFL); //回复默认信号}}int	main(int argc, char **argv)
{signal(SIGUSR1,handle1);signal(SIGUSR2,handle2);while(1){printf("i'm playing pid:%d\n",getpid());sleep(1);}system("pause");return 0;
}

2、IPC对象通信:消息队列、共享内存(最高效)、信号量集

3、socket通信:网络通信(可实现不同主机间通信)

(1)网络知识扩展
open system interconnect
1、OSI 模型  ===》开放系统互联模型  ==》分为7层:
理想模型  ==》尚未实现
tftp 
b  /etc/passwd 
a  /etc/123


应用层  a.out


表示层  网络安全,加密解密  gzip


会话层  网络断开,连接状态信息,keep-close keep-alive

传输层     tcp可靠传输,延迟大,发送失败会重传,网络开销大。  udp不可靠传输 ,延迟小,但发送出去就一下,不会检测是否发送成功,网络开销小 。 文件tcp    视频,音频udp


网络层ip   NAT   多个交换机连接起来,用户怎么找到对方。

链路层  交换机  把多个设备串在一起。两个人直接网线一连,多个人就需要交换机。 数据的格式化:把物理层二进制数据转化。  帧 校验


物理层      发送二进制数据,无线:信号塔   有线:网线(近距离)、光纤(远距离)

汇总:

应用层:为网络用户提供各种服务,例如电子邮件、文件传输等。
表示层:为不同主机间的通信提供统一的数据表示形式。
会话层:负责信息传输的组织和协调,管理进程会话过程。
传输层:管理网络通信两端的数据传输,提供可靠或不可靠的传输服务。
网络层:负责数据传输的路由选择和网际互连。
数据链路层,负责物理相邻(通过网络介质相连)的主机间的数据传输,主要作用包括物理地址寻址、数据帧封装、差错控制等。该层可分为逻辑链路控制子层(LLC)和介质访问控制子层(MAC)。
物理层,负责把主机中的数据转换成电信号,再通过网络介质(双绞线、光纤、无线信道等)来传输。该层描述了通信设备的机械、电气、功能等特性。

TCP/IP模型  ==》网际互联模型   ==》分为4层:
   实用模型  ===》工业标准
   tcp/ip协议栈
应用层   ====》应用程序
传输层   ====》端口号tcp udp
网络层   ====》IP 地址
接口层   ====》网卡 驱动  1GB

TCP/IP协议族:
www.taobao.com ---> 192.168.0.19
                       dns 域名解析
DHCP动态主机配置协议:自动分配ip  路由器


应用层: HTTP超文本传输  TFTP简单文件传输、FTP文件传输 SNMP网络管理协议  DNS域名解析 ...
传输层: TCP传输控制协议  UDP用户数据报协议   56k猫
网络层: IP互联网协议  ICMP(ping)互联网控制管理协议   RIP最短路径 OSPF最佳路径(时间短)   IGMP组播协议 ...
物理层: ARP地址解析ip找mac(ifconfig网络适配器)   RARP逆向地址解析  ...   

inet          ether

ip--->mac 硬件地址 (不同网络设备物理地址唯一)
arp,,,,
192.160.0.112

(2) A B C D E 类

IP地址 == 网络位 + 主机位
IP地址的分类: 点分十进制126.1.1.1    ipv4  网络设备按int型712934传输
A类: 超大规模性网络
8 8 8 8
1.0.0.0 - 126.255.255.255  后三位主机位
126.1.1.2
255.0.0.0   

 私有:
10.0.0.0 - 10.255.255.255
127.0.0.1


B类: 大中规模型网络
128.0.0.0 - 191.255.255.255  后两位主机
128.2.1.2  128.2.7.2
255.255.0.0
私有:
172.16.0.0 - 172.31.255.255

C类: 中小规模型网络
192.0.0.0 - 223.255.255.255  后一位主机位
255.255.255.0
私有:
192.168.0.0 - 192.168.255.255
静态路由
192.168.0.0
192.168.0.1  网关
192.168.0.255 

D类: 组播和广播
组播224.0.0.0 - 239.255.255.255一对多
广播192.168.0.255 ==  255.255.255.255一对全部
235.1.2.3
192.168.1.0 
192.168.0.1   网关
192.168.1.255 广播 

E类: 实验
240.0.0.0 - 255.255.255.255

  C 类网络:
    ip地址的前三组是网络地址,第四组是主机地址。
二进制的最高位必须是: 110xxxxx开头
十进制表示范围: 192.0.0.0 -223.255.255.255
默认网络掩码:   255.255.255.0
网络个数: 2^24 个 约 209 万个
主机个数: 2^8  个 254 个+2 ===》1 是网关 1是广播
私有地址: 192.168.x.x 局域网地址。

网络编程:

(1)虚拟机网络配置设为桥接

sudo vim  /etc/network/interfaces 有这两句即可


  sudo reboot


ifconfig 看 ens33 ip是198.168.**.**即是桥接
测试:ping www.baidu.com  

netstat  -anp查看网络状态,防止刷屏后面加上|less

unix域套接字,本地进程间通信,类似管道。

(2)网络接口
1、socket  套接字(网络设备文件描述符) ==》BSD socket ==》用于网络通信的一组接口函数。socket api  application interface
2、ip+port 地址+端口===》地址用来识别主机
     端口用来识别应用程序

如下图,因为进程号由系统分配,另一个进程没法看到,所以设置一个端口号和进程绑定,让另一个进程根据端口号识别进程

 port分为TCP port / UDP port  范围都是: 1-65535
  约定1000 以内的端口为系统使用。推荐自己写的进程端口号申请50000以上
http 80   www.baidu.com
3306
telnet 21 
ssh 22

3、网络字节序 ===》大端存储  主机字节序小端存储,,51单片机大端
192.168.0.12
存储时:12.0.168.192

数字转换函数:
#include <arpa/inet.h>
1236234687
主机转网络:uint32_t htonl (uint32_t hostlong);
ipv4 192.168.0.1 1~65535
uint16_t htons(uint16_t hostshort);
网络转主机:host to net 
net to host 
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

字符串转换函数:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

主机转网络:in_addr_t inet_addr(const char *cp);
inet_addr("192.168.1.20");
cli.sin_addr
网络转主机:char *inet_ntoa(struct in_addr in);

client, server 
browser
b/s  http   
p2p   peer   

1、模式  C/S 模式  ==》服务器/客户端模型

server:socket()-->bind()--->listen()-->accept()-->recv()-->close()
client:socket()-->connect()-->send()-->close();
int on = 1;
setsockopt(listfd, SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

利用套接字实现不同主机数据传输

服务器端:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
功能:程序向内核提出创建一个基于内存的套接字描述符

参数:domain  地址族,PF_INET == AF_INET ==>互联网程序
  PF_UNIX == AF_UNIX ==>单机程序
  type    套接字类型:
     SOCK_STREAM  流式套接字 ===》TCP   
  SOCK_DGRAM   用户数据报套接字===>UDP
  SOCK_RAW     原始套接字  ===》IP
  protocol 协议 ==》0 表示自动适应应用层协议。
返回值:成功 返回申请的套接字id
失败  -1;

man socket

int bind(int sockfd, struct sockaddr *my_addr, 
             socklen_t addrlen);
功能:如果该函数在服务器端调用,则表示将参数1相关
  的文件描述符文件与参数2 指定的接口地址关联,
  用于从该接口接受数据。

  如果该函数在客户端调用,则表示要将数据从
  参数1所在的描述符中取出并从参数2所在的接口
  设备上发送出去。

  注意:如果是客户端,则该函数可以省略,由默认
        接口发送数据。
参数:sockfd 之前通过socket函数创建的文件描述符,套接字id
  my_addr 是物理接口的结构体指针。表示该接口的信息。

  struct sockaddr      通用地址结构
  {
  u_short sa_family;  地址族
  char sa_data[14];   地址信息
  };

  转换成网络地址结构如下:
  struct _sockaddr_in    ///网络地址结构
  {
  u_short    sin_family; 地址族
  u_short    sin_port;   ///地址端口
  struct in_addr  sin_addr;   ///地址IP  ifconfig
  char    sin_zero[8]; 占位
  };

  struct in_addr
  {
  in_addr_t s_addr;
  }

  socklen_t addrlen: 参数2 的长度。
返回值:成功 0
失败  -1;

 man 7 ip

3、  int listen(int sockfd, int backlog);
    功能:在参数1所在的套接字id上监听等待链接。
参数:sockfd  套接字id
  backlog 允许链接的个数。
返回值:成功  0
失败  -1;


4、int accept(int sockfd, struct sockaddr *addr, 
socklen_t *addrlen);
   功能:从已经监听到的队列中取出有效的客户端链接并
     接入到当前程序。
   参数:sockfd 套接字id
         addr  如果该值为NULL ,表示不论客户端是谁都接入。
     如果要获取客户端信息,则事先定义变量
   并传入变量地址,函数执行完毕将会将客户端
   信息存储到该变量中。
 addrlen: 参数2的长度,如果参数2为NULL,则该值
  也为NULL;
 如果参数不是NULL,&len;
  一定要写成len = sizeof(struct sockaddr);
   返回值:成功 返回一个用于通信的新套接字id;
    从该代码之后所有通信都基于该id
-1失败

5、接受函数:/发送函数:


read()/write ()   ///通用文件读写,可以操作套接字。
recv(,0) /send(,0)      ///TCP 常用套机字读写
recvfrom()/sendto() ///UDP 常用套接字读写


ssize_t recv(int sockfd, void *buf, size_t len,
             int flags);
功能:从指定的sockfd套接字中以flags方式获取长度
  为len字节的数据到指定的buff内存中。
参数:sockfd  
如果服务器则是accept的返回值的新fd
如果客户端则是socket的返回值旧fd
  buff 用来存储数据的本地内存,一般是数组或者
  动态内存。
  len 要获取的数据长度
  flags 获取数据的方式,0 表示阻塞接受。

返回值:成功 表示接受的数据长度,一般小于等于len
失败  -1;

6、close()  ===>关闭指定的套接字id;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */ 
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{int sockfd  = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");return 1;}struct sockaddr_in ser,cli;//client  man 7 ip bzero(&ser,sizeof(ser));bzero(&cli,sizeof(cli));ser.sin_family  = AF_INET;ser.sin_port = htons(50000); // host to net short ser.sin_addr.s_addr = inet_addr("192.168.116.130");// 小端转大端int ret = bind(sockfd,(SA)&ser,sizeof(ser));if(-1 == ret){perror("bind");return 1;}socklen_t len = sizeof(cli);while(1){char buf[256]={0};recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);time_t tm;time(&tm);struct tm * tminfo = localtime(&tm);sprintf(buf,"%s %d:%d:%d\n",buf,tminfo->tm_hour,tminfo->tm_min,tminfo->tm_sec);sendto(sockfd,buf,strlen(buf),0,(SA)&cli,sizeof(cli));}return 0;
}

客户端:

1、int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);
   功能:该函数固定有客户端使用,表示从当前主机向目标
     主机发起链接请求。
   参数:sockfd 本地socket创建的套接子id
         addr 远程目标主机的地址信息。
 addrlen: 参数2的长度。
   返回值:成功 0
       失败 -1;

2、int send(int sockfd, const void *msg, 
size_t len, int flags);
   功能:从msg所在的内存中获取长度为len的数据以flags
     方式写入到sockfd对应的套接字中。

   参数:sockfd:
    如果是服务器则是accept的返回值新fd
如果是客户端则是sockfd的返回值旧fd

 msg 要发送的消息
 len 要发送的消息长度
 flags 消息的发送方式。

  返回值:成功  发送的字符长度
     失败  -1;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */ 
#include <arpa/inet.h>
#include <time.h>
#include <unistd.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{int sockfd  = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");return 1;}struct sockaddr_in ser,cli;//client  man 7 ip bzero(&ser,sizeof(ser));bzero(&cli,sizeof(cli));ser.sin_family  = AF_INET;ser.sin_port = htons(50000); // host to net short ser.sin_addr.s_addr = inet_addr("192.168.116.130");// 小端转大端socklen_t len = sizeof(cli);while(1){char buf[256]={0};strcpy(buf,"this is udp test");sendto(sockfd,buf,strlen(buf),0,(SA)&ser,sizeof(ser));bzero(buf,sizeof(buf));recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);printf("server:%s",buf);sleep(1);}close(sockfd);return 0;
}

(1)网络编程之 UDP  用户数据报

1、特性: 无链接(如下图,中间小圈是多个网络节点,发完信息路线就消失,下次可能又是另一条)  不可靠(某个网络节点可能因为数据累计量满了,导致发的信息丢失,丢包。发的一次发五个数据报,发太快,还没收完,最后收到的数据包可能就是后三个报)  大数据   

数据报特性:假设数据收三位,那么只会收到hel,然后包就丢了,之间收下一个,如果收前五位,则全部收完,并且包之间有间隔,不会出现粘连。

2、框架: C/S模式 

   server:socket() ===>bind()===>recvfrom()===>close()
   client:socket() ===>bind()===>sendto() ===>close()


注意:socket()的参数需要调整。

      socket(PF_INET,SOCK_DGRAM,0);

  bind() 客户端是可选的,服务器端是比选的。

发送接收函数:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
         const struct sockaddr *dest_addr, socklen_t addrlen);

功能:用于UDP协议中向对方发送数据。
参数:sockfd  本地的套接字id
  buff    本地的数据存储,一般是要发送的数据。
  len     要发送的数据长度
  flags   要发送数据方式,0 表示阻塞发送。

  dest_addr: 必选,表示要发送到的目标主机信息结构体。
  addrlen :目标地址长度。

返回值:成功  发送的数据长度
    失败   -1;


ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                  struct sockaddr *src_addr, socklen_t *addrlen);

功能:用于UDP协议中获取对方发送的数据。
参数:sockfd 本地的套接字id
  buff   要存储数据的内存区,一般是数组或者动态内存。
  len    要获取的数据长度,一般是buff的大小。
  flags  获取方式,0 阻塞

  src_addr 可选,表示对方的地址信息结构体,
   如果为NULL,表示不关心对方地址。
  addrlen  对方地址信息结构体大小。
   如果对方地址是NULL,则该值也为NULL。
返回值:成功 接收到的数据长度
失败  -1;

(2)tcp传输控制

服务器/客户端模型分类:c/s        b/s         p2p

c客户端是专用、b客户端是通用;   b采用http协议、c采用标准协议(tcp、udp等)或自定义协议

cs可以设计的复杂些单机、bs相对简单在线游戏(受到http框架限制);        cs单机版资源是离线的来自本地,跟服务器交互快,bs所有资源由服务端发送

ptp在下载软件会用到,比如下图客户端从服务器下载文件,当有多客户时,已经下载的也会帮助未下载的提速。

模式  C/S 模式  ==》服务器/客户端模型   tcp

有链接:发送完数据路线被保留  可靠:发送完数据有应答是否发送成功,失败再发一次。

三次握手,四次挥手。

客户端发送SYN请求建立连接,服务端发送SYN接受,一端随机发个数1000,另一端接收成功就会应答ACK随机数加一1001,另一端再回复个随机数加一,这就是一次三次握手。

客户端发送fin表示断开,另一端回应ack+1,再发送断开信号fin,另一端应答ack+1。

这就是一次四次挥手。

udp数据形式是数据报,半双工

tcp数据是流式套接字,全双工

流式套接字数据特点是连续、有顺序、无边界,如果接收方一次收3个,hel收了,el不会丢,下次接着收,反之一次收100,则三个单词一次全收了

应用层利用tcp编程

调用connect函数和accept函数,底层硬件自动完成三次握手,

调用close底层就会四次挥手。

listen排队数应该少一些,因为一旦进入排队序列,就干不了其他的事了。

利用listen监听三次握手,客户端跟服务器链接成功后,客户端就被分配一个conn通信套接字然后收发,防止多客户端数据重叠。

send发送太快时,最多累计64k数据,当满了返回0。

关闭监听,就没有客户端可以访问了。

服务端:

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <time.h>
#include <unistd.h>
typedef struct sockaddr*(SA);
typedef struct
{char filename[256];char buf[1024];int buf_len;int total_len;
} PACK;
int main(int argc, char** argv)
{//监听套接字int listfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == listfd){perror("socket");return 1;}// man 7 ipstruct sockaddr_in ser, cli;bzero(&ser, sizeof(ser));bzero(&cli, sizeof(cli));ser.sin_family = AF_INET;ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("127.0.0.1");int ret = bind(listfd, (SA)&ser, sizeof(ser));if (-1 == ret){perror("bind");return 1;}// 三次握手的排队数  ,listen(listfd, 3);socklen_t len = sizeof(cli);//通信套接字int conn = accept(listfd, (SA)&cli, &len);if (-1 == conn){perror("accept");return 1;}int first_flag = 0;int fd = -1;int current_size = 0;int total_size = 0;while (1){PACK pack;bzero(&pack, sizeof(pack));ret = recv(conn, &pack, sizeof(pack), 0);if (0 == first_flag){first_flag = 1;fd = open(pack.filename, O_WRONLY | O_TRUNC | O_CREAT, 0666);if (-1 == fd){perror("open");return 1;}total_size = pack.total_len;}if (0 == pack.buf_len){break;}write(fd, pack.buf, pack.buf_len);current_size += pack.buf_len;printf("%d/%d\n", current_size, total_size);bzero(&pack, sizeof(pack));strcpy(pack.buf,"go on");// send(conn,&pack,sizeof(pack),0);}close(conn);close(listfd);close(fd);// system("pause");return 0;
}

客户端:

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h> /* See NOTES */
#include <sys/types.h>
#include <time.h>
#include <unistd.h>typedef struct sockaddr*(SA);
typedef struct
{char filename[256];char buf[1024];int buf_len;int total_len;
} PACK;
int main(int argc, char** argv)
{int conn = socket(AF_INET, SOCK_STREAM, 0);if (-1 == conn){perror("socket");return 1;}// man 7 ipstruct sockaddr_in ser, cli;bzero(&ser, sizeof(ser));bzero(&cli, sizeof(cli));ser.sin_family = AF_INET;ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("127.0.0.1");int ret = connect(conn, (SA)&ser, sizeof(ser));if (-1 == ret){perror("connect");return 1;}PACK pack;strcpy(pack.filename, "1.png");  // /home/linux/1.pngstruct stat st;ret = stat("/home/linux/1.png", &st);if (-1 == ret){perror("stat");return 1;}pack.total_len = st.st_size;  // total sizeint fd = open("/home/linux/1.png", O_RDONLY);if (-1 == fd){perror("open");return 1;}while (1){pack.buf_len = read(fd, pack.buf, sizeof(pack.buf));send(conn, &pack, sizeof(pack), 0);if (pack.buf_len <= 0){break;}bzero(&pack,sizeof(pack));//recv(conn,&pack,sizeof(pack),0);usleep(1000*10); //10ms}close(conn);close(fd);// system("pause");return 0;
}

tcp粘包问题:由于双方没有约定,导致发送的包没法拆开。比如下方客户端发送了三个信息名字年龄身高,服务端一次性收完了,导致没法区分,数据没边界。

粘包解决:加分隔。   固定大小 如struct结构体。    自定义协议。

为防止写太快导致数据写不完,可以加个recv、send阻塞,或者sleep

tcp文件复制:

服务端

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <time.h>
#include <unistd.h>
typedef struct sockaddr*(SA);
typedef struct
{char filename[256];char buf[1024];int buf_len;int total_len;
} PACK;
int main(int argc, char** argv)
{//监听套接字int listfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == listfd){perror("socket");return 1;}// man 7 ipstruct sockaddr_in ser, cli;bzero(&ser, sizeof(ser));bzero(&cli, sizeof(cli));ser.sin_family = AF_INET;ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("127.0.0.1");int ret = bind(listfd, (SA)&ser, sizeof(ser));if (-1 == ret){perror("bind");return 1;}// 三次握手的排队数  ,listen(listfd, 3);socklen_t len = sizeof(cli);//通信套接字int conn = accept(listfd, (SA)&cli, &len);if (-1 == conn){perror("accept");return 1;}int first_flag = 0;int fd = -1;int current_size = 0;int total_size = 0;while (1){PACK pack;bzero(&pack, sizeof(pack));ret = recv(conn, &pack, sizeof(pack), 0);if (0 == first_flag){first_flag = 1;fd = open(pack.filename, O_WRONLY | O_TRUNC | O_CREAT, 0666);if (-1 == fd){perror("open");return 1;}total_size = pack.total_len;}if (0 == pack.buf_len){break;}write(fd, pack.buf, pack.buf_len);current_size += pack.buf_len;printf("%d/%d\n", current_size, total_size);bzero(&pack, sizeof(pack));strcpy(pack.buf,"go on");// send(conn,&pack,sizeof(pack),0);}close(conn);close(listfd);close(fd);// system("pause");return 0;
}

客户端:

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h> /* See NOTES */
#include <sys/types.h>
#include <time.h>
#include <unistd.h>typedef struct sockaddr*(SA);
typedef struct
{char filename[256];char buf[1024];int buf_len;int total_len;
} PACK;
int main(int argc, char** argv)
{int conn = socket(AF_INET, SOCK_STREAM, 0);if (-1 == conn){perror("socket");return 1;}// man 7 ipstruct sockaddr_in ser, cli;bzero(&ser, sizeof(ser));bzero(&cli, sizeof(cli));ser.sin_family = AF_INET;ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("127.0.0.1");int ret = connect(conn, (SA)&ser, sizeof(ser));if (-1 == ret){perror("connect");return 1;}PACK pack;strcpy(pack.filename, "1.png");  // /home/linux/1.pngstruct stat st;ret = stat("/home/linux/1.png", &st);if (-1 == ret){perror("stat");return 1;}pack.total_len = st.st_size;  // total sizeint fd = open("/home/linux/1.png", O_RDONLY);if (-1 == fd){perror("open");return 1;}while (1){pack.buf_len = read(fd, pack.buf, sizeof(pack.buf));send(conn, &pack, sizeof(pack), 0);if (pack.buf_len <= 0){break;}bzero(&pack,sizeof(pack));//recv(conn,&pack,sizeof(pack),0);usleep(1000*10); //10ms}close(conn);close(fd);// system("pause");return 0;
}

小工具:

1、telnet  远程登录工具,默认都是系统安装。

先关闭乌班图防火墙sudo ufw disable

inactive表示关闭

sudo apt-get install openssh-server openssh-client 安装
网络需要桥接。

ssh2 
使用格式: telnet ip地址  端口
   eg: telnet 192.168.1.1  8888
注意:如果没有写端口,则默认登录23 号端口。

2、arp 地址解析命令
列出与本机通信的设备ip地址及对应mac
arp -an  ===>列出当前主机的地址ARP表
arp -d  ip地址

3、netstat  测试查看网络端口使用情况
netstat -anp 
netstat -n  ===>列出当前所有网络端口使用情况
netstat -n -t  ===>列出所有TCP通信的端口信息
netstat -n -u  ===>列出所有UDP通信的端口信息

netstat -n -i   ===>列出默认接口上的通信信息
netstat -lnp |grep 8888  ===>查看指定端口上的通信详情

4、ping 命令  测试网路的联通状况

ping ip地址
ping 域名

5、wireshark

先细说一下tcpip协议栈(四层模型)数据传输过程:

如图,数据传输不是只有数据,还要跟MAC   IP   TCP  再加上数据组成分包或者一帧,然后传送。mac来自接口、ip来自网络、tcp来自传输、数据来自应用。数据到了服务端除了要对结构还有校验码。

udp一样。

这个过程在linux系统直接由系统进行拆包封包,如果在stm32就得自己编程实现。

上图就是协议头,分析一下:

以太V2MAC帧:

对方mac地址即目的地址,自己mac地址源地址各6字节。

类型:即后面这一堆的类型,这里是IP数据报。

ip数据报:

FCS:校验码。

ip头:

大小:20字节

一行4字节,共五行。

version字段:版本号ipv4、ipv6.   ,头长度:20

TOS:空的   

total length :后两个头长度  

identification:编号

ip flag:F D  M:

分片就是能不能打开。

     TTL:生命周期,每次往下一级走,ttl--,当为零时,就不走了

protocol:协议:tcp、udp

header:校验

源地址,目的地址

ip选项:可有可无

tcp头:

端口号

起始编号交互随机数

应答数据

选项大小

tcp flag:

窗口:缓冲区大小

check:校验码

urgent:紧急数据放哪

udp头:

大小:八字节

源端口,目的端口、长度(应用层长度hello),校验码

wireshark:

sudo apt-get install wireshark安装

抓包工具   tcp.port == 50000 && tcp.ip  == 192.168.0.183
sudo wireshark  ==>可视化界面

 

选网络设备:

看本地:loopback

看京东等网站:ens33

不知到:any

最上面:一包一包数据,source发送,    协议:icmp   length长度   info信息描述

中间:先是总帧,然后四层:MAC    ip   udp     应用层

然后是网卡发送的数据

tcpdump 
抓包过滤规则:
1、根据ip地址过滤:ip.src == x.x.x.x
   ip.dst == x.x.x.x
2、根据端口过滤:tcp.srcport == xx;
 tcp.dstport == xx;
 udp.srcport == xx;
 udp.dstport == xx;
3、根据协议类型过滤:
tcp   udp  icmp .....
4、任意组合以上条件抓包:
如果与的关系:  and
ip.src == 192.168.1.100 and tcp.dstport == 9999
如果或关系 :   or
ip.src == 192.168.1.100 or ip.dst == 192.168.1.102

tcp host 192.168.1.100

运行前面写的网络数据交换:

前三行就是一次三次握手。

这就跟前面介绍的头对应

网络编程出现问题,不知道在哪里,就可以用这个小工具查找。

还可以用命令行查看:

sudo tcpdump -n -i eth0 -xx src or dst www.taobao.com -AXX -vv|less 

tcpdump  ==》命令行 ===>www.tcpdump.com

1、tcpdump -n ===>在默认的网卡上开始抓包。
2、根据ip过滤: tcpdump -n  src  x.x.x.x  
    tcpdump -n  dst x.x.x.x
  抓192.168.0.130上面发出和接受到的数据包
  sudo tcpdump -n -x src or dst  192.168.0.130 
3、查看包中的内容:
tcpdump -n -x src x.x.x.x
tcpdump -n -x dst x.x.x.x

tcpdump -n -x src x.x.x.x  >xxx.log

4、根据端口过滤:
tcpdump -n src port xx
tcpdump -n dst port xx
tcpdump -n -p tcp port xx
tcpdump -n udp port xx
tcpdump -n port xx
5、根据协议过滤:
    tcpdump -n -p icmp/tcp/udp

6、根据指定接口过滤:
tcpdump -n -i eth0
tcpdump -n -i lo

7、根据以上各种条件组合抓包:
与关系:  and
或关系:  or

(3)mqtt协议      应用层

消息队列遥测传输协议,常用于物联网设备,低开销、低带宽。心脏起搏器

发布者(客户)上传数据到代理暂存部分(服务端),订阅者根据主题订阅需要的数据,由代理传给它。所以上传者上传数据需要附带数据主题。如下图,温度数据附带主题temp

服务质量qos:0,1,2

0:发布者发个数据,服务就结束了。

1:发布数据对方会应答,订阅者接受后也会给发布者应答。

2:会告诉发布者数据暂存了,还会告诉谁订阅了。

mqtt协议应用层报文三部分:

固定头2字节

可变头(根据固定头不同有改变):建立连接、连接应答、发布主题、发布主题应答、订阅主题、订阅主题应答、取消订阅、取消应答。    可能有心跳包、心跳应答:长时间没有数据交互,就会发送心跳包,检测链接是否断开。比如游戏长时间连线无响应,就会发心跳包查看是否断线。

负载payload:有数据发送就有负载,不是必要的。

编程需要随时查:

实操应用:需要用到物联网服务器:比如onenet云平台、华为云等。

可以查看快速入门了解使用方法。

编程前需要下载mqtt库,网上有开源库

还需要

下载好库之后步骤:

解压

1 .PC版本的编译步骤
    1. openssl
        进入openssl源码目录
        ./config enable-shared -fPIC #加入-fPIC 选项,不然,编译paho会出问题。
        make #这个过程需要等待下
        sudo make install 
    2.paho 的编译
        修改makefile
        1.CC ?=gcc             122line
        2.133 line 加入这两个选项
        CFLAGS += -I /usr/local/ssl/include
        LDFLAGS += -L /usr/local/ssl/lib  
        3.192line  注意-I,-L 后面填写ssl库的头文件目录和库文件目录 我这里是/usr/local/ssl/include 和 
        /usr/local/ssl/lib,如果你的不是,需要切换下
        CCFLAGS_SO += -Wno-deprecated-declarations -DOSX -I /usr/local/ssl/include
        LDFLAGS_C += -Wl,-install_name,lib$(MQTTLIB_C).so.${MAJOR_VERSION}
        LDFLAGS_CS += -Wl,-install_name,lib$(MQTTLIB_CS).so.${MAJOR_VERSION} -L /usr/local/ssl/lib
        LDFLAGS_A += -Wl,-install_name,lib${MQTTLIB_A}.so.${MAJOR_VERSION}
        LDFLAGS_AS += -Wl,-install_name,lib${MQTTLIB_AS}.so.${MAJOR_VERSION} -L /usr/local/ssl/lib
        FLAGS_EXE += -DOSX
        FLAGS_EXES += -L /usr/local/ssl/lib

        4.make 

        quesion:
        /usr/bin/ld: /usr/local/ssl/lib/libcrypto.a(x86_64cpuid.o): relocation R_X86_64_PC32 against symbol `OPENSSL_cpuid_setup' can not be used when making a shared object; recompile with -fPIC
        初步分析,ssl编译的有问题,需要在编译的时候添加 ./config enable-shared

         /tmp/ccM0lsiB.o: relocation R_X86_64_PC32 against symbol `stdout@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
        检查paho库makefile 中是否加入-fPIC编译选项。
         本makefile line:157 CCFLAGS_SO = -g  -fPIC

         5. sudo make install   /usr/local/lib /usr/local/include 
         最后:
         install: cannot stat 'build/output/doc/MQTTClient/man/man3/MQTTClient.h.3': No such file or directory
         Makefile:275: recipe for target 'install' failed
         make: [install] Error 1 (ignored)
         install -m 644 build/output/doc/MQTTAsync/man/man3/MQTTAsync.h.3 /usr/local/share/man/man3
         install: cannot stat 'build/output/doc/MQTTAsync/man/man3/MQTTAsync.h.3': No such file or directory
         Makefile:275: recipe for target 'install' faile
        
         报这个错不用理会。到此就完成了编译。
         pc 版mqtt库的安装。


2.arm版本:   
        arm-linux-gcc 配置
        1.先按照之前方法配置
        apt-get install lib32ncurses5 lib32z1(由于是64位系统,
        arm-linux-gcc 是32位,需要安装这个库)
    openssl arm 版本编译
    1. 
    ./config no-asm shared --prefix=$(pwd)/__install
     no-asm: 是在交叉编译过程中不使用汇编代码代码加速编译过程,原因是它的汇编代码是对arm格式不支持的。
    shared :生成动态连接库。
    –prefix :指定make install后生成目录的路径,不修改此项则默认为OPENSSLDIR目录(/usr/local/ssl)
    2.修改makefile
    1) CC= gcc 改成 CC = arm-linux-gcc;(根据你自己的交叉编译环境设置,我的交叉编译环境是:arm-none-linux-gnueabi-)
    2) 删除 CFLAG= 中的 “-march=pentium”;(如果有的话)

    3) AR=ar $(ARFLAGS) r 改为 AR=arm-none-linux-gnueabi-ar $(ARFLAGS) r;

    4) ARD=ar $(ARFLAGS) d 改为 ARD=arm-none-linux-gnueabi-ar $(ARFLAGS) d;(如果有的话)

    5)RANLIB= /usr/bin/ranlib 改为 RANLIB= arm-none-linux-gnueabi-ranlib;
    3.m akefile 中去掉
    153 63 -m64 去掉

    4. make
    5. make install
    6.生成的arm库子当前目录下__install

    2. arm paho
    https://blog.csdn.net/ltc844139730/article/details/52553086?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~aggregatepage~first_rank_v2~rank_aggregation-9-52553086.pc_agg_rank_aggregation&utm_term=arm%E7%A7%BB%E6%A4%8Dmqtt&spm=1000.2123.3001.4430
        1.进入paho目录
        2.  修改makefile 24行
        .PHONY: clean, mkdir, install, uninstall, html,在这行的下面加入2行

        INCLUDES = -I/home/linux/code_test/mqtt_src/openssl-1.0.0s/__install/include
        LIBSDIR  = -L/home/linux/code_test/mqtt_src/openssl-1.0.0s/__install/lib
        3.
        然后到文件的第181行,在${CC} 后面加上$(INCLUDES),在最后加上$(LIBSDIR),生成的时候需要库,要把库的路径添加进去。

        ${CC} $(INCLUDES) -g -o $@ $< -l${MQTTLIB_CS} ${FLAGS_EXES} $(LIBSDIR)
        在第187行,203行,215行做同样处理。最好和我用一样的版本,一个是这个位置不对,另一个是我之前用的1.10版本的,这样子做没有效果。
        需要多行加入。行号稍微有点差异。
    3. make CC=arm-linux-gcc



    问题
    报错:build/output/libpaho-mqtt3a.so: undefined reference to `clock_gettime'
    修改makefile 最后 加入-lrt
    FLAGS_EXES = $(LDFLAGS) -I ${srcdir} ${START_GROUP} -lpthread -lssl -lcrypto -lrt ${END_GROUP} -L ${blddir}  
    
    最后,bulid/output 下面就是 arm 版本的库了.

    在paho 目录下面有两个makefile ,后缀为arm,是arm版本的makefile 生成arm版本的库。
    后缀名为PC的是生成PC版本的库,可以根据情况进行改名。
    cp Makefile_arm makefile 
    cp Makefile_PC makefile

在云平台的设备需要进行密钥转化,利用小工具token

然后就可以编程了:

head.h是开源库提供的,只需要根据创建的设备修改,其中password就是用token生成

#ifndef HEAD_H
#define HEAD_H#include <MQTTAsync.h>
#include <MQTTClient.h>#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>#include <string.h>
//#define OLD_ADDRESS     "tcp://218.201.45.7:1883"
#define NEW_ADDRESS     "tcp://183.230.40.96:1883"
#define DEV_NAME    "first_device"
#define CLIENTID DEV_NAME
#define PRODUCT_ID "3XoV8495r6"
#define PASSWD "version=2018-10-31&res=products%2F3XoV8495r6%2Fdevices%2Ffirst_device&et=1837255523&method=sha1&sign=dtVQufcgwCrn5zmdmWLXFEtJAQY%3D";
#define QOS         0
#define TIMEOUT     10000L
//#define __cplusplus
#endif // HEAD_H

main.c只需要修改主函数,把temp换成自己在云平台创建的产品属性

#include <stdio.h>
#include "head.h"static char topic[2][200] = {0};
static MQTTClient client;
static int id =10000;
volatile static MQTTClient_deliveryToken deliveredtoken;void pack_topic(char * dev_name, char * pro_id)
{sprintf(topic[0], "$sys/%s/%s/thing/property/post/reply", pro_id, dev_name);	//订阅sprintf(topic[1], "$sys/%s/%s/thing/property/post", pro_id, dev_name);			//发布
}// 发送成功后callback
void delivered(void *context, MQTTClient_deliveryToken dt)
{printf("Message with token value %d delivery confirmed\n", dt);deliveredtoken = dt;
}
// 接收到消息的callback
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{int i;char* payloadptr;printf("Message arrived\n");printf("     topic: %s\n", topicName);printf("   message: ");payloadptr = (char*)message->payload;for(i=0; i<message->payloadlen; i++){putchar(*payloadptr++);}putchar('\n');MQTTClient_freeMessage(&message);MQTTClient_free(topicName);return 1;
}
// 掉线后的callback
void connlost(void *context, char *cause)
{printf("\nConnection lost\n");printf("     cause: %s\n", cause);
}int mqtt_init()
{pack_topic(DEV_NAME, PRODUCT_ID);int rc = MQTTClient_create(&client, NEW_ADDRESS, CLIENTID,MQTTCLIENT_PERSISTENCE_NONE, NULL);int ch;if(MQTTCLIENT_SUCCESS != rc){printf("create mqtt client failre...\n");exit(1);}MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;conn_opts.keepAliveInterval = 20;conn_opts.cleansession = 1;conn_opts.username=PRODUCT_ID;conn_opts.password=PASSWD;rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered);if(MQTTCLIENT_SUCCESS !=rc ){printf("Failed to connect, return code %d\n", rc);exit(EXIT_FAILURE);}if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS){printf("Failed to connect, return code %d\n", rc);exit(EXIT_FAILURE);}#if 0//订阅单个主题  如果需要订阅的话    MQTTClient_subscribe(client, topic[0], QOS);printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n", topic[0], CLIENTID, QOS);
#endif return rc;
}int mqtt_send(char * key, int value)
{MQTTClient_deliveryToken deliveryToken;MQTTClient_message test2_pubmsg = MQTTClient_message_initializer;// 需要发送的正文char message[1024]={0};test2_pubmsg.qos = QOS;test2_pubmsg.retained = 0;test2_pubmsg.payload =message;sprintf(message,"{\"id\":\"%d\",\"version\":\"1.0\",\"params\":{\"%s\":{\"value\":%d}}}",id++, key, value);test2_pubmsg.payloadlen = strlen(message);printf("%s\n",message);int rc = MQTTClient_publishMessage(client,topic[1],&test2_pubmsg,&deliveryToken);if(MQTTCLIENT_SUCCESS != rc){printf("client to publish failure.. %lu\n",pthread_self());exit(1);}printf("Waiting for up to %d seconds for publication on topic %s for client with ClientID: %s\n",(int)(TIMEOUT/1000), topic[0], CLIENTID);MQTTClient_waitForCompletion(client,deliveryToken,TIMEOUT);sleep(1);return rc;
}void mqtt_deinit()
{MQTTClient_disconnect(client, 10000);MQTTClient_destroy(&client);
}int main(void)
{mqtt_init();while(1){mqtt_send("tmp", 20);}mqtt_deinit();return 0;
}

这个在云平台手册查看。

其他东西在属性查看

wireshark抓包

(4)IO多路复用

        *1、定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力。

        *2、作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标的输入、中断信号等等事件,再比如web服务器如nginx,需要同时处理来来自N个客户端的事件。

        *逻辑控制流在时间上的重叠叫做 并发

        *3、而CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系统内部的调度,来做到对CPU处理的时分复用。这样多个事件流就可以并发进行,不需要一个等待另一个太久,在用户看起来他们似乎就是并行在做一样。

        *4、使用并发处理的成本:
        线程/进程创建成本
        CPU切换不同线程/进程成本 Context Switch,上下文切换  页表、寄存器、缓存
        多线程的资源竞争

        *5、有没有一种可以在单线程/进程中处理多个事件流的方法呢?一种答案就是IO多路复用。
因此IO多路复用解决的本质问题是在用更少的资源完成更多的事。

多路复用如下,中间一条河,上下各三条车道阻塞,中间的车道可以左右移动让车通过。

(2)IO模型

用有名管道来演示下列模型:
        *1、阻塞IO 

写端

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>#include <fcntl.h>
#include <unistd.h>int main()
{int ret = mkfifo("fifo", 0666);if(-1 == ret){if(EEXIST != errno){perror("mkfifo");return -1;}}int fd = open("fifo",O_WRONLY);if(fd < 0){perror("open");return -1;}while(1){write(fd, "hello", 5);sleep(1);}close(fd);
}

读端

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int ret = mkfifo("fifo", 0666);if (-1 == ret){if (EEXIST != errno){perror("mkfifo");return -1;}}int fd = open("fifo", O_RDONLY);if (fd < 0){perror("open");return -1;}while (1){char buf[1024] = {0};read(fd, buf, 1024);puts(buf);fgets(buf, 1024, stdin);puts(buf);}close(fd);remove("fifo");
}


        *2、非阻塞IO  EAGAIN  忙等待 errno(会导致cpu占有率100%左右)

        把打开的文件模式换成非阻塞:fcntl函数:

非阻塞IO ===》在阻塞IO的基础上调整其为不再阻塞等待。
 在程序执行阶段调整文件的执行方式为非阻塞:
===》fcntl() ===>动态调整文件的阻塞属性

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改指定文件的属性信息。
参数:fd 要调整的文件描述符
  cmd 要调整的文件属性宏名称
  ... 可变长的属性值参数。
返回值:成功  不一定,看cmd
失败  -1;

eg:修改文件的非阻塞属性:
int flag ;
flag  = fcntl(fd,F_GETFL,0);  ///获取fd文件的默认属性到flag变量中。
flag  = flag | O_NONBLOCK;    ///将变量的值调整并添加非阻塞属性
fcntl(fd,F_SETFL,flag);       ///将新属性flag设置到fd对应的文件生效。

以上代码执行后的阻塞IO将变成非阻塞方式

例:写端:

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int ret = mkfifo("fifo", 0666);if(-1 == ret){if(EEXIST != errno){perror("mkfifo");return -1;}}int fd = open("fifo",O_WRONLY);if(fd < 0){perror("open");return -1;}while(1){write(fd, "hello", 5);sleep(3);}close(fd);
}

读端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int ret = mkfifo("fifo", 0666);if (-1 == ret){if (EEXIST != errno){perror("mkfifo");return -1;}}int fd = open("fifo", O_RDONLY);if (fd < 0){perror("open");return -1;}//获取设备原来的状态标志未int flag = fcntl(fd, F_GETFL);//在原来的状态标志位基础上加上 非阻塞fcntl(fd, F_SETFL, flag | O_NONBLOCK);flag = fcntl(fileno(stdin), F_GETFL);fcntl(0, F_SETFL, flag | O_NONBLOCK);while (1){char buf[1024] = {0};int ret = read(fd, buf, 1024);if (ret > 0){puts(buf);}char* ret1 = fgets(buf, 1024, stdin);if (ret1 != NULL){puts(buf);}}close(fd);remove("fifo");
}

        *3、信号驱动IO  SIGIO 用的相对少(了解)

文件描述符需要追加 O_ASYNC 标志。
设备有io事件可以执行时,内核发送SIGIO信号。

1.追加标志
int flag ;
flag  = fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flag | O_ASYNC);    
2.设置信号接收者
fcntl(fd,F_SETOWN,getpid());//常用设置
3.对信号进行捕获
signal(SIGIO,myhandle);//

写端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int ret = mkfifo("fifo", 0666);if (-1 == ret){if (EEXIST != errno){perror("mkfifo");return -1;}}int fd = open("fifo", O_WRONLY);if (fd < 0){perror("open");return -1;}while (1){write(fd, "hello", 5);sleep(3);}close(fd);
}

读端

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>int fd;
void handle(int num)
{char buf[128] = {0};read(fd, buf, sizeof(buf));printf("fifo:%s\n", buf);
}
int main(int argc, char **argv)
{int ret = mkfifo("fifo", 0666);if (-1 == ret){if (EEXIST != errno){perror("mkfifo");return 1;}}signal(SIGIO, handle);fd = open("fifo", O_RDONLY);if (-1 == fd){perror("open fifo");return 1;}int flag = fcntl(fd, F_GETFL);//信号通知  信号驱动iofcntl(fd, F_SETFL, flag | O_ASYNC);//设在接收sigio的进程fcntl(fd, F_SETOWN, getpid());while (1){char buf[128] = {0};fgets(buf, sizeof(buf), stdin);printf("terminal:%s", buf);}close(fd);// remove("fifo");// system("pause");return 0;
}


         *4、并行模型 进程,线程


          *5、   IO多路复用  select、poll、epoll

        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);
功能:完成指定描述符集合(数组)中有效描述符的动态检测。
  该函数具有阻塞等待功能,在函数执行完毕后
  目标测试集合中将只保留最后有数据的描述符。

参数:nfds 描述符的上限值,一般是链接后描述符的最大值+1;
  readfds 只读描述符集
  writefds 只写描述符集
  exceptfds 异常描述符集
  以上三个参数都是 fd_set * 的描述符集合类型
  timeout  检测超时 如果是NULL表示一直检测不超时 。

返回值:超时 0
失败  -1
成功 >0


为了配合select函数执行,有如下宏函数:
void FD_CLR(int fd, fd_set *set);
功能:将指定的set集合中编号为fd的描述符号删除。

int  FD_ISSET(int fd, fd_set *set);
功能:判断值为fd的描述符是否在set集合中,
  如果在则返回真,否则返回假。

void FD_SET(int fd, fd_set *set);
功能:将指定的fd描述符,添加到set集合中。

void FD_ZERO(fd_set *set);
功能:将指定的set集合中所有描述符删除。

写端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{int ret = mkfifo("fifo", 0666);if (-1 == ret){if (EEXIST != errno){perror("mkfifo");return 1;}}int fd = open("fifo", O_WRONLY);if (-1 == fd){perror("open fifo");return 1;}while(1){write(fd, "hello", 5);sleep(3);}close(fd);// system("pause");return 0;
}

读端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{int ret = mkfifo("fifo", 0666);if (-1 == ret){if (EEXIST != errno){perror("mkfifo");return 1;}}int fd = open("fifo", O_RDONLY);if (-1 == fd){perror("open fifo");return 1;}// 1.create setfd_set rd_set, tmpset;FD_ZERO(&rd_set);FD_ZERO(&tmpset);// 2 . add fd to fd_setFD_SET(fd, &tmpset);FD_SET(0, &tmpset);while (1){// 5 clean flagrd_set = tmpset;char buf[128] = {0};// 3 wait eventselect(fd + 1, &rd_set, NULL, NULL, NULL);// find fdint i = 0;for (i = 0; i < fd + 1; i++){if (FD_ISSET(i, &rd_set) && 0 == i){fgets(buf, sizeof(buf), stdin);printf("terminal:%s", buf);}if (FD_ISSET(i, &rd_set) && fd == i){read(fd, buf, sizeof(buf));printf("fifo:%s\n", buf);}}}close(fd);// remove("fifo");// system("pause");return 0;
}

select poll epoll的区别

1. select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

select的调用一般要注意几点:
① readfds等是指针结果参数,会被函数修改,所以一般会另外定义一个allread_fdset,保持全部要监听读的句柄,将它的拷贝传递给select函数,返回可读的句柄集合,类型fdset支持赋值运算符=;
② 要注意计算nfds,当新增监听句柄时比较容易修改,当减少监听句柄时较麻烦些,如果要精确修改需要遍历或者采用最大堆等数据结构维护这个句柄集,以方便的找到第二大的句柄,或者干脆在减少监听句柄时不管nfds;
③ timeout如果为NULL表示阻塞等,如果timeout指向的时间为0,表示非阻塞,否则表示select的超时时间;
④ select返回-1表示错误,返回0表示超时时间到没有监听到的事件发生,返回正数表示监听到的所有事件数(包括可读,可 写,异常),通常在处理事件时 会利用这个返回值来提高效率,避免不必要的事件触发检查。(比如总共只有一个事件,已经在可读集合中处理了一个事件,则可写和异常就没必要再去遍历句柄集 判断是否发生事件了);
⑤ Linux的实现中select返回时会将timeout修改为剩余时间,所以重复使用timeout需要注意。

select的缺点在于:
① 由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(在Linux上是1024)个句柄(不同机器可能不一样);
② 返回的可读集合是个fdset类型,需要对所有的监听读句柄一一进行FD_ISSET的测试来判断是否可读;
③ nfds的存在就是为了解决select的效率问题(select遍历nfds个文件描述符,判断每个描述符是否是自己关心的,对关心的描述符判断是否发生事件)。但是解决不彻底,比如如果只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件。


3. epoll  man 7 epoll  i/o时间通知机制,,主动上报


int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

写端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{int ret = mkfifo("fifo", 0666);if (-1 == ret){if (EEXIST != errno){perror("mkfifo");return 1;}}int fd = open("fifo", O_WRONLY);if (-1 == fd){perror("open fifo");return 1;}while(1){write(fd, "hello", 5);sleep(3);}close(fd);// system("pause");return 0;
}

读端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>int add_fd(int epfd, int fd)
{struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd;int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);if (-1 == ret){perror("add fd ");return 1;}return 0;
}
int main(int argc, char **argv)
{int ret = mkfifo("fifo", 0666);if (-1 == ret){if (EEXIST != errno){perror("mkfifo");return 1;}}int fd = open("fifo", O_RDONLY);if (-1 == fd){perror("open fifo");return 1;}// 1. create fd setint epfd = epoll_create(2);if (-1 == epfd){perror("epoll_create");return 1;}struct epoll_event rev[2];// 2 .  add fd to setadd_fd(epfd, 0);add_fd(epfd, fd);while (1){char buf[128] = {0};// 3 wait eventint ep_ret = epoll_wait(epfd, rev, 2, -1);int i = 0;for (i = 0; i < ep_ret; i++){if (0 == rev[i].data.fd){fgets(buf, sizeof(buf), stdin);printf("termina:%s", buf);}else{read(fd, buf, sizeof(buf));printf("fifo:%s\n", buf);}}}close(fd);// remove("fifo");// system("pause");return 0;
}


epoll 解决了select和poll的几个性能上的缺陷:①不限制监听的描述符个数(poll也是),只受进程打开描述符总数的限制;②监听性能不随着监听描述 符数的增加而增加,是O(1)的,不再是轮询描述符来探测事件,而是由描述符主动上报事件;③使用共享内存的方式,不在用户和内核之间反复传递监听的描述 符信息;④返回参数中就是触发事件的列表,不用再遍历输入事件表查询各个事件是否被触发。
epoll显著提高性能的前提是:监听大量描述符,并且每次触发事件的描述符文件非常少。
epoll的另外区别是:①epoll创建了描述符,记得close;②支持水平触发和边沿触发。

相关文章:

  • 记录一次jenkins slave因为本地安装多个java版本导致的问题
  • PurgeCSS:CSS瘦身优化性能终极解决方案
  • SAP BTP连接SAP,云连接器
  • Python数据可视化艺术:动态壁纸生成器
  • Flink 系列之二十八- Flink SQL - 水位线和窗口
  • Dagster 实现数据质量自动化:6大维度检查与最佳实践
  • 关于空气钻井下等场合燃爆实时多参数气体在线监测系统技术方案
  • CodeForces 1453C. Triangles
  • 【小根堆】P9557 [SDCPC 2023] Building Company|普及+
  • 【大模型02---Megatron-LM】
  • 从传统楼盘到智慧空间:数字孪生的地产赋能之路
  • 以田为证——AI伦理治理在农业植保项目中的落地实践
  • 《Qt5.14.1与Mingw C++:打造可发布程序的技术之旅》
  • Qt Creator 从入门到项目实战
  • TickIt:基于 LLM 的自动化 Oncall 升级
  • TypeReference指定反序列化获取响应对象
  • 进行性核上性麻痹饮食攻略:营养安全双护航
  • 内网渗透测试技巧与利用操作手册(SMB / MSSQL / LDAP)
  • 完全渲染后的页面内容
  • element-ui table实现默认选中,且不可修改
  • 门户网站制作需要多少钱/蚁坊软件舆情监测系统
  • 类似酷家乐做庭院的网站/海外市场推广方案
  • 网站的建设怎么写/网络营销做的比较好的企业
  • 邯郸网站建设效果/优化排名推广教程网站
  • 四川省人民政府关于农村宅基地/seo自动优化软件
  • 群辉怎么进入wordpress后台/深圳seo排名优化