【网络】:网络编程套接字
目录
源IP地址和目的IP地址
源MAC地址和目的MAC地址
源端口号和目的端口号
端口号 VS 进程ID
TCP协议和UDP协议
网络字节序
字符串IP和整数IP相互转换
查看当前网络的状态
socket编程接口
socket常见API
创建套接字(socket)
绑定端口号(bind)
发送数据(send、sendto)
接收数据 (recv、recvfrom)
监听套接字(listen)
接收请求(accept)
建立连接(connect)
sockaddr结构
源IP地址和目的IP地址
一、定义
- 源 IP 地址(Source IP Address):它是发送数据的设备(如计算机、服务器等)在网络中的唯一标识。就像是发送信件时的寄件人地址,在网络通信中,它用于标识数据的来源。
- 目的 IP 地址(Destination IP Address):这是接收数据的设备在网络中的唯一标识,类似于收件人地址。它告诉网络基础设施,数据最终要传送到哪里。
二、在网络通信中的作用
- 数据传输导向:在互联网这样庞大的网络环境中,数据通过多个网络节点(如路由器、交换机等)进行传输。
- 源 IP 地址和目的 IP 地址就像指南针一样,引导数据包从源设备通过复杂的网络路径最终到达目的设备。
- 例如,当你在浏览器中访问一个网站时,你的计算机(源设备)的源 IP 地址和网站服务器(目的设备)的目的 IP 地址就确定了数据传输的起始点和终点。
- 路由决策依据:路由器是网络中的关键设备,用于将数据包从一个网络转发到另一个网络。
- 路由器根据数据包的目的 IP 地址来决定将其转发到哪个下一个网络节点。
- 它会查询路由表,路由表中记录了目的 IP 地址的网络范围以及对应的转发接口。
- 例如,如果一个数据包的目的 IP 地址属于某个特定的外部网络,路由器就会将其从连接外部网络的接口转发出去。
三、举例说明
- 假设你在自己的家中通过 Wi - Fi 连接的笔记本电脑访问一家在线购物网站。
- 你的笔记本电脑(假设其 IP 地址是 192.168.1.10)就是源设备,192.168.1.10 是源 IP 地址。
- 在线购物网站的服务器(假设其 IP 地址是 104.27.189.99)是目的设备,104.27.189.99 是目的 IP 地址。
- 当你在浏览器中输入网站的网址并发送请求时,数据从你的笔记本电脑(源)出发。
- 这个请求数据包包含了源 IP 地址(192.168.1.10)和目的 IP 地址(104.27.189.99)。
- 数据包首先到达你家中的无线路由器,路由器根据目的 IP 地址判断这个数据包需要发送到互联网服务提供商(ISP)的网络,然后经过一系列的网络设备(其他路由器等)的转发,最终到达购物网站的服务器(目的)。
- 在整个过程中,源 IP 地址和目的 IP 地址始终保持不变,确保数据能够准确地发送和接收。
源MAC地址和目的MAC地址
- 源 MAC 地址(Source MAC Address):它是发送数据的网络设备(如计算机网卡、网络交换机端口等)在局域网(LAN)中的物理地址。MAC 地址也被称为硬件地址,是由网络设备制造商固化在设备中的一个 48 位二进制数,通常表示为 12 位的十六进制数,例如 “00 - 1B - 44 - 11 - 3A - 0C”。这个地址在全球范围内是唯一的,用于在局域网环境中标识数据的发送端。
- 目的 MAC 地址(Destination MAC Address):它是接收数据的网络设备在局域网中的物理地址,同样是一个 48 位的二进制数,用于在局域网中标识数据的目标接收端。
有些数据的传输都是跨局域网的,数据在传输过程中会经过若干个路由器,最终才能到达对端主机。
源MAC地址和目的MAC地址是包含在链路层的报头当中的,而MAC地址实际只在当前局域网内有效,因此当数据跨网络到达另一个局域网时,其源MAC地址和目的MAC地址就需要发生变化,因此当数据达到路由器时,路由器会将该数据当中链路层的报头去掉,然后再重新封装一个报头,此时该数据的源MAC地址和目的MAC地址就发生了变化。
因此数据在传输的过程中是有两套地址:
- 一套是源IP地址和目的IP地址,这两个地址在数据传输过程中基本是不会发生变化的(存在一些特殊情况,比如在数据传输过程中使用NET技术,其源IP地址会发生变化,但至少目的IP地址是不会变化的)。
- 另一套就是源MAC地址和目的MAC地址,这两个地址是一直在发生变化的,因为在数据传输的过程中路由器不断在进行解包和重新封装。
源端口号和目的端口号
两台主机之间通信的目的不仅仅是为了将数据发送给对端主机,而是为了访问对端主机上的某个服务(某个进程)。比如我们在用百度搜索引擎进行搜索时,不仅仅是想将我们的请求发送给对端服务器,而是想访问对端服务器上部署的百度相关的搜索服务。
其实,这种在网络中通信的本质:就是进程间通信,只是在不同的机器上进程间通信 (跨网络的进程间通信),我们把这种通信称做 套接字通信(socket通信)
socket通信本质上就是两个进程之间在进行通信,只不过这里是跨网络的进程间通信。比如逛京东和刷抖音的动作,实际就是手机上的京东进程和抖音进程在和对端服务器主机上的京东服务进程和抖音服务进程之间在进行通信。
因此进程间通信的方式除了管道、消息队列、信号量、共享内存等方式外,还有套接字,只不过前者是不跨网络的,而后者是跨网络的。
那我们是如何去实现我们的 socket通信 的呢?
- 先把数据能够到达对方的机器
- 再在机器中能找到指定的进程,同一台机器也可能会同时存在多个正在进行跨网络通信的进程,因此当数据到达对端主机后,必须要通过某种方法找到该主机上对应的服务进程 !
端口号(port)的作用实际就是标识一台主机上的一个进程:
- 端口号是传输层协议的内容
- 端口号是一个2字节16位的整数
- 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
- 一个端口号只能被一个进程占用
当数据在传输层进行封装时,就会添加上对应源端口号和目的端口号的信息。这时通过源IP地址+源端口号就能够在网络上唯一标识发送数据的进程,通过目的IP地址+目的端口号就能够在网络上唯一标识接收数据的进程,此时就实现了跨网络的进程间通信。
注意:
- 因为端口号是属于某台主机的,所以端口号可以在两台不同的主机当中重复,但是在同一台主机上进行网络通信的进程的端口号不能重复。
- 所以,一个进程可以绑定多个端口号,但是一个端口号不能被多个进程同时绑定。
端口号 VS 进程ID
端口号(port)和 进程ID(PID)的作用都是标识一台主机上唯一的一个进程,那在进行socket通信时为什么不直接用 进程ID 呢?
- 进程ID(PID)是用来标识系统内所有进程的唯一性的,它是属于系统级的概念
- 端口号(PORT)是用来标识需要对外进行网络数据请求的进程的唯一性的,它是属于网络的概念
- 一台机器上可能会有大量的进程,但并不是所有的进程都要进行网络通信,可能有很大一部分的进程是不需要进行网络通信的本地进程,此时PID虽然也可以标识这些网络进程的唯一性,但在该场景下就不太合适了
- 进程管理 和 网络 解耦,因为不同平台的 进程ID 的分配可能有所不同,可能会出现不兼容的问题
底层如何通过端口号(PORT)找到对应进程的?
机器底层采用哈希的方式建立了端口号和进程PID或PCB之间的映射关系,当底层拿到端口号时就可以直接执行对应的哈希算法,然后就能够找到该端口号对应的进程。
TCP协议和UDP协议
网络协议栈是贯穿整个体系结构的,在应用层、操作系统层和驱动层各有一部分。当我们使用系统调用接口实现网络数据通信时,面对的协议层就是传输层,而传输层最典型的两种协议就是TCP协议和UDP协议
TCP协议
TCP协议叫做传输控制协议(Transmission Control Protocol),TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP协议是面向连接的,如果两台主机之间想要进行数据传输,那么必须要先建立连接,当连接建立成功后才能进行数据传输。其次,TCP协议是保证可靠的协议,数据在传输过程中如果出现了丢包、乱序等情况,TCP协议都有对应的解决方法。
UDP协议
UDP协议叫做用户数据报协议(User Datagram Protocol),UDP协议是一种无需建立连接的、不可靠的、面向数据报的传输层通信协议。
使用UDP协议进行通信时无需建立连接,如果两台主机之间想要进行数据传输,那么直接将数据发送给对端主机就行了,但这也就意味着UDP协议是不可靠的,数据在传输过程中如果出现了丢包、乱序等情况,UDP协议本身是不知道的。
既然UDP协议是不可靠的,那为什么还要有UDP协议的存在?
TCP协议可靠是需要我们做更多的工作的,TCP协议虽然是一种可靠的传输协议,但这一定意味着TCP协议在底层需要做更多的工作,因此TCP协议底层的实现是比较复杂的,我们不能只看到TCP协议面向连接可靠这一个特点,我们也要能看到TCP协议对应的缺点。
UDP协议虽然是一种不可靠的传输协议,但这一定意味着UDP协议在底层不需要做过多的工作,因此UDP协议底层的实现一定比TCP协议要简单,UDP协议虽然不可靠,但是它能够快速的将数据发送给对方,虽然在数据在传输的过程中可能会出错。
编写网络通信代码时具体采用TCP协议还是UDP协议,完全取决于上层的应用场景。如果应用场景严格要求数据在传输过程中的可靠性,此时我们就必须采用TCP协议,如果应用场景允许数据在传输出现少量丢包,那么我们肯定优先选择UDP协议,因为UDP协议足够简单。
注意: 一些优秀的网站在设计网络通信算法时,会同时采用TCP协议和UDP协议,当网络流畅时就使用UDP协议进行数据传输,而当网速不好时就使用TCP协议进行数据传输,此时就可以动态的调整后台数据通信的算法。
网络字节序
计算机在存储数据时是有大小端的概念的:
- 大端模式: 数据的高字节内容保存在内存的低地址处,数据的低字节内容保存在内存的高地址处。
- 小端模式: 数据的高字节内容保存在内存的高地址处,数据的低字节内容保存在内存的低地址处。
如果编写的程序只在本地机器上运行,那么是不需要考虑大小端问题的,因为同一台机器上的数据采用的存储方式都是一样的
但是如果涉及网络通信,那就必须考虑大小端的问题,否则对端主机识别出来的数据可能与发送端想要发送的数据是相反的。
例如,现在两台主机之间在进行网络通信,其中发送端是小端机,而接收端是大端机。发送端将发送缓冲区中的数据按内存地址从低到高的顺序发出后,接收端从网络中获取数据依次保存在接收缓冲区时,也是按内存地址从低到高的顺序保存的,就会出现想要发送的数据是相反的。
因此TCP/IP协议规定,网络数据流采用大端字节序,即低地址高字节。无论是大端机还是小端机,都必须按照TCP/IP协议规定的网络字节序来发送和接收数据。
- 如果发送端是小端,需要先将数据转成大端,然后再发送到网络当中
- 如果发送端是大端,则可以直接进行发送
- 如果接收端是小端,需要先将接收到数据转成小端后再进行数据识别
- 如果接收端是大端,则可以直接进行数据识别
为什么网络字节序采用的是大端?而不是小端?
网络字节序采用的是大端,而主机字节序一般采用的是小端,那为什么网络字节序不采用小端呢?如果网络字节序采用小端的话,发送端和接收端在发生和接收数据时就不用进行大小端的转换了。
说法一: TCP在Unix时代就有了,以前Unix机器都是大端机,因此网络字节序也就采用的是大端,但之后人们发现用小端能简化硬件设计,所以现在主流的都是小端机,但协议已经不好改了。
说法二: 大端序更符合现代人的读写习惯。
网络字节序与主机字节序之间的转换
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,系统提供了四个函数,可以通过调用以下库函数实现网络字节序和主机字节序之间的转换。
#include <arpa/inet.h>// 将一个 32 位的主机字节序整数转换为网络字节序
uint32_t htonl(uint32_t hostlong);// 将一个 16 位的主机字节序整数转换为网络字节序
uint16_t htons(uint16_t hostshort);// 将一个 32 位的网络字节序整数转换为主机字节序
uint32_t ntohl(uint32_t netlong);// 将一个 16 位的网络字节序整数转换为主机字节序
uint16_t ntohs(uint16_t netshort);
- 函数名当中的h表示主机字节序(host),n表示网络字节序(network),l表示32位长整数,s表示16位短整数
- 例如htonl表示将32位长整数从主机字节序转换为网络字节序
- 如果主机是小端字节序,则这些函数将参数做相应的大小端转换然后返回
- 如果主机是大端字节序,则这些函数不做任何转换,将参数原封不动地返回
字符串IP和整数IP相互转换
IP地址的表现形式有两种:
- 字符串IP:类似于192.168.1.1这种字符串形式的IP地址,叫做基于字符串的点分十进制IP地址
- 整数IP:IP地址在进行网络传输时所用的形式,用一个32位的整数来表示IP地址
192.168.1.1
为例,192
的二进制是11000000
,168
的二进制是10101000
,1
的二进制是00000001
,连接起来得到11000000101010000000000100000001
,转换为十进制就是3232235777
字符串IP和整数IP的相互转换函数
// 字符串IP 转换成 整数IP
in_addr_t inet_addr(const char* cp);// 整数IP转换成字符串IP
char* inet_ntoa(struct in_addr in);
查看当前网络的状态
netstat 命令
netstat
常用选项说明:
- -n:直接使用IP地址,而不通过域名服务器。
- -l:显示监控中的服务器的Socket。
- -t:显示TCP传输协议的连线状况。
- -u:显示UDP传输协议的连线状况。
- -p:显示正在使用Socket的程序识别码和程序名称
netstat命令显示的信息中:
- Proto 表示协议的类型
- Recv-Q 表示网络接收队列
- Send-Q 表示网络发送队列
- Local Address 表示本地地址
- Foreign Address 表示外部地址
- State 表示当前的状态
- PID 表示该进程的进程ID
- Program name 表示该进程的程序名称。
- 其中Foreign Address写成0.0.0.0:*表示任意IP地址、任意的端口号的程序都可以访问当前进程。
socket编程接口
socket常见API
头文件
#include <sys/types.h>
#include <sys/socket.h>
创建套接字:(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);
绑定端口号:(TCP/UDP,服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
监听套接字:(TCP,服务器)
int listen(int sockfd, int backlog);
接收请求:(TCP,服务器)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
建立连接:(TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
发送数据:
// 用于面向连接的套接字,将数据发送到已连接的对等方
ssize_t send(int sockfd, const void *buf, size_t len, int flags);// 用于无连接的套接字或未连接的面向连接套接字,将数据发送到指定地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
接收数据:
// 用于面向连接的套接字,从已连接的对等方接收数据
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);
关闭套接字:
#include <unistd.h>
// 用于关闭套接字,释放资源
int close(int fd);#include <sys/socket.h>
// 可以更灵活地关闭套接字的某些功能,如只关闭发送或接收功能
int shutdown(int sockfd, int how);
创建套接字(socket)
创建套接字(socket)的函数,如下:
int socket(int domain, int type, int protocol);
参数:
- domain:创建套接字的域或者叫做协议家族,也就是创建套接字的类型。该参数就相当于struct sockaddr结构的前16个位
- 如果是本地通信就设置为AF_UNIX
- 如果是网络通信就设置为AF_INET(IPv4)或 AF_INET6(IPv6)。
- type:创建套接字时所需的服务类型。其中最常见的服务类型是SOCK_STREAM和SOCK_DGRAM
- 如果是基于UDP 的网络通信,我们采用的就是SOCK_DGRAM,叫做用户数据报服务,
- 如果是基于TCP 的网络通信,我们采用的就是SOCK_STREAM,叫做流式套接字,提供的是流式服务。
- protocol:创建套接字的协议类别。你可以指明为TCP或UDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。
返回值:
- 套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。
socket函数属于什么类型的接口?
网络协议栈是分层的,按照TCP/IP四层模型来说,自顶向下依次是应用层、传输层、网络层和数据链路层。
我们现在所写的代码都叫做用户级代码,也就是说我们是在应用层编写代码,因此我们调用的实际是下三层的接口,而传输层和网络层都是在操作系统内完成的,也就意味着我们在应用层调用的接口都叫做系统调用接口
socket函数是被谁调用的?
socket这个函数是被程序调用的,但并不是被程序在编码上直接调用的,而是程序编码形成的可执行程序运行起来变成进程,当这个进程被CPU调度执行到socket函数时,然后才会执行创建套接字的代码,也就是说socket函数是被进程所调用的
socket函数底层如何实现的
socket函数是被进程所调用的,而每一个进程在系统层面上都有一个进程地址空间PCB(task_struct)、文件描述符表(files_struct)以及对应打开的各种文件。
而文件描述符表里面包含了一个数组fd_array,其中数组中的0、1、2下标依次对应的就是标准输入、标准输出以及标准错误。
当我们调用socket函数创建套接字时,实际相当于我们打开了一个“网络文件”,打开后在内核层面上就形成了一个对应的struct file结构体,同时该结构体被连入到了该进程对应的文件双链表,并将该结构体的首地址填入到了fd_array数组当中下标为3的位置,此时fd_array数组中下标为3的指针就指向了这个打开的“网络文件”,最后3号文件描述符作为socket函数的返回值返回给了用户。
每一个struct file结构体中包含的就是对应打开文件各种信息,比如文件的属性信息、操作方法以及文件缓冲区等
- 文件对应的属性在内核当中是由struct inode结构体来维护的
- 文件对应的操作方法实际就是一堆的函数指针(比如read*)在内核当中就是由struct file_operations结构体来维护的
- 文件缓冲区对于打开的普通文件来说对应的一般是磁盘,但对于现在打开的“网络文件”来说,这里的文件缓冲区对应的就是网卡
对于一般的普通文件来说,当用户通过文件描述符将数据写到文件缓冲区,然后再把数据刷到磁盘上就完成了数据的写入操作。
而对于现在socket函数打开的“网络文件”来说,当用户将数据写到文件缓冲区后,操作系统会定期将数据刷到网卡里面,而网卡则是负责数据发送的,因此数据最终就发送到了网络当中。
绑定端口号(bind)
绑定的函数叫做bind,该函数的函数原型如下:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
- sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符
- addr:网络相关的属性信息,包括协议家族、IP地址、端口号等
- addrlen:传入的addr结构体的长度
返回值:
- 绑定成功返回0,绑定失败返回-1,同时错误码会被设置
在绑定时需要将网络相关的属性信息(包括协议家族、IP地址、端口号等)填充到一个结构体当中,然后将该结构体作为bind函数的第二个参数进行传入,这实际就是struct sockaddr_in
结构体
struct sockaddr_in结构体
当中的成员如下:
- sin_family:表示协议家族
- 如果是本地通信就设置为 AF_UNIX
- 如果是网络通信就设置为 AF_INET(IPv4)或 AF_INET6(IPv6)
- sin_port:表示端口号,是一个16位的整数
- sin_addr:表示IP地址,是一个32位的整数
- sin_addr结构体中就一个成员(s_addr),该成员就是一个32位的整数,IP地址实际就是存储在这个整数当中的
发送数据(send、sendto)
send
用于面向连接的套接字,将数据发送到已连接的对等方sendto
用于无连接的套接字或未连接的面向连接套接字,将数据发送到指定地址
// 用于面向连接的套接字,将数据发送到已连接的对等方
ssize_t send(int sockfd, const void *buf, size_t len, int flags);// 用于无连接的套接字或未连接的面向连接套接字,将数据发送到指定地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
sockfd
:套接字描述符。buf
:指向要发送数据的缓冲区的指针。len
:要发送数据的长度。flags
:控制发送操作的标志,如MSG_DONTWAIT
(不阻塞)等。dest_addr
(仅sendto
):指向接收方地址的指针。addrlen
(仅sendto
):接收方地址结构体的长度。
返回值:
- 成功时:返回实际发送的字节数。这个数字可能小于
len
,表示只发送了部分数据。这通常发生在以下情况:- 发送缓冲区已满,无法一次性发送全部数据,需要多次调用
send
函数将剩余数据发送完。
- 发送缓冲区已满,无法一次性发送全部数据,需要多次调用
- 失败时:返回 -1
接收数据 (recv、recvfrom)
recv
用于面向连接的套接字,从已连接的对等方接收数据recvfrom
用于无连接的套接字或未连接的面向连接套接字,接收数据并获取发送方地址
// 用于面向连接的套接字,从已连接的对等方接收数据
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);
参数:
sockfd
:套接字描述符。buf
:指向接收数据的缓冲区的指针。len
:缓冲区的长度。flags
:控制接收操作的标志,如MSG_DONTWAIT
(不阻塞)等。src_addr
(仅recvfrom
):指向发送方地址的指针。addrlen
(仅recvfrom
):指向发送方地址结构体长度的指针。
返回值:
- 成功时:返回接收到的字节数,如果连接已经正常关闭,返回值为 0,表示对方已关闭连接。
- 失败时:返回 -1
监听套接字(listen)
int listen(int sockfd, int backlog);
参数:
sockfd
:- 这是一个已经创建并绑定了本地地址的套接字描述符。套接字通常由
socket
函数创建,例如int sockfd = socket(AF_INET, SOCK_STREAM, 0);
。 - 对于
AF_INET
,表示使用 IPv4 协议族;SOCK_STREAM
表示使用面向连接的 TCP 套接字。在调用listen
之前,该套接字需要使用bind
函数绑定到一个本地地址,以便让服务器程序在特定的地址和端口上监听客户端的连接请求。
- 这是一个已经创建并绑定了本地地址的套接字描述符。套接字通常由
backlog
:- 它指定了连接请求队列的最大长度。当多个客户端同时尝试连接服务器时,操作系统会将这些连接请求存储在一个队列中等待服务器处理。
- 例如,将
backlog
设置为 10,意味着最多可以有 10 个未处理的连接请求在队列中等待服务器的accept
操作。设置这个值时需要权衡资源利用和性能,过小可能导致连接请求丢失,过大可能会占用过多的系统资源。
返回值:
- 成功:返回 0,表示套接字已成功进入监听状态,服务器可以接收客户端的连接请求。
- 失败:返回 -1,并设置相应的错误代码。可以使用
perror
函数来输出具体的错误信息。
接收请求(accept)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd
:- 这是一个已经处于监听状态的套接字描述符,通常是由
socket
函数创建,并经过bind
和listen
函数处理后进入监听状态的套接字。例如,int sockfd = socket(AF_INET, SOCK_STREAM, 0);
,并且经过listen(sockfd, backlog);
处理。 - 它代表服务器端的监听套接字,通过该套接字接收客户端的连接请求。
- 这是一个已经处于监听状态的套接字描述符,通常是由
addr
:- 这是一个指向
struct sockaddr
(或其变体,如struct sockaddr_in
对于 IPv4)的指针,用于存储客户端的地址信息。 - 当
accept
成功返回时,这个指针所指向的结构体将被填充为发起连接请求的客户端的地址信息,包括客户端的 IP 地址和端口号。 - 如果不关心客户端的地址信息,可以将此参数设置为
NULL
。
- 这是一个指向
addrlen
:- 这是一个指向
socklen_t
类型的指针,它表示addr
参数所指向的结构体的长度。 - 在调用
accept
之前,需要将其初始化为addr
所指向结构体的大小,例如,对于struct sockaddr_in
,可以使用sizeof(struct sockaddr_in)
。 - 在
accept
返回后,addrlen
的值可能会被修改为实际存储客户端地址信息的长度,尤其是在使用可变长度地址结构体时。
- 这是一个指向
返回值:
- 成功:
accept
函数会返回一个新的套接字描述符,这个新的套接字将用于和客户端进行通信。- 服务器可以使用这个新的套接字进行读写操作,例如使用
send
和recv
函数发送和接收数据。
- 失败:
- 如果
accept
调用失败,它将返回 -1,并设置相应的错误代码。可以使用perror
函数来输出具体的错误信息,常见的错误可能包括系统资源不足、没有连接请求等待等。
- 如果
建立连接(connect)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:这是一个已经创建的客户端套接字描述符addr
:这是一个指向struct sockaddr
(或其变体,如struct sockaddr_in
对于 IPv4 或struct sockaddr_in6
对于 IPv6)的指针,包含了服务器的地址信息。addrlen
:这是addr
所指向的结构体的长度,通常使用sizeof
操作符获取。
返回值:
- 成功:
- 如果
connect
函数调用成功,它将返回 0,表示已经成功连接到服务器。 - 一旦连接成功,客户端就可以使用这个套接字发送和接收数据,例如使用
send
和recv
函数。
- 如果
- 失败:
- 如果
connect
函数调用失败,它将返回 -1,并设置相应的错误代码。可以使用perror
函数输出具体的错误信息。 - 常见的错误可能包括服务器不可达、连接超时、服务器未监听等。
- 如果
sockaddr结构
套接字不仅支持跨网络的进程间通信,还支持本地的进程间通信(域间套接字)。在进行跨网络通信时我们需要传递的端口号和IP地址,而本地通信则不需要,因此套接字提供了sockaddr_in结构体和sockaddr_un结构体:
- sockaddr_in 结构体是用于跨网络通信的
- sockaddr_un 结构体是用于本地通信的
为了让套接字的网络通信和本地通信能够使用同一套函数接口,于是就出现了sockeaddr结构体,该结构体与sockaddr_in和sockaddr_un的结构都不相同,但这三个结构体头部的16个比特位都是一样的,这个字段叫做协议家族
注意: 实际我们在进行网络通信时,定义的还是sockaddr_in这样的结构体,只不过在传参时需要将该结构体的地址类型进行强转为sockaddr* 了。
为什么没有用 void* 代替 struct sockaddr* 类型?
我们可以将这些函数的 struct sockaddr* 参数类型改为 void*,此时在函数内部也可以直接指定提取头部的16个比特位进行识别,最终也能够判断是需要进行网络通信还是本地通信,那为什么还要设计出sockaddr这样的结构呢?
实际在设计这一套网络接口的时候C语言还不支持void*,于是就设计出了sockaddr这样的解决方案。并且在C语言支持了void*之后也没有将它改回来,因为这些接口是系统接口,系统接口是所有上层软件接口的基石,系统接口是不能轻易更改的,否则引发的后果是不可想的,这也就是为什么现在依旧保留sockaddr结构的原因。
相关文章:

【网络】:网络编程套接字
目录 源IP地址和目的IP地址 源MAC地址和目的MAC地址 源端口号和目的端口号 端口号 VS 进程ID TCP协议和UDP协议 网络字节序 字符串IP和整数IP相互转换 查看当前网络的状态 socket编程接口 socket常见API 创建套接字(socket) 绑定端口号&…...

java基础概念55-不可变集合
一、定义 不可变集合:不可以被修改的集合,只能查询。(长度、内容均不可被修改) 二、创建不可变集合 【注意】: 此方法是在JDK9中引入的。 2-1、list不可变集合 示例: import java.util.List;public cla…...
深入理解 C++ 函数重载
在 C 中, 函数重载是一个非常强大的特性, 允许多个函数使用相同的名称, 但具有不同的参数类型. 重载解析决定了在给定的调用中, 编译器应选择哪个版本的重载函数. 本文将深入探讨 C 重载解析的工作原理, 帮助你在实际编程中更好地理解这一机制. 重载(Overload) vs 重写(Overri…...

相机和激光雷达的外参标定 - 无标定板版本
1. 实现的效果 通过本软件实现求解相机和LiDAR的外参,即2个传感器之间的三维平移[x, y, z]和三维旋转[roll, pitch, yaw]。完成标定后,可将点云投影到图像,效果图如下: 本软件的优势:(1)无需特…...
Redis 知识速览
文章目录 1. Redis 简介2. Redis 优缺点3. Redis 高性能4. Redis VM 机制5. Redis 数据类型6. 应用场景7. 持久化8. 过期策略9. 内存相关10. 线程模型11. 事务12. 集群 1. Redis 简介 定义:Redis 是一个用 C 语言编写的高性能非关系型(NoSQL)…...

LeetCode 热题 100_从前序与中序遍历序列构造二叉树(47_105_中等_C++)(二叉树;递归)
LeetCode 热题 100_从前序与中序遍历序列构造二叉树(47_105) 题目描述:输入输出样例:题解:解题思路:思路一(递归): 代码实现代码实现(思路一(递归…...
使用sqlplus的easy connect时如何指定是链接到shared server还是dedicated process
在oracle配置了shared server的情况下 可以使用 :shared来指定链接到shared server也可以默认不指定 不指定的情况下会默认链接到shared server 如果想链接到 dedicated process 则必须显式指定链接到dedicated process server type的类型包括DEDICATED, SHARED, or POOLED. […...

ubuntu22.4 ROS2 安装gazebo(环境变量配置)
ubuntu版本:ubuntu22.4 最近在学习ROS2 视频教程古月居的入门课: 视频教程 文字笔记 问题 在学到关于Gazebo的时候,遇到下面问题: 运行 $ ros2 launch gazebo_ros gazebo.launch.py在这里卡住,不弹出gazebo 解决…...
【机器学习:十四、TensorFlow与PyTorch的对比分析】
1. 发展背景与社区支持 1.1 TensorFlow的背景与发展 TensorFlow是Google于2015年发布的开源深度学习框架,基于其前身DistBelief系统。作为Google大规模深度学习研究成果的延续,TensorFlow从一开始就定位为生产级框架,强调跨平台部署能力和性…...

[C++]类与对象(上)
目录 💕1.C中结构体的优化 💕2.类的定义 💕3.类与结构体的不同点 💕4.访问限定符(public,private,protected) 💕5.类域 💕6.类的实例化 💕7.类的字节大小 💕8.类的字节大小特例…...
大数据技术实训:Zookeeper集群配置
一、本地模式安装部署 1)安装前准备 (1)安装jdk (2)拷贝Zookeeper安装包到Linux系统下 (3)解压到指定目录 tar -zxvf zookeeper-3.5.7.tar.gz -C /opt/module/ 2)配置修改 &am…...
HTML5 加载动画(Loading Animation)
加载动画(Loading Animation)详解 概述 加载动画是指在数据加载过程中,向用户展示的一种视觉效果,旨在提升用户体验,告知用户系统正在处理请求。它可以减少用户的等待焦虑感,提高界面的互动性。 常见的加…...

C语言进阶-2指针(一)
目录 1. 字符指针1.1 一般用法:字符指针指向单字符1.2 第二种用法,字符串首地址给指针变量1.3 习题,下面代码的输出结果是什么?为什么? 2. 指针数组2.1实例—— 字符指针数组2.2实例——整形指针数组2.3 例子,识别下下…...
【人工智能】用Python进行对象检测:从OpenCV到YOLO的全面指南
对象检测是计算机视觉领域的核心任务之一,广泛应用于视频监控、自动驾驶、智能安防等多个场景。随着深度学习技术的发展,基于传统方法的对象检测逐渐被基于神经网络的先进模型所取代。本文将系统地介绍如何使用Python进行对象检测,重点探讨了…...

《深度剖析算法优化:提升效率与精度的秘诀》
想象一下,你面前有一堆杂乱无章的数据,你需要从中找到特定的信息,或者按照一定的规则对这些数据进行排序。又或者,你要为一个物流公司规划最佳的配送路线,以降低成本和提高效率。这些问题看似复杂,但都可以…...

Mysql--重点篇--索引(索引分类,Hash和B-tree索引,聚簇和非聚簇索引,回表查询,覆盖索引,索引工作原理,索引失效,索引创建原则等)
索引是数据库中用于加速查询操作的重要机制。通过索引,MySQL可以快速定位到满足查询条件的数据行,而不需要扫描整个表。合理的索引设计可以显著提高查询性能,但不合理的索引可能会导致性能下降和磁盘空间浪费。因此,理解索引的工作…...
matlab使用 BP 神经网络进行数据预测的完整流程,包括数据读取、数据预处理等等
%% 初始化程序 warning off % 关闭报警信息 close all % 关闭所有图窗 clear % 清空变量 clc % 清空命令行 setdemorandstream(172) %设置随机种子为1%% 读取数据 data xlsread(Y.xlsx); %% 划分训练集…...

systemd-networkd NetworkManager 介绍
systemd-networkd 和 NetworkManager 的详细介绍 systemd-networkd 和 NetworkManager 都是 Linux 系统中常用的网络管理工具,但它们的设计目标和使用场景不同。以下是它们的详细介绍、功能、使用场景和差异。 1. systemd-networkd systemd-networkd 是一个由 syst…...

本地部署项目管理工具 Leantime 并实现外部访问
Leantime 是一款开源 AI 项目。它可以在本地直接运行大语言模型 LLM、生成图像、音频等。直接降低了用户使用AI的门褴。本文将详细的介绍如何利用 Docker 在本地部署 Leantime 并结合路由侠实现外网访问本地部署的 Leantime 。 第一步,本地部署安装 Leantime 1&am…...
PHP cURL 函数初学者完全指南
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...