【Linux | 网络】网络编程套接字

发布于:2025-07-08 ⋅ 阅读:(13) ⋅ 点赞:(0)

在这里插入图片描述

一、预备知识

1.1 理解IP地址

在IP数据报头部中,有两个IP地址,分别叫做源IP地址,和目的IP地址。

思考:我们光有IP地址就可以完成通信了嘛?
IP地址能够标识互联网中的唯一的一台主机,想象一下发qq消息的例子,有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出,这个数据要给哪个程序进行解析。


1.2 认识端口号

两台主机间通信并不是使用机器通信,而是使用机器上的程序进行通信,程序也就是进程,所以需要一个标识来标记主机中的进程,也就是这里所讲的端口号,端口号(port)是传输层协议的内容,能够标识主机中唯一的一个进程

  • 端口号是一个2字节16位的整数
  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程
  • 一个端口号只能被一个进程占用

传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述 “数据是谁发的,要发给谁”。


1.3 理解网络套接字

IP地址能够标识互联网中的唯一的一台主机,端口号能够标识主机中唯一的一个进程。也就是说{IP地址,port},就能够标识网络中唯一的一个进程,使用网络进行通信的本质就是使用{IP地址,port}进行通信的,它的名字叫做网络套接字。


1.4 理解 “端口号” 和 “进程ID”

我们之前在学习系统编程的时候,学习了 PID 用来表示唯一一个进程;此处我们的端口号也是唯一表示一个进程。那么这两者之间是怎样的关系?

PID是用来标识主机上的进程的,port是用来标识主机上用来进行网络通信的进程的。由于主机上并不是所有进程都需要进行网络通信的,并且我们希望其他模块(进程模块)与网络模块进行解耦,所以就有了端口号的概念。

一个端口号通常与一个进程进行绑定,一个进程也可以绑定多个端口号,但是一个端口号不能被多个进程绑定。


1.5 认识TCP协议与UDP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)和UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识,后面我们再详细讨论TCP和UDP的一些细节问题。大家不要被两个协议特点所迷惑,这两个协议只有不同,没有好坏

TCP协议的特点

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议的特点

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

1.6 网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
  • 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据
  • 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可

二、socket编程接口

2.1 socket 常见API

// 头文件
#include <sys/types.h>          
#include <sys/socket.h>
#include <netinet/in.h> 
#include <arpa/inet.h>

2.1.1 socket函数

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

功能:创建一个新的套接字。

参数

  • domain:指定协议族(如AF_INET用于IPv4,AF_INET6用于IPv6)。
  • type:指定套接字类型(如SOCK_STREAM用于TCP,SOCK_DGRAM用于UDP)。
  • protocol:通常指定为0,让系统自动选择协议。

返回值:成功时返回套接字文件描述符,失败时返回-1并设置errno。


2.1.2 bind函数

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);

功能:将套接字与特定的IP地址和端口号绑定。

参数

  • socket:要绑定的套接字文件描述符。
  • address:指向包含IP地址和端口号的sockaddr结构的指针。
  • address_len:address结构的大小。

返回值:成功时返回0,失败时返回-1并设置errno。


2.1.3 listen函数

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

功能:使套接字进入监听状态,准备接受连接请求。

参数

  • socket:要监听的套接字文件描述符。
  • backlog:指定系统应为相应套接字排队的最大连接数。

返回值:成功时返回0,失败时返回-1并设置errno。


2.1.4 accept函数

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);

功能:接受一个连接请求。

参数

  • socket:处于监听状态的套接字文件描述符。
  • address:如果不为NULL,将存储客户端的地址信息。
  • address_len:指向address结构大小的指针,函数返回时存储实际大小。

返回值:成功时返回新的套接字文件描述符用于与客户端通信,失败时返回-1并设置errno。


2.1.5 connect函数

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

功能:主动与服务器建立连接。

参数

  • sockfd:客户端套接字文件描述符。
  • addr:指向包含服务器地址信息的sockaddr结构的指针。
  • addrlen:addr结构的大小。

返回值:成功时返回0,失败时返回-1并设置errno。


2.2 主机字节序和网络字节序的转换的函数

// 头文件
#include <arpa/inet.h>

2.2.1 htonl函数

uint32_t htonl(uint32_t hostlong);

功能:将32位无符号整数从主机字节序转换为网络字节序

参数

  • hostlong :是一个 32 位无符号整数,以主机字节序表示。

返回值:返回转换后的以网络字节序表示的 32 位无符号整数。


2.2.2 ntohl函数

uint32_t ntohl(uint32_t netlong);

功能:将32位无符号整数从网络字节序转换回主机字节序

参数

  • netlong:是一个 32 位无符号整数,以网络字节序表示。

返回值:返回转换后的以主机字节序表示的 32 位无符号整数。


2.2.3 htons函数

uint16_t htons(uint16_t hostshort);

功能:将16位无符号整数从主机字节序转换为网络字节序

参数

  • hostshort:是一个 16 位无符号整数,以主机字节序表示。

返回值:返回转换后的以网络字节序表示的 16 位无符号整数。


2.2.4 ntohs函数

uint16_t ntohs(uint16_t netshort);

功能:将16位无符号整数从网络字节序转换回主机字节序

参数

  • netshort :是一个 16 位无符号整数,以网络字节序表示。

返回值:返回转换后的以主机字节序表示的 16 位无符号整数。


2.3 IP地址形式转换的函数

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

2.3.1 inet_aton函数

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

功能:将点分十进制的IPv4地址字符串转换为网络字节序的二进制形式。

参数

  • cp :是指向以null结尾的IPv4地址字符串的指针;
  • inp :是指向 struct in_addr 结构的指针,用于存储转换后的地址。

返回值

  • 如果转换成功,则返回非零值;
  • 如果转换失败,则返回零。

2.3.2 inet_addr函数

in_addr_t inet_addr(const char *cp);

功能:将点分十进制字符串形式的 IP 地址转换为 32 位二进制整数形式(网络字节序)。

参数

  • cp:指向点分十进制字符串形式 IP 地址的指针。

返回值:如果转换成功,返回转换后的 32 位二进制整数;如果转换失败,返回 INADDR_NONE(通常为 0xffffffff)。不过需要注意的是,INADDR_NONE 也是一个有效的 IP 地址(255.255.255.255),这可能会导致一些混淆,因此更推荐使用 inet_aton 函数。


2.3.3 inet_network函数

in_addr_t inet_network(const char *cp);

功能:将点分十进制字符串形式的 IP 地址转换为 32 位二进制整数形式(主机字节序),并且通常用于提取网络号。

参数

  • cp:指向点分十进制字符串形式 IP 地址的指针。

返回值:如果转换成功,返回转换后的 32 位二进制整数;如果转换失败,返回 -1。


2.3.4 inet_ntoa函数

char *inet_ntoa(struct in_addr in);

功能:将网络字节序的IPv4地址(struct in_addr)转换回点分十进制的字符串形式。

参数

  • in:是包含要转换地址的 struct in_addr 结构。

返回值:返回一个指向静态分配的、以null结尾的字符串的指针,该字符串包含转换后的点分十进制IPv4地址。注意,返回的字符串指针指向一个静态分配的区域,因此每次调用 inet_ntoa 时,上次调用返回的字符串可能会被覆盖。


2.4 套接字中发送/接收数据的函数

2.4.1 recvfrom函数

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

功能:从指定的套接字接收数据报,并可选地获取发送方的地址信息。

参数

  • sockfd:套接字文件描述符,指向一个已绑定并处于监听状态的UDP套接字。
  • buf:指向用于存储接收到的数据报的缓冲区。
  • len:缓冲区的大小,即最多可以接收的字节数。
  • flags:通常设置为0,但可以指定如MSG_PEEK(查看数据但不移除)等标志。
  • src_addr:指向sockaddr结构的指针,用于存储发送方的地址信息。如果不需要此信息,可以设置为NULL。
  • addrlen:指向socklen_t变量的指针,用于输入src_addr结构的大小,并在函数返回时存储实际写入src_addr的地址长度。如果src_addr为NULL,则此参数也应为NULL。

返回值

  • 如果成功,它将返回一个 ssize_t 类型的值,表示接收到的字节数。
  • 如果发生错误,它将返回 -1,并且全局变量 errno 将被设置为一个描述错误的代码。

2.4.2 sendto函数

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

功能:用于向指定的地址发送数据报。

参数

  • sockfd:套接字文件描述符,指向一个已绑定(或未绑定但使用了默认路由)的UDP套接字。
  • buf:指向包含要发送的数据报的缓冲区。
  • len:要发送的数据报的长度(字节数)。
  • flags:通常设置为0,但可以指定如MSG_DONTWAIT(非阻塞发送)等标志。
  • dest_addr:指向sockaddr结构的指针,包含了目标地址的信息。
  • addrlen:dest_addr结构的大小。

返回值

  • 函数返回时,如果成功,它将返回一个 ssize_t 类型的值,表示发送的字节数。通常,这个值应该等于 len,除非发生了错误或发送被中断。
  • 如果发生错误,sendto 将返回 -1,并且全局变量 errno 将被设置为一个描述错误的代码。

2.5 sockaddr结构

网络编程中,socket分为很多种类:

  1. unix socket:也被称为域socket,它允许在同一台主机上的不同进程之间通过文件系统路径进行通信。
  2. 网络socket:它允许在不同主机上之间进行通信,也允许同一台主机上的不同进程之间进行通信。
  3. 原始socket:它允许程序绕过操作系统的网络协议栈,直接发送和接收原始数据包。

由于设计者想用同一套接口就解决上面应用场景,所以就设计出来sockaddr_in结构体类型,通过下图我们可以看到sockaddr_in结构体与sockaddr_un结构体都有一个地址类型,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。

在这里插入图片描述


结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到