网络编程基础——从创建套接字到进行数据传输的各函数的参数详细解释

发布时间:2023年12月26日

Linux 网络编程基础

本文主要涉及

  1. 网络编程的基础概念:IP地址,以及典型的两种通信方式TCP与UDP;
  2. 网络编程从创建套接字到进行数据传输的各函数的参数详细解释

一、 IP地址

为了在两台计算机之间进行通信,它们需要在同一个网络(例如,局域网)中,并且每台计算机都需要有一个唯一的标识符,通常是IP地址。

1.1 IP地址,局域网,路由

以下是更详细的解释:

  1. IP地址:IP地址是网络中设备的标识符,它允许数据在网络上进行路由。每台计算机或设备(例如,智能手机、服务器等)在网络上都必须有一个唯一的IP地址。在IPv4协议中,IP地址通常是一个32位的数字,如192.168.1.1。在IPv6协议中,IP地址更长,并采用了128位的地址空间,如2001:0db8:85a3:0000:0000:8a2e:0370:7334

  2. 局域网(LAN):局域网是一个较小的网络,通常覆盖一个建筑或者一个组织的办公室。在局域网中的设备可以直接互相通信,无需通过互联网。例如,一个家庭网络或者公司内部的网络都可以是局域网。

  3. 路由:当两台计算机在同一个局域网中,并且有各自的IP地址后,它们可以直接互相通信。但是,如果两台计算机位于不同的局域网或网络上,那么它们需要一个路由器或者网关来转发数据路由器负责决定如何将数据从源地址传输到目标地址

1.2 IPV4、IPV6

IPV4 --> 4个字节来表示一台主机
IPV4资源紧张–>扩充到IPV6–>8个字节
IPV4 4个字节,如果一个局域网内的所有IP分完,那么可以分配给2^32个

  1. IPv4 (Internet Protocol version 4):

    • IPv4地址由32位二进制数字组成,通常以四个八位数字表示,每个数字之间用.分隔,例如:192.168.1.1。
    • 这32位地址空间共有 2 32 2^{32} 232个可能的地址,即约42亿个。然而,由于历史原因和地址的分配方式,实际可用的IPv4地址数量远远少于这个数目。
    • IPv4的主要问题是地址资源的枯竭。随着互联网的普及和设备的增多,可用的IPv4地址快速减少。
  2. IPv6 (Internet Protocol version 6):

    • 为了解决IPv4地址空间不足的问题,IPv6被设计出来。IPv6地址是128位二进制数字,通常以八个16位的数字块表示,每个块之间用:分隔,例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334。
    • IPv6的地址空间是IPv4地址空间的 2 96 2^{96} 296倍,这是一个巨大的数字。实际上,这提供了大量的地址供应,远远超过了我们当前的需求。
    • 除了地址空间更大外,IPv6还带来了其他一些优势,例如简化的头部、自动地址配置等。

总的来说,IPv6被视为解决IPv4地址空间不足问题的长期解决方案。随着时间的推移和互联网的发展,IPv6的采用率正在逐渐增加,但在全球范围内,IPv4和IPv6可能会长时间共存,直到所有设备和网络都能完全迁移到IPv6。

1.3 IP地址的组成=网络号+主机号

在世界范围来看,局域网的数量也很多,在互联网上是不通过局域网之内的用户进行通信的,因此在找另外一台主机时,除去区分这个主机以外还需要区分局域网。
所以我们的IP地址分为:网络号+主机号
网络号:用来标识局域网;
主机号:标识局域网中的主机。
要区分哪些是网络号与主机号那就需要子网掩码.
子网掩码:
可以没有0,也没有1
11111111 11111111 11111111 11111111 --> 全是网络号没有主机没有意义

全零–> 全是主机,没有网络,是可以存在的,是世界上最大的网

  1. IP地址的基本组成:

    • 网络号: 这部分标识了设备所在的网络或子网络。网络号的长度取决于地址的分类或子网划分方式。
    • 主机号: 这部分标识了该网络内的具体设备。
  2. IP地址的分类:
    根据IP地址的高阶位,IP地址被分为以下几类:

    • A类地址: 0开头,例如0.0.0.0到127.255.255.255。这些地址被用于大型网络
    • B类地址: 10开头,例如128.0.0.0到191.255.255.255。这些地址常用于中等大小的网络
    • C类地址: 110开头,例如192.0.0.0到223.255.255.255。这些地址常用于小型网络或子网
    • D类地址: 1110开头,用于多点广播
    • E类地址: 1111开头,保留用于未来使用或实验目的。
  3. 子网掩码:

    • 为了更细粒度地划分网络,引入了子网掩码的概念。子网掩码用于指定IP地址中哪些位属于网络号,哪些位属于主机号。
    • 例如,如果子网掩码是255.255.255.0,那么前24位属于网络号,后8位属于主机号。
  4. CIDR (Classless Inter-Domain Routing):

    • 为了更灵活地使用IP地址和支持无类别域间路由,引入了CIDR。CIDR允许将网络号和主机号的长度灵活地定义为不同的值,而不仅仅是基于A、B、C类地址。

当两台计算机在互联网上通信时,路由器或交换机会根据IP地址和子网掩码来确定目标地址是在本地网络还是需要通过路由器进行转发。这就是为什么需要网络号和主机号的原因。

1.4 端口

一台电脑里面可能会运行多个网络程序,但对应的网卡都是一个。

因为IP地址都是公用的,所以需要一个机制去区分网络程序---端口。
网络程序= IP+端口

在计算机网络中,端口号用于标识在一个特定的IP地址上运行的应用程序。这是因为一个计算机可以运行多个网络服务或应用程序,而每个服务或应用程序都需要一个独特的标识符,这就是端口号。

  • 知名端口 (Well-Known Ports): 端口号范围从0到1023。这些端口号被众所周知,并且通常用于标准服务,如HTTP(端口80)、HTTPS(端口443)、FTP(端口21)、SSH(端口22)等。这些端口号是为特定的服务保留的,并且在大多数系统上都被固定地分配给了特定的服务。

  • 注册端口 (Registered Ports): 端口号范围从1024到49151。这些端口号可以被应用程序自由使用,只要它们不与已知的服务冲突。它们是为那些不是标准服务但仍然需要网络连接的应用程序保留的。

  • 动态/私有端口 (Dynamic/Private Ports): 端口号范围从49152到65535。这些端口号可以被客户端程序用作源端口,当它们发起与远程服务的通信时。这些端口号不会在系统上被预分配给特定的服务或应用程序。

二、 通信方式

通信有两种:TCP与UDP
TCP(传输控制协议)和UDP(用户数据报协议)两种不同的传输层协议的基本操作流程

2.1 TCP

TCP是一个面向连接的协议,它确保了数据的可靠传输。以下是你描述的TCP通信的步骤:

服务端:

  1. 创建套接字。
  2. 将服务器的IP地址和端口号绑定到套接字。
  3. 监听连接请求,创建一个连接队列。
  4. 循环等待客户端的连接请求,接受客户端连接,返回一个新的连接套接字。
  5. 通过连接套接字与客户端进行数据交换。
  6. 关闭连接套接字。

客户端:

  1. 创建套接字。
  2. 准备目标服务器的IP地址和端口号。
  3. 连接到目标服务器。
  4. 与服务器进行数据交换。
  5. 关闭连接套接字。

2.2 UDP

UDP是一个无连接的协议,它不保证数据的可靠传输,但传输效率较高。以下是你描述的UDP通信的步骤:

服务端:

  1. 创建套接字。
  2. 绑定服务器的IP地址和端口号。
  3. 接收客户端的数据包,从中获取客户端的地址信息。
  4. 根据客户端的地址信息,向客户端发送数据。
  5. 关闭套接字。

客户端:

  1. 创建套接字。
  2. 准备目标服务器的IP地址和端口号。
  3. 向服务器发送数据。
  4. 根据自己的逻辑处理来自服务器的响应。
  5. 关闭套接字。

总之,TCP提供了可靠的数据传输机制,适用于那些需要确保数据完整性和顺序的应用场景。而UDP则更适合那些需要快速传输、实时性要求高,但可以容忍数据丢失的应用场景。

三、 相关函数解析

3.1 创建套接字socket

   man 2 socket

NAME

   socket - create an endpoint for communication

SYNOPSIS

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

   int socket(int domain, int type, int protocol);
  • NAME: 显示了系统调用的名称,这里是 socket

  • SYNOPSIS: 给出了函数的原型或声明。

    • #include <sys/types.h>: 这是一个C预处理器指令,用于包含类型定义。
    • #include <sys/socket.h>: 这是一个C预处理器指令,用于包含socket函数的声明。
    • int socket(int domain, int type, int protocol);: 这是 socket 函数的原型。它接受三个参数并返回一个整数值。
  • 参数解释:

    • domain: 这个参数指定了套接字的协议家族或地址族。例如,AF_INET 表示使用IPv4协议,AF_INET6 表示使用IPv6协议。其他常见值包括 AF_UNIXAF_LOCAL(用于本地通信)。
    • type: 这个参数指定了套接字的类型。例如,SOCK_STREAM 表示流套接字(通常用于TCP),而 SOCK_DGRAM 表示数据报套接字(通常用于UDP)。
    • protocol: 这个参数通常设置为0,让系统选择合适的协议。但如果需要特定的协议,可以提供它的编号。

3.2 绑定 bind

NAME 名称
bind - 将一个名字和一个套接字绑定到一起(赋一个名字给一个套接字)

SYNOPSIS 概述
#include <sys/types.h>
#include <sys/socket.h>

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

DESCRIPTION 描述
bind 为套接字 sockfd 指定本地地址 my_addr. my_addr 的长度为 addrlen
(字节).传统的叫法是给一个套接字分配一个名字. 当使用 socket(2), 函数创
建一个套接字时,它存在于一个地址空间(地址族), 但还没有给它分配一个名字

  • sockfd:这是要绑定的套接字的文件描述符(socket file descriptor)。在创建套接字后,通过 socket 函数获得。该文件描述符指示操作系统内核中的套接字实例。

  • my_addr:这是一个指向 sockaddr 结构的指针,用于指定本地地址信息。sockaddr 结构是一个通用的套接字地址结构,它的具体类型取决于套接字的地址族(AF_INET、AF_INET6 等)。通常,您需要使用类型转换将 sockaddr_insockaddr_in6 结构的指针强制转换为 sockaddr 结构的指针。

    例如,对于 IPv4 地址,可以这样使用:

    struct sockaddr_in my_addr;
    // 设置 my_addr 中的相关字段
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in));
    

    对于 IPv6 地址,可以使用类似的方法:

    struct sockaddr_in6 my_addr;
    // 设置 my_addr 中的相关字段
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in6));
    
  • addrlen:这是 my_addr 结构的长度,以字节为单位。对于 IPv4 地址,应该是 sizeof(struct sockaddr_in),对于 IPv6 地址,应该是 sizeof(struct sockaddr_in6)

使用步骤如下:

  1. 创建套接字,获取套接字文件描述符 sockfd,例如使用 socket 函数。

  2. 设置 my_addr 结构中的字段,以指定要绑定的本地地址信息。确保 sin_familysin6_family 等字段设置为正确的地址族。

  3. 调用 bind 函数,将套接字 sockfd 与本地地址 my_addr 绑定。

  4. 检查 bind 函数的返回值,如果返回 -1 表示绑定失败,可以通过 perror 函数输出错误信息。

  5. 如果 bind 成功,套接字就与指定的本地地址绑定,其他套接字可以通过该地址与该套接字进行通信。

示例(IPv4 地址):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置本地地址
    struct sockaddr_in my_addr;
    memset(&my_addr, 0, sizeof(struct sockaddr_in));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(8080);  // 设置端口号
    my_addr.sin_addr.s_addr = INADDR_ANY;  // 表示接受任意本地地址

    // 绑定套接字与本地地址
    if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 绑定成功后,可以进行其他操作,比如监听连接、接受连接等

    // 关闭套接字
    close(sockfd);

    return 0;
}

3.3 结构体:struct sockaddr_in

struct sockaddr_in 是用于表示 IPv4 地址的结构体。它定义在头文件 <netinet/in.h> 中。

以下是 struct sockaddr_in 结构体的详细解析:

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* Internet address */
    char           sin_zero[8];/* padding to make structure the same size as sockaddr */
};

每个字段的解释如下:

  1. sin_family

    • 类型:sa_family_t
    • 描述:地址家族(Address Family)。对于 IPv4 地址,这个值通常被设置为 AF_INET
  2. sin_port

    • 类型:in_port_t
    • 描述:端口号,以网络字节顺序(Big Endian)存储。在设置和使用端口时,通常需要使用 htons() 函数将主机字节顺序转换为网络字节顺序。
  3. sin_addr

    • 类型:struct in_addr

    • 描述:一个结构体,用于存储 IPv4 地址。其具体定义如下:

      struct in_addr {
          in_addr_t s_addr;  /* IPv4 address in network byte order */
      };
      

      其中,s_addr 是一个 uint32_t 类型的值,表示 IPv4 地址。

  4. sin_zero

    • 类型:char[8]
    • 描述:这是为了使 struct sockaddr_in 结构体的大小与通用的 struct sockaddr 结构体大小相同而存在的填充字段。在某些早期的实现中,struct sockaddr 的大小是 16 字节,而 struct sockaddr_in 的大小是 16 字节,所以 sin_zero 被用来填充,以确保两者大小相同。但在现代的实现中,struct sockaddr 的大小通常已经被扩展到 28 字节或更多,所以这个填充字段不再是必需的。

当你创建一个 struct sockaddr_in 实例并填充其字段时,你通常会为 sin_family 赋值为 AF_INET,为 sin_port 赋值为你希望使用的端口号,并为 sin_addr.s_addr 赋值为你希望绑定的 IPv4 地址。

3.4 ip IPv4 协议的基本使用

   man 7 ip

NAME (名称)

   ip - Linux IPv4 协议实现

SYNOPSIS(总览)

   #include <sys/socket.h>
   #include <net/netinet.h>

   tcp_socket = socket(PF_INET, SOCK_STREAM, 0);
   raw_socket = socket(PF_INET, SOCK_RAW, protocol);
   udp_socket = socket(PF_INET, SOCK_DGRAM, protocol);
  1. 创建 TCP 套接字:

    tcp_socket = socket(PF_INET, SOCK_STREAM, 0);
    
    • PF_INET:这是协议族(Protocol Family)的标识符,表示 IPv4。
    • SOCK_STREAM:这是套接字类型,表示一个面向连接的、可靠的数据流套接字(即 TCP 套接字)。
    • 0:这是套接字使用的具体协议。在此情况下,它会自动选择默认的协议,即 TCP。
  2. 创建 RAW 套接字:

    raw_socket = socket(PF_INET, SOCK_RAW, protocol);
    
    • SOCK_RAW:这是另一种套接字类型,允许您访问协议的原始数据。创建这种套接字需要特定的权限,并且在大多数情况下,您需要以 root 用户身份运行程序。
    • protocol:这是一个特定于套接字类型的参数。对于 RAW 套接字,您需要指定希望捕获或发送的 IP 协议类型(如 ICMP、IGMP 等)。
  3. 创建 UDP 套接字:

    udp_socket = socket(PF_INET, SOCK_DGRAM, protocol);
    
    • SOCK_DGRAM:这是套接字类型,表示一个无连接的、不可靠的数据报套接字(即 UDP 套接字)。

3.5 网络字节次序的改变:htonl

网络字节顺序(通常称为大端字节顺序)是计算机网络中用于数据传输的标准字节顺序。在大端字节顺序中,最高有效字节(Most Significant Byte,MSB)存储在最低的地址,而最低有效字节(Least Significant Byte,LSB)存储在最高的地址。

如果您的系统是小端字节顺序(MSB 存储在最高的地址,LSB 存储在最低的地址),而您希望与其他大多数网络设备进行通信(它们通常使用大端字节顺序),则需要使用如 htonl()htons() 这样的函数来转换字节顺序。

   man htonl

NAME

   htonl,  htons,  ntohl,  ntohs - convert values between host and network
   byte order

SYNOPSIS

   #include <arpa/inet.h>

   uint32_t htonl(uint32_t hostlong);

   uint16_t htons(uint16_t hostshort);//h版本端口小端模式转网络字节序

   uint32_t ntohl(uint32_t netlong);

   uint16_t ntohs(uint16_t netshort);//n版本网络字节序转端口小端

DESCRIPTION

   The htonl() function converts the unsigned integer hostlong  from  host
   byte order to network byte order.

下面是这些函数的简单描述:

  • htonl(): host to network long。此函数将一个无符号整数从主机字节顺序转换为网络字节顺序。

  • htons(): host to network short。此函数将一个无符号短整数(通常是端口号)从主机字节顺序转换为网络字节顺序。

  • ntohl(): network to host long。此函数将一个无符号整数从网络字节顺序转换回主机字节顺序。

  • ntohs(): network to host short。此函数将一个无符号短整数从网络字节顺序转换回主机字节顺序。

为了确保数据在网络上正确传输,当发送数据之前,通常需要使用 htonl()htons() 转换数据;当接收数据时,通常需要使用 ntohl()ntohs() 进行转换。

例如,如果您想将一个短整数(例如一个端口号)从小端字节顺序转换为网络字节顺序,您可以这样做:

uint16_t port = 12345; // 一个示例端口号
uint16_t network_port = htons(port);

这样,network_port 就是 port 的网络字节顺序表示形式。

3.6 IP地址的转换:inet

这些函数是用于IPv4地址的转换和操作的。

   man inet

NAME

   inet_aton,    inet_addr,    inet_network,   inet_ntoa,   inet_makeaddr,
   inet_lnaof, inet_netof - Internet address manipulation routines

SYNOPSIS

   #include <sys/socket.h>
   #include <netinet/in.h>
   #include <arpa/inet.h>

   int inet_aton(const char *cp, struct in_addr *inp);

   in_addr_t inet_addr(const char *cp);
   
   in_addr_t inet_network(const char *cp);

   char *inet_ntoa(struct in_addr in);//将得到的IP地址转换成点分式的

   struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);

   in_addr_t inet_lnaof(struct in_addr in);

下面是对这些函数的简要描述:

  1. inet_aton()

    • 描述:将点分十进制的IPv4地址字符串转换为二进制的网络字节顺序的32位整数,并存储到struct in_addr结构体中。
    • 参数cp为点分十进制的IPv4地址字符串,inp是一个指向struct in_addr的指针。
    • 返回值:如果转换成功,返回1,否则返回0
  2. inet_addr()

    • 描述:与inet_aton()类似,但是返回的是in_addr_t类型的值。
    • 参数cp为点分十进制的IPv4地址字符串。
    • 返回值:转换成功时返回网络字节顺序的32位整数形式的IPv4地址,失败时返回INADDR_NONE
  3. inet_network()

    • 描述:与inet_addr()类似,但是返回的是网络字节顺序的32位整数形式的IPv4网络地址(即网络号部分)。
    • 参数cp为点分十进制的IPv4地址字符串。
    • 返回值:返回网络字节顺序的32位整数形式的IPv4网络地址。
  4. inet_ntoa()

    • 描述:将二进制的网络字节顺序的32位整数形式的IPv4地址转换为点分十进制的字符串形式。
    • 参数in是一个struct in_addr结构体。
    • 返回值:返回点分十进制的IPv4地址字符串。
  5. inet_makeaddr()

    • 描述:基于给定的网络地址和主机地址创建一个IPv4地址。
    • 参数net为网络地址部分(网络字节顺序),host为主机地址部分(网络字节顺序)。
    • 返回值:返回一个新的in_addr_t值,代表组合的IPv4地址。
  6. inet_lnaof()

    • 描述:从一个struct in_addr结构体中获取主机地址部分。
    • 参数in是一个struct in_addr结构体。
    • 返回值:返回网络字节顺序的32位整数形式的IPv4主机地址。

这些函数在进行网络编程时非常有用,因为它们允许开发者在不同的表示形式之间进行转换,从而使得网络通信更为方便。

3.7 接收一个连接:accept

accept函数在UNIX和Linux系统中用于接受一个传入的连接请求。它常用于服务器端编程,特别是在使用TCP协议时。

man 2 accept

NAME 名称

   accept - 在一个套接字上接收一个连接

SYNOPSIS 概述

   #include <sys/types.h>
   #include <sys/socket.h>

   int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

下面是对accept函数的参数和使用的详细解释:

3.7.1 参数
  1. int s:这是服务器监听套接字的描述符。当客户端尝试连接到服务器时,这个描述符将被用来接受连接。

  2. struct sockaddr *addr:这是一个指向struct sockaddr的指针,用于存储客户端的地址信息。当accept函数成功返回时,这个地址结构会被填充为客户端的地址。

  3. socklen_t *addrlen:这是一个指向socklen_t类型的指针,指向addr结构体的大小。当accept函数成功返回时,这个指针的值会被更新为实际的addr结构体的大小。

3.7.2 返回值
  • 如果accept成功接受了一个连接,它将返回一个新的套接字描述符,该描述符代表与客户端建立的连接。这个新的套接字可以用于与客户端进行数据交换。

  • 如果accept失败,它将返回-1,并设置errno以指示错误的原因。

3.8 建立与远程服务器的连接:connect

connect函数在UNIX和Linux系统中用于建立与远程服务器的连接。当客户端想要与服务器端的特定服务进行通信时,它会使用connect函数。

man 2 connect

NAME

   connect - initiate a connection on a socket

SYNOPSIS

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

   int connect(int sockfd, const struct sockaddr *addr,
               socklen_t addrlen);

以下是对connect函数的参数和使用的详细解释:

3.8.1 参数
  1. int sockfd:这是客户端的套接字描述符。该描述符应该是通过socket函数创建的。

  2. const struct sockaddr *addr:这是一个指向struct sockaddr类型的指针,它包含了服务器的地址信息。在使用TCP/IP时,这通常是一个指向struct sockaddr_in类型的指针,其中包含了服务器的IP地址和端口号。

  3. socklen_t addrlen:这是一个表示addr结构体的大小的值。

3.8.2 返回值
  • 如果connect函数成功建立了连接,它将返回0

  • 如果connect函数失败,它将返回-1,并设置errno以指示错误的原因。

3.9 接收信息recv

man 2 recv
man 2 read

NAME

   recv, recvfrom, recvmsg - receive a message from a socket

SYNOPSIS

   #include <sys/types.h>
   #include <sys/socket.h>

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

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

   ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

    ssize_t read(int fd, void *buf, size_t count);
3.9.1 recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd: 套接字描述符。
  • buf: 接收数据的缓冲区。
  • len: 缓冲区的最大长度。
  • flags: 这是一个标志,可以是0或者其他特定的值。
    • 使用0时,函数默认按照常规方式接收数据。
    • 如果套接字设置为非阻塞模式,并且没有数据可用,recv将返回-1并设置errnoEAGAINEWOULDBLOCK

3.9.2 recvfrom

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd: 套接字描述符。
  • buf: 接收数据的缓冲区。
  • len: 缓冲区的最大长度。
  • flags: 标志,如同上述的recv
  • src_addr: 一个指向struct sockaddr的指针,用于存储发送方的地址信息。
  • addrlen: 一个指向socklen_t的指针,用于指定src_addr结构体的大小。

这里的关键点是,当您使用recvfrom从UDP套接字接收数据时,您不仅接收数据,还会得到发送方的地址。因此,这对于无连接的UDP套接字非常有用。

3.9.3 recvmsg

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

recvmsg提供了一个更加灵活的接口,允许接收来自套接字的多个数据片段(或消息)。它使用一个包含多个缓冲区的struct msghdr来处理这些数据。这在某些高级应用程序或特定的通信场景中可能很有用。

3.9.4 read
ssize_t read(int fd, void *buf, size_t count);
  • fd: 文件描述符,可以是套接字描述符。
  • buf: 用于存储读取数据的缓冲区。
  • count: 要读取的最大字节数。
3.9.5 总结
  • recv: 用于TCP套接字。它从连接的套接字上接收数据。如果没有数据可用,它可能会阻塞,除非设置了非阻塞模式。

  • recvfrom: 用于UDP套接字。除了接收数据,它还可以返回发送方的地址。这在UDP中很有用,因为UDP是无连接的。

  • recvmsg: 提供了更高级的功能,允许从一个套接字接收多个缓冲区的数据。这对于某些特定的应用程序和场景可能很有用。

  • read: 虽然它不是一个套接字特定的函数,但是它可以用于从任何文件描述符(包括套接字)中读取数据。

TCP三个都可以使用,但是UDP是无连接的,因此只能使用recivefrom

3.10 发送信息:send,write

man 2 send
man 2 write

NAME

   send, sendto, sendmsg - 从套接字发送消息

概述

   #include <sys/types.h>
   #include <sys/socket.h>

   int send(int s, const void *msg, size_t len, int flags);
   int  sendto(int s, const void *msg, size_t len, int flags, const struct
   sockaddr *to, socklen_t tolen);
   int sendmsg(int s, const struct msghdr *msg, int flags);

    #include <unistd.h>

   ssize_t write(int fd, const void *buf, size_t count);

当然可以。下面是对这些发送函数的详细解释:

3.10.1 send
int send(int s, const void *msg, size_t len, int flags);
  • s: 套接字描述符。
  • msg: 要发送的数据的指针。
  • len: 要发送的数据的长度。
  • flags: 这是一个标志,用于指定发送操作的行为。
    • 使用0时,函数默认按照常规方式发送数据。
    • 如果套接字设置为非阻塞模式,并且发送缓冲区已满,send将返回-1并设置errnoEAGAINEWOULDBLOCK
3.10.2 sendto
int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
  • s: 套接字描述符。
  • msg: 要发送的数据的指针。
  • len: 要发送的数据的长度。
  • flags: 如同上述的send函数。
  • to: 目标地址的struct sockaddr结构体指针,指明了数据应该发送到哪里。
  • tolen: to指向的struct sockaddr结构体的大小。

使用sendto时,您可以指定数据的接收者地址,这对于UDP套接字或在多播或广播场景中发送数据很有用。

3.10.3 sendmsg
int sendmsg(int s, const struct msghdr *msg, int flags);
  • s: 套接字描述符。
  • msg: 包含要发送数据和其他发送参数的struct msghdr结构体指针。
  • flags: 如同上述的send函数。

sendmsg提供了一个更加灵活的接口,允许发送来自多个缓冲区的数据片段(或消息)。这对于某些高级应用程序或特定的通信场景可能很有用。

3.10.4 write
ssize_t write(int fd, const void *buf, size_t count);

虽然write函数不是发送函数,但它与发送函数有相似的工作原理,因此值得一提。

  • fd: 文件描述符。
  • buf: 要写入的数据的指针。
  • count: 要写入的数据的长度。

write函数用于将数据写入文件描述符,这可能是套接字、文件或其他类型的描述符。在网络编程中,它经常用于发送数据到套接字。

TCP 三个都可以使用,UDP只能使用sendto

3.11 关闭套接字:shutdown,close

man 2 shutdown
man 2 close

NAME

   shutdown - shut down part of a full-duplex connection
   close - 关闭一个文件描述符

SYNOPSIS

   #include <sys/socket.h>

   int shutdown(int sockfd, int how);

    #include <unistd.h>

   int close(int fd);

If how is SHUT_RD,

   further  receptions  will  be  disallowed.   If how is SHUT_WR, further
   transmissions will be disallowed.  If how is SHUT_RDWR, further  recep‐
   tions and transmissions will be disallowed.
3.11.1 shutdown
int shutdown(int sockfd, int how);
  • sockfd: 套接字描述符。
  • how: 定义了如何关闭套接字的方式。
    • SHUT_RD: 关闭套接字的读取方向。这意味着进一步的接收操作(如recvread)将被禁止,但发送操作仍然可以进行。
    • SHUT_WR: 关闭套接字的写入方向。这意味着进一步的发送操作(如sendwrite)将被禁止,但接收操作仍然可以进行。
    • SHUT_RDWR: 同时关闭套接字的读取和写入方向。

这个函数通常在网络编程中用于优雅地关闭一个连接,尤其是当您想确保所有的数据都已经被接收或发送后再关闭连接时。

3.11.2 close
int close(int fd);
  • fd: 文件描述符或套接字描述符。

close函数用于关闭文件描述符。在网络编程中,它经常用于关闭套接字描述符,从而终止与另一端的连接。

当使用close关闭套接字描述符时,任何未发送或未接收的数据都将被丢弃,这可能导致连接重置(RST)消息被发送给另一端,这与shutdown不同,shutdown允许您更加优雅地关闭连接。

文章来源:https://blog.csdn.net/qq_63831368/article/details/135218862
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。