CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2017/12/30/嵌入式Linux网络编程——TCP_UDP/#more
本文主要记录TCP/UDP网络编程的基础知识,采用TCP/UDP实现宿主机和目标机之间的网络通信。
回想去年校招那会,笔试题老是出现TCP/UDP相关的内容。
那时候的我,熟悉点硬件,勉强会点STM32,哪知道什么TCP/UDP……
渐渐的,TCP/UDP似乎就成了一个挥之不去的阴影。
赶在今年年底,简单的入下门,明年估计会用上。
1.目标
暂时想不出什么好的应用场景,
目前想到目标就是实现让两个设备通过网络传输数据,
比如开发板和Linux主机之间传数据,
以后就可以实现开发板通过网络上报数据或者主机通过网络控制开发板。
此外,暂时不想关心具体的网络模型,更注重于网络相关函数的直接使用。
2.Linux网络编程基础
2.1 嵌套字
多个TCP连接或者多个应用程序进程 可能需要同一个TCP端口传输数据。
为了区分不同应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP交互提供了称为**嵌套字(Socket)**的接口。
Linux中的网络编程正是通过Socket接口实现的,Socket是一种文件描述符。
常用的TCP/IP有以下三种类型的嵌套字:
- 流式嵌套字(SOCK_STREAM)
用于提供面向连接的、可靠的数据传输服务,即使用TCP进行传输。 - 数据报嵌套字(SOCK_DGRAM)
用于提供无连接的服务,即使用UDP进行传输。 - 原始嵌套字(SOCK_RAW)
可以读写内核没有处理的IP数据报,而流式嵌套字只能读取TCP的数据,数据报嵌套字只能读取UDP的数据。
因此,如果要访问其它协议发送的数据必须使用原始嵌套字,它允许对底层协议(如IP或ICMP)直接访问。
2.2 端口
TCP/IP协议中的端口,端口号的范围从0~65535。
一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1023。例如http的端口号是80,FTP为21,SSH为22,Telnet为23等。
还有一类是用户自己定义的,通常是大于1024的整型值。
2.3 网络地址
网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信)。
在网络中,每一个节点(计算机或路由)都有一个网络地址,如192.168.1.4,也就是IP地址。
两个进程通信时,首先要确定各自所在的网络节点的网络地址。
但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。
在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。
所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程。
例如,如网络中某一台计算机的IP为192.168.1.4,操作系统分配给计算机中某一应用程序进程的端口号为1500,则此时192.168.1.4 1500
就构成了一个套接口。
2.3.1 网络地址的格式
在Socket程序设计中,struct sockaddr
用于记录网络地址,其格式如下:
{% codeblock lang:c %}
struct sockaddr
{
unsigned short sa_family; /协议族,采用AF_XXX的形式,例如AF_INET(IPv4协议族)/
char sa_data[14]; /14字节的协议地址,包含该socket的IP地址和端口号。/
};
{% endcodeblock %}
但在实际编程中,并不针对sockaddr
数据结构进行操作,而是用与其等价的sockaddr_in
数据结构:
{% codeblock lang:c %}
struct sockaddr_in
{
short int sa_family; /地址族/
unsigned short int sin_port; /端口号/
struct in_addr sin_addr; /IP地址/
unsigned char sin_zero[8]; /填充0 以保持与struct sockaddr同样大小/
};
{% endcodeblock %}
2.3.2 网络地址的转换
IP地址通常用数字加点(如192.168.1.4)表示,而在struct in_addr
中使用的式32位整数表示。因此,Linux提供如下函数进行两者之间的转换:
- inet_aton()函数:
所需要头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
函数格式:
int inet_aton(const char *cp, struct in_addr *inp);
函数功能:
将a.b.c.d字符串形式的IP地址转换成32位网络序号IP地址;
*cp:存放字符串形式的IP地址的指针
*inp:存放32位的网络序号IP地址
返回值:
转换成功,返回非0,否则返回0;
- inet_ntoa()函数:客户机端:
所需要头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
函数格式:
char *inet_ntoa(struct in_addr in);
函数功能:
将32位网络序号IP地址转换成a.b.c.d字符串形式的IP地址;
in:Internet主机地址的结构
返回值:
转换成功,返回一个字符指针,否则返回NULL;
2.4 字节序
不同的CPU采用对变量的字节存储顺序可能不同。
常用的X86结构是小端模式,很多的ARM,DSP都为小端模式,即内存的低地址存储数据的低字节,高地址存储数据的高字节。
而KEIL C51则为大端模式,内存的高地址存储数据的低字节,低地址存储数据高字节。
对于网络传输来说,数据顺序必须是一致的,网络字节顺序采用大端字节序方式。
下面是四个常用的转换函数:
主机转网络:
- htons()函数:
所需要头文件:
#include <netinet/in.h>
函数格式:
unsigned short int htons(unsigned short int hostshort)
函数功能:
将参数指定的16位主机(host)字符顺序转换成网络(net)字符顺序;
hostshort:待转换的16位主机字符顺序数
返回值:
返回对应的网络字符顺序数;
- htonl()函数:
所需要头文件:
#include <netinet/in.h>
函数格式:
unsigned long int htons(unsigned long int hostlong)
函数功能:
将参数指定的32位主机(host)字符顺序转换成网络(net)字符顺序;
hostlong:待转换的32位主机字符顺序数
返回值:
返回对应的网络字符顺序数;
网络转主机:
- ntohs()函数:
所需要头文件:
#include <netinet/in.h>
函数格式:
unsigned short int ntohs(unsigned short int netshort)
函数功能:
将参数指定的16位网络(net)字符顺序转换成主机(host)字符顺序;
netshort:待转换的16位网络字符顺序数
返回值:
返回对应的主机字符顺序数;
- ntohl()函数:
所需要头文件:
#include <netinet/in.h>
函数格式:
unsigned long int ntohl(unsigned long int netlong)
函数功能:
将参数指定的32位网络(net)字符顺序转换成主机(host)字符顺序;
netshort:待转换的32位网络字符顺序数
返回值:
返回对应的主机字符顺序数;
3.TCP
TCP有专门的传递保证机制,收到数据时会自动发送确认消息,发送方收到确认消息后才会