Linux环境下的UDP/TCP网络通信API接口函数

本文详细介绍了UDP协议及其常用API,包括struct sockaddr结构体、AF_INET与AF_UNIX的区别,以及socket创建、IP与端口绑定、数据发送与接收等关键函数的使用。同时,简述了TCP协议的相关API,如listen、accept、connect、recv和send等,帮助开发者更好地理解和应用网络通信。

UDP协议属于传输层,无需连接即可通信,在使用UDP协议进行通信之前,我们需要先了解一下一些常见的API。


目录

一、了解struct sockaddr

1、AF_INET

2、AF_UNIX

二、UDP常见API

1、网络字节序转换函数(TCP/UDP)

2、本地IP地址 与 网络字节序IP 的转换函数(TCP/UDP)

3、socket创建函数(TCP/UDP)

4、IP、端口绑定函数bind(TCP/UDP)

5、接收网络数据 recvfrom(UDP)

6、发送数据 sendto(UDP)

7、监听请求 listen(TCP)

8、服务端接受连接请求 accept(TCP)

9、连接服务端 connect(TCP)

10、接收数据 recv(TCP)

11、发送数据 send(TCP)


一、了解struct sockaddr

网络通信的方式有很多,我们主要了解AF_INET、域间通信这两种通信方式。简单来说,AF_INET用于网络通信,域间通信可以理解为一台主机内的通信。

这两种通信方式中的地址格式各不相同,那要如何保存地址信息呢??如果是AF_INET,保存地址信息的数据类型为 struct sockaddr_in;如果是AF_UNIX,保存地址信息的数据类型为 struct sockaddr_un。

为了方便记忆和使用,设计者把这些通信方式的地址格式统一化,也就是现在的struct sockaddr。主要包括地址类型 和 地址的具体内容(换成生活中的例子,就相当于 国籍 和 国内地址)。

1、AF_INET

当我们使用的通信方式为AF_INET时,使用的结构体类型是struct sockaddr_in,我们需要填入的内容最多有三个,为什么是最多呢?服务端作为被访问的对象,IP和端口一样都不能少;但是客户端作为访问者,没有必要显式确定自己的端口号,交由OS决定即可。

就好比,你从快递小哥那取快递时,你会在意对方叫啥吗??回到正题,下面就介绍一下如何创建

 
  1. /*********************************服务端*********************************/

  2. #include <netinet/in.h> //引入定义sockaddr_in的头文件

  3. /*

  4. ** 定义端口号

  5. ** uint16_t一般是网络传输的时候使用的数据类型

  6. ** 因为这种数据类型不会像int一样因为x64或者x86而发生变化,uint16_t就只占16字节

  7. */

  8. uint16_t port = 8080;

  9. struct sockaddr_in local; //声明结构体

  10. local.sin_family = AF_INET; //填充地址类型,即你的通信方式

  11. /*

  12. ** 服务端和客户端通信时要交换地址和端口,即端口和ip地址会被送入网络

  13. ** 这里的port作为主机上的变量,是主机序列,需要转化为网络字节序

  14. ** 转换函数在下面第二部分

  15. */

  16. local.sin_port = htons(port);

  17. /*

  18. ** 这里也是同理,我们输入的内容是点分十进制,字符串风格的IP地址"127.0.0.1"

  19. ** a.首先需要转化为整数IP

  20. ** b.然后再转化为网络字节序

  21. ** OS给我们提供了一个函数 inet_addr 来完成上述两步的工作

  22. */

  23. //local.sin_addr.s_addr = inet_addr("42.192.83.143");

  24. /*

  25. ** 为什么不使用上面这种写法呢?

  26. ** 一个服务器可能有多个网卡,可能绑定多个IP地址,比如42.192.83.143、42.192.83.144

  27. ** 如果我们绑定了指定地址,那就只能收到该地址的数据,我们希望的是:收到来自多个IP地址的数据

  28. */

  29. local.sin_addr.s_addr = INADDR_ANY;

2、AF_UNIX

当我们使用的通信方式为域间通信时,使用的结构体类型是struct sockaddr_un,此时第一个16位地址类型已经帮我们填好了,我们只需要填入路径名。这个就类似于之前学的命名管道,在使用命名管道的时候,我们也需要指明管道的位置,以便于让通信双方看到同一份资源。

由于现在统一了上述两者的类型,在实际填入到函数时,我们需要强转成struct sockaddr类型,详情参考下面的bind函数

二、UDP常见API

1、网络字节序转换函数(TCP/UDP)

一般数据在内存中是按照字节存储的,存储的方式分为大端和小端。在不知道对方主机的存储方式的情况下,我们不知道是否需要转换数据的存储方式。因此,TCP/IP协议规定:发送到网络的数据流应采用大端字节序!

如果当前主机是大端,那就可以忽略直接发送;否则就需要转化成大端再发送,下面提供了一些网络字节序和主机字节序的转换函数。

h代表host,n表示network

htonl:host to network,当主机序列为32位整型时,由主机字节序转化为网络字节序。

ntohs:network to host,当网络字节序为16位整型时,由网络字节序转化为主机字节序。

注意:以htonl为例,如果主机是小端序,那么该函数就会将数据字节序转化为大端;如果主机是大端序,该函数不会做任何操作。

2、本地IP地址 与 网络字节序IP 的转换函数(TCP/UDP)

站在客户端的角度,在网络通信的时候,一般IP地址和端口号要发送给对端,而我们使用的IP地址是点分十进制的,比如127.0.0.1,这样传递起来不是特别方便,我们需要先将其转化为四字节的整数,然后再转化为网络字节序。

站在服务端的角度,如果你想要拿到对端的IP地址,那就需要将网络字节序转化为本地主机字节序,然后再转化为点分十进制的字符串IP。

Linux给我们提供了这两者的转换函数

 参数比较简单,一个只要提供对应的字符串

inet_addr("127.0.0.1");    //将点分十进制的字符串IP地址转化为网络字节序的形式

另一个要提供保存地址信息的结构体,假设服务端保存对端地址信息的结构体为struct  sockaddr_in  peer;

那这里的in_addr又是什么类型??其实这个东西就存在与 sockaddr_in 的内部,这个东西就是存储IP地址的数据类型

const char* client_ip = inet_ntoa(peer.sin_addr);

3、socket创建函数(TCP/UDP)

可以这么认为:socket套接字 = IP地址 + 端口号,是传输数据的载体,同时携带着发送源头的地址信息

以客户端访问服务端为例,服务端可以根据远端的socket套接字来获取到地址信息,以此来对远端的访问做出响应。下面要重点解释一下这个函数的参数。

(1) 第一个参数:domain

表明你使用哪种通信方式,是网络通信还是域间通信。参考值如下。

(2) 第二个参数:type

表示你要使用哪种套接字。参考值如下

SOCK_STREAM:从后面的注释可以了解到,这种套接字提供一种可靠的、基于连接的数据流。

                                说白了,就是TCP

SOCK_DGRAM:这种套接字提供一种无连接的、不可靠的数据报,其实就是UDP。

(3) 第三个参数

指明我们要使用哪种协议,一般设为0,其实前面两个参数就已经可以确定我们要使用哪种协议了。第一个参数选AF_INET,表明我们要使用网络通信的方式,网络通信有两种协议TCP、UDP。第二个参数选SOCK_DGRAM,表明我们选择UDP。

(4) 返回值

成功返回一个文件描述符,失败返回-1。由此可知,套接字本质还是文件。

4、IP、端口绑定函数bind(TCP/UDP

一般服务器会用到这个函数,而客户端不会使用。因为服务器一般是被访问的对象,既然是被访问,那就必须告知别人服务器的地址,别人才能来访问!

那为什么客户端不绑定端口呢?原因有两个,第一,当前主机的一些端口可能已经固定被一些程序使用,如果你自己随意绑定的话,很容易和其他应用程序的端口冲突;第二个,没有必要主动绑定,客户端不会被其他主机访问,客户端在访问服务器的时候,一般设置由OS随机分配,OS最清楚哪个端口有没有被占用。

(1) 第一个参数:这个是我们上面调用创建套接字函数返回的文件描述符

(2) 第二个参数:这个参数是套接字地址数据,我们在博客的一开始就说明了这个参数。

(3) 第三个参数:第二个参数结构体的大小

(4)返回值:成功返回0,失败返回-1

下面介绍一下大致的使用方式,假设是网络通信AF_INET,我们需要最开始说的struct sockaddr_in,以及前面创建的socket套接字

 
  1. //为了统一类型,这里需要强转成统一的结构体类型

  2. bind(server,(struct sockaddr*)&local,sizeof(local))

5、接收网络数据 recvfrom(UDP)

UDP协议中我们使用recvfrom接收网络数据,函数声明如下:

第一个参数:通信套接字

第二个参数:接收缓冲区,即你要把接收到的数据放在哪

第三个参数:你要预留多大字节空间给接收缓冲区

第四个参数:代表读的方式,比如阻塞或者非阻塞,一般设置为0

第五个参数:这个是一个输出型参数,代表获取对端的地址信息,即是谁给你发的数据,以便于后续做出响应

第六个参数:这也是一个输出型参数,代表获取到的地址信息所占字节数

返回值:调用成功,返回接收到的字节数;调用失败,返回-1

6、发送数据 sendto(UDP)

UDP协议中我们使用sendto来向网络发送数据。函数声明如下:

第一个参数:通信套接字

第二个参数:存放要发送的内容的缓冲区,即数组

第三个参数:要发送的内容所占字节数

第四个参数:发送方式,和上面一样,一般设置为0

第五个参数:对端地址信息

第六个参数:对端地址信息所占字节数

返回值:调用成功返回发送的字符数/字节数;调用失败返回-1

7、监听请求 listen(TCP)

listen函数的作用是,将套接字变为监听状态,因为你也不知道什么时候会有请求到来,于是就让套接字一直处在监听状态,等待请求到来。

第一个参数:最初的套接字,也是转化前的套接字。

第二个参数:这个参数暂时设为5。

返回值:成功返回0,失败返回-1

8、服务端接受连接请求 accept(TCP)

绝大多数情况下都是客户端连接服务端,所以服务端是被动接受连接请求的,我们使用accept来接受客户端的连接请求。

 第一个参数:用于通信的套接字

第二个参数:输出型参数,对端的地址信息。也就是谁给你发送的请求。

第三个参数:输出型参数,对端的地址信息大小。

返回值:成功会返回一个套接字(即文件描述符),失败返回错误码。

注意:你或许会很疑惑,上面不是已经把套接字设置为监听状态了吗,这里怎么又有个套接字?现在我们模拟一个场景,某个餐馆的员工在门外拉客,当拉到客人的时候,就通知店里的服务员开始服务,而自己继续拉客。这里的拉客少年就是上面所说的监听套接字,店里的服务员就是这里accept返回的套接字。这样就保证了,一个服务端可以和多个客户端连接。

9、连接服务端 connect(TCP)

客户端作为访问的一方,需要主动给客户端发送请求,如何发送请求呢?就是这里的connect函数

第一个参数:客户端的通信套接字

第二个参数:输入型参数,代表对端的地址信息。也就是客户端要连接谁。

第三个参数:输入型参数,代表地址信息的大小。

返回值:成功返回0,失败返回-1.

10、接收数据 recv(TCP)

recv函数和系统调用函数read类似,不同之处在于,recv函数可以选择接收方式(如阻塞或者非阻塞)。

第一个参数:socket函数返回的文件描述符,也可以称为套接字

第二个参数:缓冲区,即你要把接收到的字符串放到哪。

第三个参数:缓冲区大小,即你预留出了多大空间来存放你接收到的数据

第四个参数:选择接收模式,如阻塞或者非阻塞,一般设为0

返回值:成功返回接收到的字符所占字节数,失败返回-1.

11、发送数据 send(TCP)

send函数类似于系统调用函数write,不同之处在于可以选择发送方式

第一个参数:socket函数返回的文件描述符,也可以称为套接字

第二个参数:缓冲区,即你要发送哪个缓冲区的内容

第三个参数:缓冲区大小,即你要发送的字符数目有多少

第四个参数:选择发送方式

返回值:成功返回已经发送的字符数,失败返回-1

(650条消息) Linux环境下的UDP/TCP网络通信API接口函数_abs(ln(1+NaN))的博客-CSDN博客_linux udp 函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值