Linux环境下的UDP/TCP网络通信API接口函数(含网络字节序的转换)

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

网络字节序和本地字节序的转换,可以参考:本地字节序和网络字节序的相互转换


目录

一、了解struct sockaddr

1、AF_INET

2、AF_UNIX

二、UDP常见API

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

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

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

4、发送数据 sendto(UDP)

5、监听请求 listen(TCP)

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

7、连接服务端 connect(TCP)

8、接收数据 recv(TCP)

9、发送数据 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决定即可。

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

/*********************************服务端*********************************/
#include <netinet/in.h>    //引入定义sockaddr_in的头文件


/*
** 定义端口号
** uint16_t一般是网络传输的时候使用的数据类型
** 因为这种数据类型不会像int一样因为x64或者x86而发生变化,uint16_t就只占16字节
*/
uint16_t port = 8080;      

struct sockaddr_in local;            //声明结构体

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

/*
** 服务端和客户端通信时要交换地址和端口,即端口和ip地址会被送入网络
** 这里的port作为主机上的变量,是主机序列,需要转化为网络字节序
** 转换函数在下面第二部分
*/
local.sin_port = htons(port);   


/*
** 这里也是同理,我们输入的内容是点分十进制,字符串风格的IP地址"127.0.0.1"   
** a.首先需要转化为整数IP
** b.然后再转化为网络字节序
** OS给我们提供了一个函数 inet_addr 来完成上述两步的工作
*/
//local.sin_addr.s_addr = inet_addr("42.192.83.143");
/*
** 为什么不使用上面这种写法呢?
** 一个服务器可能有多个网卡,可能绑定多个IP地址,比如42.192.83.143、42.192.83.144
** 如果我们绑定了指定地址,那就只能收到该地址的数据,我们希望的是:收到来自多个IP地址的数据
*/
local.sin_addr.s_addr = INADDR_ANY;

2、AF_UNIX

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

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

二、UDP常见API

1、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。由此可知,套接字本质还是文件。

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

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

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

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

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

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

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

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

//为了统一类型,这里需要强转成统一的结构体类型
bind(server,(struct sockaddr*)&local,sizeof(local))

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

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

第一个参数:通信套接字

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

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

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

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

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

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

4、发送数据 sendto(UDP)

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

第一个参数:通信套接字

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

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

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

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

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

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

5、监听请求 listen(TCP)

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

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

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

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

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

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

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

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

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

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

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

7、连接服务端 connect(TCP)

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

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

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

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

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

8、接收数据 recv(TCP)

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

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

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

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

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

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

9、发送数据 send(TCP)

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

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

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

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

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

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

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值