Linux的Socket开发补充
是listen函数阻塞等待连接,还是accept函数阻塞等待连接?
这两个函数的名字,听起来像listen一直在阻塞监听,有连接了就accept,但其实不是的。
调用
listen()后,程序会立即返回,继续执行后续代码,不会因为等待客户端连接而停止。listen()的主要作用是设置套接字的状态,告诉内核该套接字现在处于监听状态,可以接受客户端的连接请求。
accept()函数用于接受客户端的连接请求。当有客户端连接到服务器时,accept()会创建一个与客户端通信的新套接字,并返回这个新套接字的文件描述符。在默认情况下,accept()函数是阻塞的。这意味着如果调用accept()时没有客户端连接请求到来,程序会一直阻塞在accept()调用处,直到有新的客户端连接请求到达。然而,如果将套接字设置为非阻塞模式,那么accept()在没有客户端连接请求时会立即返回一个错误,而不是阻塞等待。
socket服务端accept之后就recv读数据,会不会这时候客户端还没send结束,导致没有数据可读呢?
socket服务端在accept之后就recv读数据,确实可能会遇到客户端还没send结束,导致没有数据可读的情况。以下是对这一问题的详细分析:
TCP协议特性
流式传输:TCP是面向流的协议,它将数据作为连续的字节流进行传输,而不是一个个独立的数据包。这意味着服务端的recv函数不会因为客户端未发送完整数据包而阻塞等待,而是会返回当前已接收到的数据。如果客户端还没有发送完数据,服务端的recv可能会读取到部分数据,甚至可能读取不到任何数据(即返回0),这取决于客户端发送数据的进度和网络状况。
缓冲机制:TCP协议栈会在内核中为套接字维护接收缓冲区。当客户端发送数据时,数据首先被存储在服务端的接收缓冲区中。服务端的recv函数从接收缓冲区中读取数据。如果客户端发送的数据还没有完全到达服务端的接收缓冲区,或者缓冲区中暂时没有可供读取的数据,recv函数可能会返回较少的数据量,甚至立即返回(如果在非阻塞模式下)。
服务端处理方式
阻塞模式:在默认的阻塞模式下,如果服务端的recv函数在调用时发现没有数据可读,它会阻塞在那里,直到有数据到达或者发生错误(如客户端关闭连接)才会返回。这种情况下,即使客户端还没有发送完数据,服务端也会一直等待,直到有数据可以读取。
非阻塞模式:如果服务端将套接字设置为非阻塞模式,那么recv函数在没有数据可读时不会阻塞,而是立即返回一个错误代码(通常是EAGAIN或EWOULDBLOCK),表示目前没有数据可供读取。这就需要服务端程序能够正确处理这种非阻塞情况下的返回值,避免因为误判而导致程序异常。
客户端发送情况
发送速度:如果客户端发送数据的速度比较慢,或者网络状况不佳导致数据传输延迟较大,那么服务端在接受连接后可能很快就调用了recv函数,但此时客户端可能只发送了一部分数据甚至还没有开始发送数据。在这种情况下,服务端的recv函数可能读取不到任何数据或者只能读取到部分数据。
分批次发送:有些应用程序可能会将数据分批次发送。例如,客户端先发送一个数据包头,然后暂停一下再发送数据内容。如果服务端在客户端发送包头后就立即调用recv函数,那么它可能只能读取到包头信息,而无法读取到后续的数据内容。
综上所述,为了避免这种情况带来的问题,服务端程序需要正确地处理recv函数的返回值,并且可能需要结合使用其他机制(如定时器、心跳包等)来确保数据的完整性和通信的可靠性。
服务端是一次性读完所有的数据,还是分次读取?
服务端的读取策略
阻塞模式:在默认情况下,recv函数是阻塞的。如果服务端调用recv函数时接收缓冲区中没有数据可读,那么recv函数会阻塞在那里,直到有数据到达或者发生错误(如客户端关闭连接)为止。在这种情况下,服务端可能会一直等待,直到接收到完整的数据或连接关闭。然而,这种策略可能导致服务端在高并发场景下性能下降,因为每个连接都可能因为等待数据而占用一个线程或进程。
非阻塞模式:为了提高性能和响应速度,服务端可以将套接字设置为非阻塞模式。在非阻塞模式下,recv函数在没有数据可读时不会阻塞,而是立即返回一个错误代码(通常是EAGAIN或EWOULDBLOCK)。这样,服务端可以在没有数据可读时继续处理其他任务或检查其他连接的状态。然而,这也要求服务端实现更复杂的逻辑来处理数据的读取和解析。
分批读取:考虑到网络延迟、数据包大小以及应用层协议的设计,服务端通常会采用分批读取的策略。即服务端每次只读取一定量的数据(如固定大小的缓冲区),然后进行处理。如果接收到的数据不足以构成一个完整的消息或数据单元,服务端会继续等待并读取后续到达的数据。这种策略可以平衡性能和资源利用率,避免因为等待单个大消息而导致整个服务端性能下降。
综上所述,服务端是否一次性读完所有的数据还是分次读取并没有一个固定的答案。它取决于具体的应用场景、网络状况、数据量大小以及服务端的设计和实现方式。
accept4函数
accept4是accept的一个扩展版本,它提供了更多的功能和灵活性。
accept4和accept都是用于在监听套接字上接受新的连接请求的系统调用,但它们之间存在一些关键的区别。以下是对这两个函数的详细比较:参数列表
accept:原型为
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);。
accept4:原型为
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);。与accept相比,多了一个flags参数,用于设置接受的套接字属性。如设置非阻塞模式或关闭执行时自动关闭文件描述符等。
socket请求和url请求有什么区别?
最近遇到一个问题,请别人帮忙看的时候,对方让提供访问的域名,但是我其实是通过socket去发起请求的,所以没有什么域名,于是就产生了这个疑问:socket请求和url请求有什么区别?
Socket是计算机网络中用于实现不同主机或进程间通信的端点抽象,基于TCP/IP协议族,通过创建Socket对象,指定目的IP地址和端口号,建立连接后实现数据的双向传输。
URL(Uniform Resource Locator)是统一资源定位符,用于完整地描述Internet上网页或其他资源的地址。URL请求通常指的是使用HTTP协议向这些资源地址发起的请求。
想到一个问题,那就是socket中,什么场景下需要结合poll/epoll来使用。
我们知道,poll是多路监听,监听多个socket,如果只有一个socket需要监听,应该用不上poll吧?直接bind-listen-accept即可,有连接来了就recv等等。但是,如果有多个socket,那么这时候就可以使用poll了吧?此时,是先把每个socketfd给bind-listen,然后再poll?poll到了之后就直接accept?accept通常也是阻塞的,这时候是不是因为有poll了所以肯定是有连接的因此不会阻塞了?
问下deepseek看看
使用 Socket 后是否需要结合 poll(或 select/epoll),取决于具体的通信模式、性能需求以及程序架构。以下是关键分析,帮助你判断是否需要使用 poll:
不需要 poll 的情况(简单阻塞模式)
适用场景
单线程顺序处理:程序只需处理一个 Socket,且可以接受阻塞等待。
简单请求-响应模型:客户端发送请求后,同步等待服务端回复,无并发需求。
低实时性要求:对延迟不敏感,允许线程在 recv() 或 accept() 时阻塞。
示例代码(无 poll)
// 客户端阻塞发送和接收 send(sockfd, request, strlen(request), 0); recv(sockfd, response, sizeof(response), 0); // 阻塞直到数据到达// 服务端阻塞监听 while (1) {int client_fd = accept(sockfd, NULL, NULL); // 阻塞直到新连接recv(client_fd, buf, sizeof(buf), 0); // 阻塞读取数据 }优点
代码简单,逻辑直观。
无需管理多路复用或事件驱动。
缺点
无法同时处理多个连接或事件。
线程在阻塞时会闲置,浪费资源。
需要 poll 的情况(高性能/并发场景)
适用场景
多路复用:需要同时监听多个 Socket(如多个客户端连接或混合 TCP/UDP/RPMSG)。
非阻塞操作:避免线程因某个 Socket 的延迟而卡死,提升实时性。
超时控制:在指定时间内等待数据,超时后执行其他逻辑。
事件驱动架构:如高性能服务器、实时系统或 GUI 应用。
示例代码(使用 poll)
#include <poll.h>struct pollfd fds[2]; fds[0].fd = sockfd1; // RPMSG Socket fds[0].events = POLLIN; fds[1].fd = sockfd2; // TCP Socket fds[1].events = POLLIN;while (1) {int ret = poll(fds, 2, 1000); // 超时 1 秒if (ret < 0) {perror("poll");break;} else if (ret == 0) {printf("Timeout, no events.\n");continue;}// 检查 RPMSG Socket 是否有数据if (fds[0].revents & POLLIN) {recv(fds[0].fd, buf, sizeof(buf), 0);printf("RPMSG data: %s\n", buf);}// 检查 TCP Socket 是否有新连接if (fds[1].revents & POLLIN) {int client_fd = accept(fds[1].fd, NULL, NULL);// 处理新连接...} }优点
高效并发:单线程即可处理多个 Socket。
灵活控制:支持超时、非阻塞和优先级事件处理。
资源友好:减少线程数量,降低上下文切换开销。
缺点
代码复杂度较高,需处理事件状态(revents)。
需要理解 poll/select/epoll 的差异(epoll 更适合大规模连接)。
在
poll的使用中,既可以监听 监听 Socket(Listening Socket),也可以监听 已连接 Socket(Connected Socket),具体取决于你的程序角色(服务端或客户端)和需求。以下是详细说明:监听 监听 Socket(服务端场景)
用途:服务端通过监听 Socket 接受新的客户端连接(accept())。
触发条件:当有新连接请求到达时,poll 会返回该 Socket 的可读事件(POLLIN)。
代码示例:
int listen_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听 Socket bind(listen_fd, ...); // 绑定地址 listen(listen_fd, 5); // 开始监听struct pollfd fds[1]; fds[0].fd = listen_fd; // 监听 Socket fds[0].events = POLLIN; // 关注可读事件(新连接)while (1) {int ret = poll(fds, 1, 1000); // 等待事件if (ret > 0 && (fds[0].revents & POLLIN)) {int client_fd = accept(listen_fd, NULL, NULL); // 接受新连接printf("New client connected: %d\n", client_fd);} }关键点:
仅服务端需要监听“监听 Socket”。
POLLIN 表示有新连接到达,需调用 accept()。
监听 已连接 Socket(服务端/客户端场景)
用途:检测已建立的连接是否有数据可读/可写,或是否发生错误。
触发条件:
POLLIN:数据到达,可调用 recv()。
POLLOUT:Socket 可发送数据(非阻塞模式下有用)。
POLLERR:Socket 发生错误。
代码示例:
int client_fd = accept(listen_fd, ...); // 服务端获取已连接 Socket // 或 int client_fd = connect(...); // 客户端建立连接struct pollfd fds[1]; fds[0].fd = client_fd; // 已连接 Socket fds[0].events = POLLIN; // 关注可读事件while (1) {int ret = poll(fds, 1, 1000);if (ret > 0 && (fds[0].revents & POLLIN)) {char buf[1024];int len = recv(client_fd, buf, sizeof(buf), 0); // 读取数据if (len > 0) printf("Received: %s\n", buf);} }关键点:
适用于服务端(处理客户端连接)和客户端(监听服务端回复)。
可同时监听多个已连接 Socket(多客户端场景)。
比如,我们可以把多个连接socket放一起监听,有数据再去recv。
上面两种场景,总结就是,在accept之前poll,有连接就accept;再就是在recv之前poll,有数据就recv。这种场景下,accept或者recv设置成非阻塞也是可以的。
同时监听 监听 Socket 和 已连接 Socket(服务端经典模型)
服务端通常需要同时监听新连接和已连接 Socket 的数据:
struct pollfd fds[MAX_CLIENTS + 1]; fds[0].fd = listen_fd; // 监听 Socket fds[0].events = POLLIN;int nfds = 1; // 当前监听的 Socket 数量while (1) {int ret = poll(fds, nfds, -1); // 阻塞等待事件if (ret <= 0) continue;// 1. 检查新连接if (fds[0].revents & POLLIN) {int client_fd = accept(listen_fd, NULL, NULL);fds[nfds].fd = client_fd; // 将新连接加入 poll 监听fds[nfds].events = POLLIN;nfds++;}// 2. 检查已连接 Socket 的数据for (int i = 1; i < nfds; i++) {if (fds[i].revents & POLLIN) {char buf[1024];int len = recv(fds[i].fd, buf, sizeof(buf), 0);if (len <= 0) {close(fds[i].fd); // 连接关闭或错误fds[i].fd = -1; // 从 poll 中移除} else {printf("Data from client %d: %s\n", fds[i].fd, buf);}}} }若是监听 Socket 的
POLLIN,则调用accept();若是已连接 Socket,则调用recv()。
accept跟三次握手有什么关系?
在 TCP 协议中,accept() 的调用时机确实与三次握手有关,但它的行为还受到操作系统内核和 Socket 配置的影响。以下是详细分析:
正常情况下:accept() 在三次握手完成后返回
流程:
客户端发送 SYN → 服务端(监听 Socket 收到 SYN,进入 SYN_RCVD 状态)。
服务端回复 SYN+ACK → 客户端。
客户端发送 ACK → 服务端(完成三次握手,连接进入 ESTABLISHED 状态,并加入内核的已完成连接队列)。
服务端的 accept() 从已完成队列中取出一个连接,返回新的已连接 Socket。
关键点:
accept() 本身不参与三次握手,它只是从内核队列中获取已建立的连接。
如果队列为空,accept() 会阻塞(默认行为),直到有新连接到达。
异常情况:accept() 可能延迟返回
(1) 已完成连接队列已满
内核会维护一个已完成连接队列(accept queue),其大小由 listen(fd, backlog) 的 backlog 参数和系统配置(如 net.core.somaxconn)共同决定。
如果队列满:
客户端发送的第三个 ACK 可能被丢弃,导致客户端认为连接已建立,但服务端 accept() 无法立即返回。
客户端后续发送的数据会被服务端忽略(触发客户端重传)。
(2) 监听 Socket 设置为非阻塞
如果监听 Socket 设置为非阻塞(fcntl(fd, F_SETFL, O_NONBLOCK)):
当已完成队列为空时,accept() 会立即返回 -1,并设置 errno=EAGAIN 或 EWOULDBLOCK。
需结合 poll/epoll 监听 POLLIN 事件后再调用 accept()。
(3) 客户端快速关闭连接
如果客户端在三次握手完成后立即发送 RST(如端口未开放),accept() 可能返回错误(取决于操作系统实现)。
close和四次挥手有什么关系?
在 TCP 协议中,
close()系统调用会触发连接的四次挥手过程,这是 TCP 可靠关闭连接的标准机制。以下是详细分析:四次挥手的基本流程
当一端(假设为客户端)调用
close()关闭 Socket 时,TCP 会启动四次挥手:FIN(客户端 → 服务端)
客户端调用
close()后发送FIN报文,进入FIN_WAIT_1状态,表示不再发送数据。ACK(服务端 → 客户端)
服务端收到
FIN后回复ACK,进入CLOSE_WAIT状态,此时服务端可能仍有数据要发送。FIN(服务端 → 客户端)
注意,这时候,只能服务端向客户端发消息,因为客户端已经不再发消息了。
服务端调用
close()后发送FIN报文,进入LAST_ACK状态。ACK(客户端 → 服务端)
客户端收到
FIN后回复ACK,进入TIME_WAIT状态,服务端收到后连接彻底关闭。上面其实就是两轮FIN和ACK。客户端先向服务端发送FIN(第一次),接着就收到服务端的ACK(第二次),这时候,客户端虽然不能发送但是还能接收数据,服务端也能向客户端发送数据。这时候,双方还处于半关闭的状态。接着等服务端发送FIN(第三次),客户端再返回ACK(第四次),服务端收到后就马上进入CLOSED状态,客户端在一段定时时间后也会进入CLOSED状态,这时双方才真正地断开了连接。
close()在四次挥手中的作用(1) 主动关闭方(客户端)
close(sockfd); // 发送 FIN,启动四次挥手
行为:
内核立即发送
FIN报文(若发送缓冲区无残留数据)。若对端未关闭,连接进入
FIN_WAIT_1状态。(2) 被动关闭方(服务端)
// 服务端检测到 FIN 后,需显式调用 close() 完成挥手 char buf[1024]; int len = recv(sockfd, buf, sizeof(buf), 0); if (len == 0) { // 收到 FIN(EOF)close(sockfd); // 发送 FIN,完成四次挥手 }行为:
收到
FIN后,内核自动回复ACK,但需应用程序调用close()发送最终的FIN。总结
close()是触发四次挥手的应用程序接口,而四次挥手是 TCP 层的行为。完整挥手需要双方协作:主动关闭方发起,被动关闭方响应。
理解状态机(如
TIME_WAIT)对调试高并发服务至关重要。
直接使用socket编程,是不是就不涉及什么应用层协议
直接使用 Socket 编程 仍然可能涉及 应用层协议,但具体是否涉及取决于你的实现方式。以下是关键分析:
Socket 是传输层接口,不强制应用层协议
Socket 编程(如 Linux 的
socket()、Windows 的Winsock)提供的是 传输层(TCP/UDP)的通信能力,它本身不限定应用层协议。你可以完全自定义数据格式,不依赖 HTTP、FTP 等标准协议。
也可以手动实现简单协议,例如:
// 发送一个自定义结构体(无标准应用层协议) struct CustomData {int id;float value;char name[32]; }; send(socket_fd, &data, sizeof(data), 0);这种场景下,没有使用标准应用层协议,但仍需双方约定数据格式(类似私有协议)。
什么情况下不涉及应用层协议?
如果满足以下条件,可以认为 不涉及标准应用层协议:
数据格式完全由开发者定义(如裸二进制数据、自定义结构体)。
通信双方直接解析原始字节流,无需遵循 HTTP/MQTT 等规范。
仅用于局域网或特定设备间通信,无需跨网络兼容性。
示例:
嵌入式设备间通过 UDP 发送传感器数据(直接
sendto()二进制数据)。游戏客户端和服务器的自定义协议(如 Protobuf + TCP)。
什么情况下仍会涉及应用层协议?
即使直接使用 Socket,以下情况仍会 隐式或显式依赖应用层协议:
使用标准协议库
例如:在 Socket 上手动实现 HTTP 请求(拼接
GET /path HTTP/1.1\r\nHost:...)。虽然未用
libcurl,但仍遵循 HTTP 规范(应用层协议)。依赖第三方序列化工具
例如:通过 Socket 发送 JSON/XML(虽非标准协议,但属于应用层数据格式)。
混合使用底层和高级协议
例如:用 Socket 传输 TLS 加密数据(TLS 属于会话层/表示层,但通常归类为应用层安全协议)。
典型场景对比
场景 是否涉及应用层协议? 说明 通过 Socket 发送 0xAA 0xBB ❌ 不涉及 纯二进制,无格式约定。 用 Socket 实现 HTTP 客户端 ✔️ 涉及 需遵循 HTTP 报文格式。 自定义协议(如 ID+LEN+DATA) ❌ 不涉及(严格来说算私有协议) 双方自行约定格式。 使用 Socket + Protobuf ⚠️ 部分涉及 Protobuf 是数据序列化格式,非标准协议。 关键结论
Socket 本身不强制应用层协议,但实际通信通常需要某种数据格式约定(即使是私有协议)。
完全避免应用层协议:需直接操作原始字节流,且双方硬编码解析逻辑。
“不涉及应用层协议” ≠ 无协议:开发者仍需定义数据结构和交互规则(只是未用现成标准)。
如果需要极简通信(如单片机间传输),可直接用 Socket + 二进制数据;若需兼容性(如 Web 服务),则需实现 HTTP 等应用层协议。
大多数网络应用层协议都是对socket的封装吗
是的,大多数网络应用层协议都可以看作是对 Socket API 的封装或更高层次的抽象,但具体实现方式可能有所不同。以下是详细分析:
1. Socket 的作用
Socket 是操作系统提供的 网络通信基础接口,位于传输层(TCP/UDP)之上,负责:
建立连接(如
connect()、accept())数据传输(如
send()、recv())管理网络地址和端口(如
bind()、listen())应用层协议需要通过 Socket 实现底层通信,但协议本身会定义更高级的规则(如消息格式、状态机等)。
2. 应用层协议与 Socket 的关系
(1) 直接基于 Socket 实现的协议
许多经典协议直接封装 Socket API,例如:
HTTP/1.1:通过 TCP Socket 传输文本请求和响应。
SMTP/POP3:基于 TCP Socket 的明文命令交互。
FTP:使用两个 Socket(控制通道 + 数据通道)。
这些协议在 Socket 之上定义了 报文格式 和 交互逻辑(如 HTTP 的
GET / POST方法)。(2) 使用中间库封装的协议
现代开发中,协议可能通过更高级的库实现,但这些库底层仍依赖 Socket:
HTTP/2、gRPC:使用库(如 libcurl、gRPC Core)封装 Socket,支持多路复用、二进制帧等。
WebSocket:基于 TCP Socket,但通过 HTTP 升级握手建立连接。
(3) 非 Socket 的实现
少数场景下,协议可能绕过标准 Socket:
自定义传输层:如 QUIC(基于 UDP 的 HTTP/3)直接操作 UDP Socket,并实现可靠性保证。
内核旁路技术:如 DPDK,但这类方案仍需底层网络接口支持。
3. 为什么说“封装”是合理的?
抽象复杂性:Socket 只提供字节流传输,而应用层协议需要处理:
报文分割(如 HTTP 的
Content-Length)。状态管理(如 TLS 握手)。
错误恢复(如 SMTP 的重试机制)。
标准化交互:协议定义了语义(如 RESTful API 的 URI 和 Method),而 Socket 仅负责传输。
4. 例外情况
纯消息队列协议:如 AMQP、MQTT,可能依赖更底层的 Socket 封装库(如 ZeroMQ)。
RPC 框架:如 gRPC,抽象了 Socket,开发者只需关注接口定义(Protocol Buffers)。
总结
大多数应用层协议可以视为 Socket 的语义化封装,但现代开发中通常会通过高级库(如 HTTP 客户端、gRPC 框架)进一步隐藏 Socket 细节。协议的核心价值在于定义 通信规则,而 Socket 是其实现的 底层工具。
常见地址宏定义
Linux中常见的一些socket ip地址常量宏
在Linux编程中,使用socket进行网络通信时,经常需要指定IP地址。为了方便和标准化,系统定义了一些常见的IP地址常量宏。这些宏通常在头文件
<netinet/in.h>中定义。以下是一些常见的IP地址常量宏:INADDR_ANY
值:
0x00000000IP地址:
0.0.0.0用途: 用于绑定一个套接字到所有可用的网络接口上。这通常用于服务器应用程序,使其能够从任何网络接口接收外部连接。
INADDR_LOOPBACK
值:
0x7f000001IP地址:
127.0.0.1用途: 表示本地回环地址(loopback address)。用于在同一台机器上的进程之间进行通信,不涉及任何网络接口。
INADDR_BROADCAST
值:
0xffffffffIP地址:
255.255.255.255用途: 表示广播地址。用于发送数据包到网络上的所有主机。
INADDR_NONE
值:
0xffffffffIP地址:
255.255.255.255用途: 表示无效的IP地址。当解析IP地址失败时,返回这个值。
IN_LOOPBACKNET
值:
127用途: 表示回环网络号。用于识别回环地址。
IN_CLASSA_NET
值:
0xff000000用途: 表示A类网络的网络掩码。
IN_CLASSB_NET
值:
0xffff0000用途: 表示B类网络的网络掩码。
IN_CLASSC_NET
值:
0xffffff00用途: 表示C类网络的网络掩码。
IN_CLASSD_NET
值:
0xe0000000用途: 表示D类网络的网络掩码。
IN_MULTICAST(i)
值:
0xe0000000 + (i)用途: 表示多播地址范围。
IN_EXPERIMENTAL(i)
值:
0xf0000000 + (i)用途: 表示实验性地址范围。
IN_RFC3021_MASK
值:
0xffffff00用途: 表示RFC 3021定义的默认子网掩码。
IN_CLASSA_NSHIFT, IN_CLASSA_HOST, IN_CLASSA_MAX
值:
8,0xffffff00,16777216用途: 分别表示A类网络的子网位数、主机部分的掩码和最大主机数。
IN_CLASSB_NSHIFT, IN_CLASSB_HOST, IN_CLASSB_MAX
值:
16,0xffff0000,65536用途: 分别表示B类网络的子网位数、主机部分的掩码和最大主机数。
IN_CLASSC_NSHIFT, IN_CLASSC_HOST, IN_CLASSC_MAX
值:
24,0xffffff00,256用途: 分别表示C类网络的子网位数、主机部分的掩码和最大主机数。
这些常量宏在编写网络程序时非常有用,可以帮助开发者更方便地处理IP地址和网络相关的操作。
重点理解以下四种:
INADDR_ANY
INADDR_LOOPBACKINADDR_BROADCAST
INADDR_NONE
INADDR_LOOPBACK和INADDR_ANY是两个在网络编程中常用的常量,它们分别代表不同的IP地址,用于指定服务器绑定的地址。以下是它们的主要区别:INADDR_LOOPBACK (127.0.0.1)
用途: 表示本地回环地址(loopback address)。
功能: 用于在同一台机器上的进程之间进行通信,不涉及任何网络接口。
典型场景: 主要用于测试和开发环境,确保服务只接受来自本地主机的连接。
安全性: 因为只有本地进程可以连接到这个地址,所以它提供了一种简单的方法来限制访问,仅允许本地访问。
INADDR_ANY (0.0.0.0)
用途: 表示所有可用的网络接口。
功能: 用于绑定一个套接字到所有可用的网络接口上,使得该服务可以从任何网络接口接收连接请求。
典型场景: 通常用于服务器应用程序,使其能够从任何网络接口接收外部连接。
灵活性: 提供更高的灵活性,因为服务器可以接受来自任何网络接口的连接,这在多网卡或多IP地址的环境中特别有用。
INADDR_BROADCAST
用途:表示广播地址,用于发送数据包到网络上的所有主机。
IP地址表示:
255.255.255.255。使用场景:通常用于局域网中的广播通信,不会在当前路由器进行转发。
INADDR_NONE
用途:表示无效的IP地址,当解析IP地址失败时,返回这个值。
IP地址表示:
255.255.255.255。使用场景:用于检查IP地址是否有效,或者在某些情况下表示未指定的IP地址。
INADDR_ANY 是一个在网络编程中常用的选项,它用于 socket 函数中,表示服务器应该绑定到所有可用的IP地址上。在多网卡的环境下,这意味着服务器可以接收到发送到任何网卡的连接请求。具体来说,INADDR_ANY 被定义为 0.0.0.0,这个地址在实际上代表了“不确定地址”或“所有地址”、“任意地址”。
代码示例
在使用 socket 进行网络编程时,如果你想让你的服务器监听某个端口,你可以在调用 bind() 函数时使用 INADDR_ANY。这样,无论数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,服务器都可以接收到。例如:
深入理解
当你的服务器有多个网卡,每个网卡上有不同的IP地址时,使用 INADDR_ANY 可以避免确定服务器上有哪些网络端口的麻烦。这在服务器操作系统可能随时增减IP地址的情况下尤其有用。此外,当服务器程序在 0.0.0.0 这个地址上进行监听时,它可以接收到所有网卡上指定端口的数据。
重要考虑
使用 INADDR_ANY 不会导致收到重复的数据包或重复发送数据。这是因为路由的关系,从客户端来的IP包只可能到达其中一个网卡。同时,服务器进程在发送数据时,操作系统会根据维护的路由表决定数据包应该从哪一个出站网关向目标发送。路由表记录有优先级别,不同的操作系统可能会有不同的处理方式。
客户端的情况
对于客户端,如果绑定 INADDR_ANY,在 TCP 连接中,系统调用会将其绑定到一个具体的IP地址。对于 UDP,即使使用 connect() 系统调用也不会绑定到一个具体地址,因为 UDP 是面向无连接的,系统只是将目标地址的信息记录在内部的socket数据结构中,供以后使用。
总的来说,INADDR_ANY 是一个非常有用的选项,它允许服务器在多网卡环境中灵活地接收来自任何网卡的连接请求,而无需单独绑定每个网卡的IP地址。这在实际应用中可以大大简化网络编程的复杂性,并提高服务器的可移植性和灵活性。
网络地址大小端转换
Linux网络通信----htonl()、htons()、ntohl()、ntohs()四个函数
参考:
Linux网络通信----htonl()、htons()、ntohl()、ntohs()四个函数_linux ntohs-CSDN博客
ip点分十进制
注意,C语言中不能直接识别ip地址的点分十进制,我们一般都要写对应的十六进制,比如:
0.0.0.0对应0x00000000;
127.0.0.1对应0x7f000001;
255.255.255.255对应0xffffffff
Netlink通信
Netlink内核和用户空间之间的通信
socket(AF_NETLINK, SOCK_RAW, 0)使用示例
在Linux中,
socket(AF_NETLINK, SOCK_RAW, 0)用于创建一个Netlink套接字。Netlink是一种IPC(进程间通信)机制,主要用于内核和用户空间之间的通信。以下是一个简单的示例代码,演示如何使用Netlink套接字进行基本操作:#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <linux/netlink.h>#define NETLINK_USER 31 #define MAX_PAYLOAD 1024 /* maximum payload size*/struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; int sock_fd; struct msghdr msg;int main() {// Create a Netlink socketsock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USER);if (sock_fd < 0) {perror("socket creation failed");return -1;}// Initialize source address structurememset(&src_addr, 0, sizeof(src_addr));src_addr.nl_family = AF_NETLINK;src_addr.nl_pid = getpid(); /* self pid */src_addr.nl_groups = 0; /* not in mcast groups */// Bind the socket to the source addressif (bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)) < 0) {perror("bind failed");close(sock_fd);return -1;}// Initialize destination address structurememset(&dest_addr, 0, sizeof(dest_addr));dest_addr.nl_family = AF_NETLINK;dest_addr.nl_pid = 0; /* For Linux Kernel */dest_addr.nl_groups = 0; /* unicast */// Allocate memory for the netlink message header and payloadnlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);nlh->nlmsg_pid = getpid();nlh->nlmsg_flags = 0;// Fill the netlink message payloadstrcpy(NLMSG_DATA(nlh), "Hello from user space!");// Prepare the I/O vectoriov.iov_base = (void *)nlh;iov.iov_len = nlh->nlmsg_len;memset(&msg, 0, sizeof(msg));msg.msg_name = (void *)&dest_addr;msg.msg_namelen = sizeof(dest_addr);msg.msg_iov = &iov;msg.msg_iovlen = 1;// Send the message to the kernelif (sendmsg(sock_fd, &msg, 0) < 0) {perror("sendmsg failed");free(nlh);close(sock_fd);return -1;}printf("Message sent to kernel\n");// Clean upfree(nlh);close(sock_fd);return 0; }解释:
创建Netlink套接字:使用
socket(AF_NETLINK, SOCK_RAW, NETLINK_USER)创建一个Netlink套接字。NETLINK_USER是一个自定义的协议号,可以根据需要更改。初始化源地址结构:设置源地址结构体
src_addr,包括协议族、进程ID和组ID。绑定套接字:将套接字绑定到源地址。
初始化目标地址结构:设置目标地址结构体
dest_addr,包括协议族、目标进程ID和组ID。这里的目标进程ID为0,表示发送给内核。分配并填充Netlink消息:分配内存并填充Netlink消息头和负载数据。
准备I/O向量:设置I/O向量以指向Netlink消息。
发送消息:使用
sendmsg函数将消息发送到内核。清理资源:释放分配的内存并关闭套接字。
这个示例展示了如何创建和使用Netlink套接字与内核进行通信。根据具体需求,可以扩展此示例以处理更复杂的Netlink消息和响应。
setsockopt()函数
setsockopt()函数是一个用于在Linux和其他Unix-like系统中设置套接字选项的系统调用。它允许程序员调整套接字的行为或特性,以适应不同的网络环境和应用程序需求。以下是关于setsockopt()函数的相关介绍:函数原型:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd:要操作的套接字文件描述符。level:指定套接字级别的选项,如SOL_SOCKET(通用选项)、IPPROTO_TCP(TCP层选项)等。optname:选项名称,是一个整数值,代表具体的选项,如SO_REUSEADDR(重用地址)或SO_LINGER(linger选项)等。optval:指向包含选项值的内存区域的指针。optlen:选项值的长度。常用选项:
- 套接字层选项(
SOL_SOCKET):如SO_REUSEADDR、SO_KEEPALIVE等。- TCP协议层选项(
IPPROTO_TCP):如TCP_NODELAY、TCP_KEEPIDLE等。- IP协议层选项(
IPPROTO_IP):如IP_TTL。使用示例:
setsockopt()函数用于设置套接字选项,以调整套接字的行为。以下是如何使用setsockopt()函数设置套接字选项的具体步骤和示例代码:步骤
- 创建套接字:使用
socket()函数创建一个套接字。- 设置选项:使用
setsockopt()函数设置所需的套接字选项。- 绑定、监听或连接:根据需要绑定到本地地址、监听连接请求或连接到远程服务器。
- 发送/接收数据:使用
send()、recv()等函数进行数据传输。- 关闭套接字:使用
close()函数关闭套接字。示例代码
以下是一个具体的示例,展示如何使用
setsockopt()函数设置SO_REUSEADDR选项,允许绑定到已被使用的地址。#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h>#define PORT 8080 #define BUFFER_SIZE 1024int main() {int server_fd;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};const char *hello = "Hello from server";// 创建套接字文件描述符if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置SO_REUSEADDR选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");close(server_fd);exit(EXIT_FAILURE);}// 填充服务器信息结构体address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY; // 自动选择IP地址address.sin_port = htons(PORT);// 绑定套接字到指定端口if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接请求if (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}printf("Server listening on port %d\n", PORT);// 接受客户端连接int new_socket;if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");close(server_fd);exit(EXIT_FAILURE);}// 读取客户端消息read(new_socket, buffer, BUFFER_SIZE);printf("Message from client: %s\n", buffer);// 发送响应消息给客户端send(new_socket, hello, strlen(hello), 0);printf("Hello message sent\n");// 关闭套接字close(new_socket);close(server_fd);return 0; }解释
创建套接字:使用
socket()函数创建一个TCP套接字。设置SO_REUSEADDR选项:通过
setsockopt()函数设置SO_REUSEADDR选项,允许绑定到已被使用的地址。这对于开发和调试非常有用,因为可以快速重启服务器而无需等待端口释放。绑定、监听和接受连接:将套接字绑定到指定的端口并开始监听连接请求。当有客户端连接时,接受连接并处理数据。
发送/接收数据:从客户端读取数据并发送响应消息。
关闭套接字:在完成通信后,关闭套接字以释放资源。
通过这种方式,你可以灵活地配置套接字的行为,以满足不同的网络编程需求。
总的来说,通过
setsockopt()函数,程序可以根据需要动态地配置套接字的行为,这对于实现高级网络功能非常关键。
相关文章:
Linux的Socket开发补充
是listen函数阻塞等待连接,还是accept函数阻塞等待连接? 这两个函数的名字,听起来像listen一直在阻塞监听,有连接了就accept,但其实不是的。 调用listen()后,程序会立即返回,继续执行后续代码&a…...
Flutter异常Couldn‘t find dynamic library in default locations
Flutter项目在Windows系统使用ffigen生成代码时报下面的错误: [SEVERE] : Couldnt find dynamic library in default locations. [SEVERE] : Please supply one or more path/to/llvm in ffigens config under the key llvm-path. Unhandled exception: Exception: …...
Spring-AOP分析
Spring分析-AOP 1.案例引入 在上一篇文章中,【Spring–IOC】【https://www.cnblogs.com/jackjavacpp/p/18829545】,我们了解到了IOC容器的创建过程,在文末也提到了AOP相关,但是没有作细致分析,这篇文章就结合示例&am…...
[特殊字符] Prompt如何驱动大模型对本地文件实现自主变更:Cline技术深度解析
在AI技术快速发展的今天,编程方式正在经历一场革命性的变革。从传统的"人写代码"到"AI辅助编程",再到"AI自主编程",开发效率得到了质的提升。Cline作为一款基于VSCode的AI编程助手,通过其独特的pro…...
【专业解读:Semantic Kernel(SK)】大语言模型与传统编程的桥梁
目录 Start:什么是Semantic Kernel? 一、Semantic Kernel的本质:AI时代的操作系统内核 1.1 重新定义LLM的应用边界 1.2 技术定位对比 二、SK框架的六大核心组件与技术实现 2.1 内核(Kernel):智能任务调度中心 2…...
PHP 8 中的 Swow:高性能纯协程网络通信引擎
一、什么是 Swow? Swow 是一个高性能的纯协程网络通信引擎,专为 PHP 设计。它结合了最小化的 C 核心和 PHP 代码,旨在提供高性能的网络编程支持。Swow 的核心目标是释放 PHP 在高并发场景下的真正潜力,同时保持代码的简洁和易用性…...
你学会了些什么211201?--http基础知识
概念 HTTP–Hyper Text Transfer Protocol,超文本传输协议;是一种建立在TCP上的无状态连接(短连接)。 整个基本的工作流程是:客户端发送一个HTTP请求(Request ),这个请求说明了客户端…...
每天学一个 Linux 命令(29):tail
可访问网站查看,视觉品味拉满: http://www.616vip.cn/29/index.html tail 命令用于显示文件的末尾内容,默认显示最后 10 行。它常用于实时监控日志文件或查看文件的尾部数据。以下是详细说明和示例: 命令格式 tail [选项] [文件...]常用选项 选项描述-n <NUM> …...
【形式化验证基础】活跃属性Liveness Property和安全性质(Safety Property)介绍
文章目录 一、Liveness Property1、概念介绍2、形式化定义二、Safety Property1. 定义回顾2. 核心概念解析3. 为什么强调“有限前缀”4. 示例说明4.1 示例1:交通信号灯系统4.2 示例2:银行账户管理系统5. 实际应用的意义三. 总结一、Liveness Property 1、概念介绍 在系统的…...
技工院校无人机专业工学一体化人才培养方案
随着无人机技术在农业植保、地理测绘、应急救援等领域的深度应用,行业复合型人才缺口持续扩大。技工院校作为技能型人才培养主阵地,亟需构建与行业发展同步的无人机专业人才培养体系。本文基于"工学一体化"教育理念,从课程体系、实…...
PI0 Openpi 部署(仅测试虚拟环境)
https://github.com/Physical-Intelligence/openpi/tree/main 我使用4070tisuper, 14900k,完全使用官方默认设置,没有出现其他问题。 目前只对examples/aloha_sim进行测试,使用docker进行部署, 默认使用pi0_aloha_sim模型(但是文档上没找到对应的&…...
计算机视觉——利用AI幻觉检测图像是否是生成式算生成的图像
概述 俄罗斯的新研究提出了一种非常规方法,用于检测不真实的AI生成图像——不是通过提高大型视觉-语言模型(LVLMs)的准确性,而是故意利用它们的幻觉倾向。 这种新方法使用LVLMs提取图像的多个“原子事实”,然后应用自…...
性能测试工具和JMeter功能概要
主流性能测试工具 LoadRunner JMeter [本阶段学习] 1.1 LoadRunner HP LoadRunner是一种工业级标准性能测试负载工具,可以模拟上万用户实施测试,并在测试时可实时检测应用服务器及服务器硬件各种数据,来确认和查找存在的瓶颈支持多协议&am…...
《理解 Java 泛型中的通配符:extends 与 super 的使用场景》
大家好呀!👋 今天我们要聊一个让很多Java初学者头疼的话题——泛型通配符。别担心,我会用最通俗易懂的方式,带你彻底搞懂这个看似复杂的概念。准备好了吗?Let’s go! 🚀 一、为什么我们需要泛型通配符&…...
C#学习第17天:序列化和反序列化
什么是序列化? 定义:序列化是指把对象转换为一种可以轻松存储或传输的格式,如JSON、XML或二进制格式。这个过程需要捕获对象的类型信息和数据内容。用途:使得对象可以持久化到文件、发送至网络、或存储在数据库中。 什么是反序列…...
FlaskRestfulAPI接口的初步认识
FlaskRestfulAPI 介绍 记录学习 Flask Restful API 开发的过程 项目来源:【Flask Restful API教程-01.Restful API介绍】 我的代码仓库:https://gitee.com/giteechaozhi/flask-restful-api.git 后端API接口实现功能:数据库访问控制…...
CSS预处理工具有哪些?分享主流产品
目前主流的CSS预处理工具包括:Sass、Less、Stylus、PostCSS等。其中,Sass是全球使用最广泛的CSS预处理工具之一,以强大的功能、灵活的扩展性以及完善的社区生态闻名。Sass通过增加变量、嵌套、混合宏(mixin)等功能&…...
微信小程序中,将搜索组件获取的值传递给父页面(如 index 页面)可以通过 自定义事件 或 页面引用 实现
将搜索组件获取的值传递给父页面(如 index 页面)可以通过 自定义事件 或 页面引用 实现 方法 1:自定义事件(推荐) 步骤 1:搜索组件内触发事件 在搜索组件的 JS 中,当获取到搜索值时,…...
深度学习预训练和微调
目录 1. 预训练(Pre-training)是什么? 2. 微调(Fine-tuning)是什么? 3. 预训练和微调的对象 4. 特征提取如何实现? 预训练阶段: 微调阶段: 5. 这样做的作用和意义 …...
AI 速读 SpecReason:让思考又快又准!
在大模型推理的世界里,速度与精度往往难以兼得。但今天要介绍的这篇论文带来了名为SpecReason的创新系统,它打破常规,能让大模型推理既快速又准确,大幅提升性能。想知道它是如何做到的吗?快来一探究竟! 论…...
Qt通过ODBC和QPSQL两种方式连接PostgreSQL或PolarDB PostgreSQL版
一、概述 以下主要在Windows下验证连接PolarDB PostgreSQL版(阿里云兼容 PostgreSQL的PolarDB版本)。Linux下类似,ODBC方式则需要配置odbcinst.ini和odbc.ini。 二、代码 以下为完整代码,包含两种方式连接数据库,并…...
MobaXterm连接Ubuntu(SSH)
1.查看Ubuntu ip 打开终端,使用指令 ifconfig 由图可知ip地址 2.MobaXterm进行SSH连接 点击session,然后点击ssh,最后输入ubuntu IP地址以及用户名...
Lambda 函数与 peek 操作的使用案例
Lambda 函数和 peek 操作是 Java 8 Stream API 中非常有用的特性,下面我将介绍它们的使用案例。 Lambda 函数使用案例 Lambda 表达式是 Java 8 引入的一种简洁的匿名函数表示方式。 集合操作 List<String> names Arrays.asList("Alice", "B…...
C# 的 字符串插值($) 和 逐字字符串(@) 功能
这段代码使用了 C# 的 字符串插值($) 和 逐字字符串() 功能,并在 SQL 语句中动态拼接变量。下面详细解释它们的用法: 1. $(字符串插值) $ 是 C# 的 字符串插值 符号,允许…...
软考 中级软件设计师 考点知识点笔记总结 day13 数据库系统基础知识 数据库模式映像 数据模型
文章目录 数据库系统基础知识6.1 基本概念6.1.1 DBMS的特征与分类 6.2 数据库三级模式两级映像6.3 数据库的分析与设计过程6.4 数据模型6.4.1 ER模型6.4.2 关系模型 数据库系统基础知识 基本概念 数据库三级模式两级映像 数据库的分析与设计过程 数据模型 关系代数 数据库完整…...
蓝桥杯2024省A.成绩统计
蓝桥杯2024省A.成绩统计 题目 题目解析与思路 题目要求返回至少要检查多少个人的成绩,才有可能选出k名同学,他们的方差小于一个给定的值 T 二分枚举答案位置,将答案位置以前的数组单独取出并排序,然后用k长滑窗O(1)计算方差 问…...
Mac mini 安装mysql数据库以及出现的一些问题的解决方案
首先先去官网安装一下mysql数据库,基本上都是傻瓜式安装的流程,我也就不详细说了。 接下来就是最新版的mysql安装的时候,他就会直接让你设置一个新的密码。 打开设置,拉到最下面就会看到一个mysql的图标: 我设置的就是…...
俄罗斯方块-简单开发版
一、需求分析 实现了一个经典的俄罗斯方块小游戏,主要满足以下需求: 1.图形界面 使用 pygame 库创建一个可视化的游戏窗口,展示游戏的各种元素,如游戏区域、方块、分数等信息。 2.游戏逻辑 实现方块的生成、移动、旋转、下落和锁…...
STM32的启动方式
目录 一、从主闪存存储器启动(Main Flash Memory) 二、从系统存储器启动(System Memory) 三、从内置SRAM启动(Embedded SRAM) 四、从外挂存储介质启动的实现方式 1. 存储介质选型 2. 硬件连接 3. 引…...
你学会了些什么200601?--Flask搭建造测试数据平台
搭建造数平台的环境: ***python3.7 ***html5 ***css ***JavaScript ***Ajax ***MySQL 前台页面的显示 1.为了页面美化,使用了JavaScript,通过逐级展开/隐藏的的方式显示下一级菜单 2.为了在提交表单数据时页面不发生跳转,需要引用…...
