嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十六)网络通信

简介: 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十六)网络通信

1.网络通信


了解网络通信,可以看如下视频:

半小时了解网络通信


1.1 网络通信概述


使用GIT下载资料后,视频中涉及的文档和图片,位于如下目录:

01_all_series_quickstart\
04_嵌入式Linux应用开发基础知识\
doc_pic\08.网络编程
source\12_socket\


1.1.1 IP和端口


所有的数据传输,都有三个要素 :源、目的、长度。怎么表示源或者目的呢?请看下图:

1670903203298.jpg

所以,在网络传输中需要使用“IP和端口”来表示源或目的。


1.1.2 网络传输中的2个对象:server和client


我们经常访问网站,这涉及2个对象:网站服务器,浏览器。网站服务器平时安静地呆着,浏览器主动发起数据请求。网站服务器、浏览器可以抽象成2个软件的概念:server程序、client程序。

1670903231418.jpg


1.1.3 两种传输方式:TCP/UDP


在一般的网络书籍中,网络协议被分为5层,如下图所示:

1670903380693.jpg

应用层:它是体系结构中的最高层,直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。


在因特网中的应用层协议很多,如支持万维网应用的HTTP协议,支持电子邮件的SMTP协议,支持文件传送的FTP协议,DNS,POP3,SNMP,Telnet等等。


运输层:负责向两个主机中进程之间的通信提供服务。

运输层主要使用以下两种协议:

(1) 传输控制协议TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,能够提供可靠的交付。

(2) 用户数据包协议UDP(User Datagram Protocol):无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能提供“尽最大努力交付”。


网络层:负责将被称为数据包(datagram)的网络层分组从一台主机移动到另一台主机。


链路层:因特网的网络层通过源和目的地之间的一系列路由器路由数据报。


物理层:在物理层上所传数据的单位是比特。物理层的任务就是透明地传送比特流。


这些层对于初学者来说很难理解,我们只需要知道:我们需要使用“运输层”编写应用程序,我们的应用程序位于“应用层”。

使用“运输层”时,可以选择TCP协议,也可以选择UDP协议。


TCP和UDP 原理上的区别

TCP向它的应用程序提供了面向连接的服务。这种服务有2个特点:可靠传输、流量控制(即发送方/接收方速率匹配)。它包括了应用层报文划分为短报文,并提供拥塞控制机制。

UDP协议向它的应用程序提供无连接服务。它没有可靠性,没有流量控制,也没有拥塞控制。


为何存在UDP协议

既然TCP提供了可靠数据传输服务,而UDP不能提供,那么TCP是否总是首选呢?

答案是否定的,因为有许多应用更适合用UDP,举个例子:视频通话时,使用UDP,偶尔的丢包、偶尔的花屏时可以忍受的;如果使用TCP,每个数据包都要确保可靠传输,当它出错时就重传,这会导致后续的数据包被阻滞,视频效果反而不好。

使用UDP时,有如下特点:

a. 关于何时发送什么数据控制的更为精细

采用UDP时只要应用进程将数据传递给UDP,UDP就会立即将其传递给网络层。而TCP有重传机制,而不管可靠交付需要多长时间。但是实时应用通常不希望过分的延迟报文段的传送,且能容忍一部分数据丢失。

b. 无需建立连接,不会引入建立连接时的延迟。

c. 无连接状态,能支持更多的活跃客户。

d. 分组首部开销较小。


TCP/UDP网络通信大概交互图

下面我们分别画出运用TCP协议和运用UDP协议的客户端和服务器大概交互图。

1670903431956.jpg

1670903447003.jpg


1.2 网络编程主要函数介绍


1.2.1 socket函数


int socket(int domain, int type,int protocol);

此函数用于创建一个套接字。

domain是网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等)。

AF_UNIX只能够用于单一的Unix 系统进程间通信,而AF_INET是针对Internet的,因而可以允许远程通信使用。


type是网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等)。

SOCK_STREAM表明用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。

SOCK_DGRAM 表明用的是UDP协议,这样只会提不可靠,无连接的通信。


关于protocol,由于指定了type,所以这个地方一般只要用0来代替就可以了。

此函数执行成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况。


1.2.2 bind函数


int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

从函数用于将地址绑定到一个套接字。

sockfd是由socket函数调用返回的文件描述符。

my_addr是一个指向sockaddr的指针。

addrlen是sockaddr结构的长度。


sockaddr的定义:

struct sockaddr{
unisgned short  as_family;
char sa_data[14];
};


不过由于系统的兼容性,我们一般使用另外一个结构(struct sockaddr_in) 来代替。


sockaddr_in的定义:

struct sockaddr_in{
unsigned short   sin_family;     
unsigned short     sin_port;
struct in_addr   sin_addr;
unsigned char   sin_zero[8];
}


如果使用Internet所以sin_family一般为AF_INET。

sin_addr设置为INADDR_ANY表示可以和任何的主机通信。

sin_port是要监听的端口号。

bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样。


1.2.3 listen函数


int listen(int sockfd,int backlog);

此函数宣告服务器可以接受连接请求。

sockfd是bind后的文件描述符。

backlog设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。

listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。


1.2.4 accept函数


int accept(int sockfd, struct sockaddr *addr,int *addrlen);

服务器使用此函数获得连接请求,并且建立连接。

sockfd是listen后的文件描述符。

addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。

accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。


1.2.5 connect函数


int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);

可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。

sockfd是socket函数返回的文件描述符。

serv_addr储存了服务器端的连接信息,其中sin_add是服务端的地址。

addrlen是serv_addr的长度

connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。


1.2.6 send函数


ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd 指定发送端套接字描述符;

buf 指明一个存放应用程序要发送数据的缓冲区;

len 指明实际要发送的数据的字节数;

flags 一般置0。

客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据


1.2.7 recv函数


ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd 指定接收端套接字描述符;

buf 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

len 指明buf的长度;

flags 一般置0。

客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。


1.2.8 recvfrom函数


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


recvfrom通常用于无连接套接字,因为此函数可以获得发送者的地址。

src_addr 是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。

addrlen 常置为sizeof (struct sockaddr)。


1.2.9 sendto函数


ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

sendto和send相似,区别在于sendto允许在无连接的套接字上指定一个目标地址。

dest_addr 表示目地机的IP地址和端口号信息,

addrlen 常常被赋值为sizeof (struct sockaddr)。

sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。


1.3 TCP编程


本章配套的视频,我没有重新录制,而是修改这个视频得来:2012年录制的“第3期项目视频”中的“第1课第6.1节_一小时学会网络编程_两个简单例子_tcp_udp”。

在2012年录制的视频,质量一点都不比现在的差。

悦己之作,方能悦人。很高兴,我从来没降低要求。


使用GIT下载所有源码后,本节源码位于如下目录:


01_all_series_quickstart\
04_嵌入式Linux应用开发基础知识\source\12_socket\
tcp\


/*socket
 *bind
 *listen
 *accept
 *send/recv
 */
//1.把头文件包含进来,再写
//本段代码大多数函数头文件
#include<sys/types>
#include <sys/socket.h>
//meset头文件
#include <string.h>
//ntoa函数头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//fork函数的头文件
#include <unistd.h>
#include <stdio.h>
#define SERVER_PORT 8888
int main(int argc,char **argv)
{
  int iSocketServer;
  int iRet;
  struct sockaddr_in tSocketServerAddr;
  int iAddrlen;
  int iSocketClient;
  unsigned char ucRecvBuf[1000];
  int iRecvLen;
  int iClientNum = 0;
  tSocketServerAddr.sin_family = AF_INFT;
  tSocketServerAddr.sin_port = htons( SERVER_pORT;) //host to net,short"将主机字节序转换为网络字节序。
  tSocketServerAddr.sin_addr.s_addr=INADDR_ANY;//监听主机sever上所有的ip,因为服务器是被动的响应请求。
  memset(tSocketSeverAddr.sin_zero,0,8);
  iSocketServer = int socket(AF_INET,SOCK_STREAM,0);
  if(-1 == iSocketServer)
  {
  printf("socket error!\n");
  return -1;
  }
  iRet = bind(iSocketSever,(const struct sockaddr *)&tSocketServerAddr,sizeof(struct sockaddr));
  /*不过由于系统的兼容性,我们一般不用这个头文件,而使用其他一个结构体(struct sockaddr_in)来代替,在中有sockaddr_in的定义:
  struct sockaddr_in{
  unsigned short  sin_family;
  unsigned short  sin_port;
  struct in_addr  sin_add;
  unsigned char   sin_zero[8];
  }
  我们主要使用Internet所以sin_family一般为AF_INET,
  sin_addr设置为INADDR_ANY表示可以和任何的主机通信。
  sin_port是要监听端口号,
  sin_zero[8]是用来填充的
  bind 将本地的端口同socket返回的文件描述捆绑在一起。成功是返回0,失败的情况和socket一样。
  */
  if(-1==iRet)
  {
  printf("bind error!\n");
  return -1;
  }
  iRet = listen(iSocketServer,BACKLOG);
  if(-1 == iRet)
  {
  printf("listen error!\n");
  return -1;
  }
  while(1)
  {
  iAddrlen = sizeof(struct sockaddr);
  iSocketClient = accept(iSocketServer,(struct sockaddr *)&tSocketClientAddr,&iAddrlen);//等待一条连接
  if(-1 != iSocketClient)
  {
    iClientNum++;//当多台客户端访问的时候
    printf("Get connect from client %d : %s\n",iClientNum,inet ntoa(tSocketClientAddr.sin_addr));
    if(!fork())//每来一个连接的时候,都使用fork创建一个进程
    {
    /*子进程的源码*/
    while (1)
    {
      /*接受客户端发来的数据并显示出来*/
      iRecvLen = recv(iSocketClient,ucRecvBuf,999,0);
      if(iRecvlen <= 0)
      {
      close(iSocketClient);
      return -1;
      }
      else 
      {
      ucRecvBuf[iRecvLen]='\0';//最后一个位置加上一个结束符
      printf("get msg from client %d:%s\n",iClientNum,ucRecvBuf)//从缓冲中打印出来
      }
    }
  }
  }
  close(iSocketServer) ;
  return 0;
}
//本段代码大多数函数头文件
#include<sys/types>
#include <sys/socket.h>
//meset头文件
#include <string.h>
//ntoa函数头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//fork函数的头文件
#include <unistd.h>
#include <stdio.h>
/*socket
 *connect
 *send/recv
 *
 */
#define SERVER_PORT 8888
int main( int argc,char **argv){
  int iSocketClient;
  int iRet;
  struct sockaddr_in tSocketServerAddr;
  unsigned char ucSendBuf[1000];
  int iSendLen;
  if(argc!=2)//如果传输参数不是两个,则报错并输出服务器ip
  {
  printf("Usage:\n");
  printf("%s <server_ip\n>",argv[0]);
  return -1;
  }
  iSocketClient = socket(AF_INET,SOCK_STREAM,0);
  tSocketServerAddr.sin_family = AF_INFT;
  tSocketServerAddr.sin_port = htons( SERVER_pORT;) //host to net,short"将主机字节序转换为网络字节序。
  //tSocketServerAddr.sin_addr.s_addr=INADDR_ANY;//监听主机sever上所有的ip,因为服务器是被动的响应请求。
  memset(tSocketSeverAddr.sin_zero,0,8);
  if(0 == inet_aton(argv[1],&tSocketServerAddr.sin_addr))//上面那个代码,是服务器监视其他客户端的代码,而这段是把客户端的ip传送给服务器的代码
  {
  printf("invaild server_ip\n");
  return -1;
  }
  iRet = connect(iSocketClient,(const struct sockaddr *)&tSocketServerAddr,sizeof(struct sockaddr));
  if(-1 == iRet)
  {
  printf("connect error!\n");
  return -1;
  }
  while(1){
  if(fgets(ucSendBuf,999,stdin))
  {
    iSendLen =send(iSocketClient,ucSendBuf,strlen(ucSendBuf),0)
    if(iSendLen<=0){
    close(iSocketClient);
    return -1;
    }
  } 
  }
  /*不过由于系统的兼容性,我们一般不用这个头文件,而使用其他一个结构体(struct sockaddr_in)来代替,在中有sockaddr_in的定义:
  struct sockaddr_in{
  unsigned short  sin_family;
  unsigned short  sin_port;
  struct in_addr  sin_add;
  unsigned char   sin_zero[8];
  }
  我们主要使用Internet所以sin_family一般为AF_INET,
  sin_addr设置为INADDR_ANY表示可以和任何的主机通信。
  sin_port是要监听端口号,
  sin_zero[8]是用来填充的
  bind 将本地的端口同socket返回的文件描述捆绑在一起。成功是返回0,失败的情况和socket一样。
  */
  return 0;
}


1.4 UDP编程


本章配套的视频,我没有重新录制,而是修改这个视频得来:2012年录制的“第3期项目视频”中的“第1课第6.1节_一小时学会网络编程_两个简单例子_tcp_udp”。

在2012年录制的视频,质量一点都不比现在的差。

悦己之作,方能悦人。很高兴,我从来没降低要求。


使用GIT下载所有源码后,本节源码位于如下目录:

01_all_series_quickstart\
04_嵌入式Linux应用开发基础知识\source\12_socket\
udp\
udp2\
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
 * connect
 * send/recv
 */
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
  int iSocketClient;
  struct sockaddr_in tSocketServerAddr;
  int iRet;
  unsigned char ucSendBuf[1000];
  int iSendLen;
  if (argc != 2)
  {
  printf("Usage:\n");
  printf("%s <server_ip>\n", argv[0]);
  return -1;
  }
  iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
  tSocketServerAddr.sin_family      = AF_INET;
  tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
  //tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
  if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
  {
  printf("invalid server_ip\n");
  return -1;
  }
  memset(tSocketServerAddr.sin_zero, 0, 8);
  iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));  
  if (-1 == iRet)
  {
  printf("connect error!\n");
  return -1;
  }
  while (1)
  {
  if (fgets(ucSendBuf, 999, stdin))
  {
    iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
    if (iSendLen <= 0)
    {
    close(iSocketClient);
    return -1;
    }
  }
  }
  return 0;
}


#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* socket
 * bind
 * sendto/recvfrom
 */
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
  int iSocketServer;
  int iSocketClient;
  struct sockaddr_in tSocketServerAddr;
  struct sockaddr_in tSocketClientAddr;
  int iRet;
  int iAddrLen;
  int iRecvLen;
  unsigned char ucRecvBuf[1000];
  int iClientNum = -1;
  iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == iSocketServer)
  {
  printf("socket error!\n");
  return -1;
  }
  tSocketServerAddr.sin_family      = AF_INET;
  tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
  tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
  memset(tSocketServerAddr.sin_zero, 0, 8);
  iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
  if (-1 == iRet)
  {
  printf("bind error!\n");
  return -1;
  }
  while (1)
  {
  iAddrLen = sizeof(struct sockaddr);
  iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
  if (iRecvLen > 0)
  {
    ucRecvBuf[iRecvLen] = '\0';
    printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
  }
  }
  close(iSocketServer);
  return 0;
}
相关实践学习
自建数据库迁移到云数据库
本场景将引导您将网站的自建数据库平滑迁移至云数据库RDS。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业务,无需过多担心数据库的管理和维护。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
相关文章
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
647 61
|
12月前
|
安全 Linux 网络安全
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
315 0
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
Vite使用svg-企业级开发(支持本地svg和网络svg渲染)
本教程介绍如何在Vite项目中集成SVG图标插件。首先安装`vite-plugin-svg-icons`,配置插件指向SVG图标目录,并注册全局组件。接着创建SVG图标组件,支持内部图标与外部图片展示。通过简单配置,即可在页面中灵活使用各类SVG图标,提升开发效率。
589 0
|
安全 网络协议 Linux
Linux网络应用层协议展示:HTTP与HTTPS
此外,必须注意,从HTTP迁移到HTTPS是一项重要且必要的任务,因为这不仅关乎用户信息的安全,也有利于你的网站评级和粉丝的信心。在网络世界中,信息的安全就是一切,选择HTTPS,让您的网站更加安全,使您的用户满意,也使您感到满意。
481 19
|
开发者
鸿蒙仓颉语言开发教程:网络请求和数据解析
本文介绍了在仓颉开发语言中实现网络请求的方法,以购物应用的分类列表为例,详细讲解了从权限配置、发起请求到数据解析的全过程。通过示例代码,帮助开发者快速掌握如何在网络请求中处理数据并展示到页面上,减少开发中的摸索成本。
鸿蒙仓颉语言开发教程:网络请求和数据解析
|
Linux 数据安全/隐私保护
使用Linux命令行接入无线网络Wi-Fi的示例。
现在,你已经使用命令行成功地连接到 Wi-Fi 网络了。这两个示例涵盖了用 `nmcli` 和 `wpa_supplicant` 连接无线网络的常见场景,让你能够不依赖图形化界面来完成这个任务。在日常使用中熟练掌握这些基本操作能增强你对 Linux 系统的理解,帮助你更有效地处理各种问题。
1271 12
|
监控 安全 网络协议
恶意软件无处逃!国内版“Manus”AiPy开发Windows沙箱工具,进程行为+网络传输层级监控! 头像 豪气的
NImplant.exe 是一款后渗透测试工具,可实现远程管理与持久化控制。其优点包括无文件技术、加密通信和插件扩展,但也存在被检测风险及配置复杂等问题。为深入分析其行为,我们基于 aipy 开发了 Windows 沙箱工具,针对桌面上的 NImplant.exe 进行多维度分析,涵盖进程行为、网络连接(如 TCP 请求、目标 IP/域名)、文件控制等,并生成传输层监控报告与沙箱截图。结果显示,aipy 工具响应迅速,报告清晰易读,满足分析需求。
|
Ubuntu Linux
Linux系统管理:服务器时间与网络时间同步技巧。
以上就是在Linux服务器上设置时间同步的方式。然而,要正确运用这些知识,需要理解其背后的工作原理:服务器根据网络中的其他机器的时间进行校对,逐步地精确自己的系统时间,就像一只犹豫不决的啮齿动物,通过观察其他啮齿动物的行为,逐渐确定自己的行为逻辑,既简单,又有趣。最后希望这个过程既能给你带来乐趣,也能提高你作为系统管理员的专业素养。
2500 20
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
736 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
网络协议 物联网
VB6网络通信软件上位机开发,TCP网络通信,读写数据并处理,完整源码下载
本文介绍使用VB6开发网络通信上位机客户端程序,涵盖Winsock控件的引入与使用,包括连接服务端、发送数据(如通过`Winsock1.SendData`方法)及接收数据(利用`Winsock1_DataArrival`事件)。代码实现TCP网络通信,可读写并处理16进制数据,适用于自动化和工业控制领域。提供完整源码下载,适合学习VB6网络程序开发。 下载链接:[完整源码](http://xzios.cn:86/WJGL/DownLoadDetial?Id=20)
637 12

热门文章

最新文章