CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章高频C++11重难点知识解析

  • 第2章Linux GDB高级调试指南

  • 第3章C++多线程编程从入门到进阶

  • 第4章C++网络编程重难点解析

    • 4.1 学习网络编程,你应该掌握哪些 socket 函数
    • 4.2 TCP 网络通信的基本流程
    • 4.3 设计跨平台网络通信库时需要注意的一些 socket 函数用法
    • 4.4 bind 函数重难点分析
    • 4.5 select 函数用法和原理
    • 4.6 socket 的阻塞模式和非阻塞模式
    • 4.7 发送 0 字节的数据是什么效果?
    • 4.8 connect 函数在阻塞和非阻塞模式下的行为
    • 4.9 连接时顺便接收第一组数据
    • 4.10 如何获取当前 socket 对应的接收缓冲区中有多少数据可读
    • 4.11 Linux EINTR 错误码
    • 4.12 Linux SIGPIPE 信号
    • 4.13 Linux poll 函数用法
    • 4.14 Linux epoll 模型
    • 4.15 高效的 readv 和 writev 函数
    • 4.16 主机字节序和网络字节序
    • 4.17 域名解析 API 介绍
  • 第5章网络通信故障排查常用命令

  • 第6章高性能网络通信协议设计精要

  • 第7章高性能服务结构设计

  • 第8章Redis 网络通信模块源码分析

  • 第9章后端服务重要模块设计探索

  • C++后端开发进阶
  • 第4章C++网络编程重难点解析
zhangxf
2023-04-05

4.9 连接时顺便接收第一组数据

在一些应用场景下,我们需要在网络通信双方建立连接成功后就把对端的第一组数据接收过来。

Linux 提供了一个叫 TCP_DEFER_ACCEPT 的 socket 选项,设置了该选项之后只有连接建立成功且收到第一组对端数据,accept() 函数才会返回。以下是 libevent 网络库中的该选项的用法:

//evutil.c
int
evutil_make_tcp_listen_socket_deferred(evutil_socket_t sock)
{
#if defined(EVENT__HAVE_NETINET_TCP_H) && defined(TCP_DEFER_ACCEPT)
  int one = 1;

  /* TCP_DEFER_ACCEPT tells the kernel to call defer accept() only after data
   * has arrived and ready to read */ 
  return setsockopt(sock, IPPROTO_TCP, TCP_DEFER_ACCEPT, &one,
  	(ev_socklen_t)sizeof(one)); 
#endif
  return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Windows 也提供了类似的功能,其扩展的函数 AcceptEx() 用于接受连接后,可选择性地决定是否顺便收取第一组对端发来的数据。 这个函数的签名如下:

BOOL AcceptEx(
  SOCKET       sListenSocket,
  SOCKET       sAcceptSocket,
  PVOID        lpOutputBuffer,
  DWORD        dwReceiveDataLength,
  DWORD        dwLocalAddressLength,
  DWORD        dwRemoteAddressLength,
  LPDWORD      lpdwBytesReceived,
  LPOVERLAPPED lpOverlapped
);
1
2
3
4
5
6
7
8
9
10
  • 参数 sListenSocket 是侦听 socket;
  • 参数 sAcceptSocket 对应新产生的 clientsocket;
  • 参数 lpOutputBuffer 是一个输出缓冲区,其内容由三块构成,分别是收到的第一组数据和连接双方的地址(均为 sockaddr_in 结构);
  • 参数 dwReceiveDataLength 为 lpOutputBuffer 中用于存放第一组数据的缓冲区长度,如果设置为 0,AcceptEx() 会立即返回,如果设置为大于 0,AcceptEx() 函数会等待第一组数据收到后再返回;
  • 参数 dwLocalAddressLength 和 dwRemoteAddressLength 是 lpOutputBuffer 中用于存放本端和对端地址结构的缓冲区长度,这两个参数的值设置必须大于等于 16;
  • 参数 lpdwBytesReceived 是实际收到的第一组数据的字节数;
  • 参数 lpOverlapped 是一个 OVERLAPPED 结构的指针。
  • 返回值:函数调用成功返回 TRUE,调用失败返回 FALSE。

这个函数的具体用法我们在后面介绍完成端口的章节详细讲解。

既然被动连接方可以在接受连接时顺便收取第一组数据,主动连接方也可以在发起连接时顺便发送第一组数据,Windows 系统提供了 ConnectEx() 函数来达到此目的,其函数签名如下:

BOOL ConnectEx(
  SOCKET 			s,
  const sockaddr*   name,
  int 				namelen,
  PVOID 			lpSendBuffer,
  DWORD 			dwSendDataLength,
  LPDWORD 			lpdwBytesSent,
  LPOVERLAPPED 		lpOverlapped
)
1
2
3
4
5
6
7
8
9

ConnectEx() 函数除了具备大家熟悉的 connect() 函数的功能以外,还可以通过参数 lpSendBuffer 和 dwSendDataLength 来指定连接建立成功时发送的数据内容和长度。

和 AcceptEx 一样,你不能直接使用函数名 ConnectEx 调用该函数,必须通过调用 WSAIoctl() 函数获取 ConnectEx 函数指针后,再通过指针调用之。libevent 网络库完成端口模块代码演示了这一做法:

//event_iocp.c
static void *
get_extension_function(SOCKET s, const GUID *which_fn)
{
	void *ptr = NULL;
	DWORD bytes=0;
	//调用WSAIoctl获取相关函数指针
	WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER,
	    (GUID*)which_fn, sizeof(*which_fn),
	    &ptr, sizeof(ptr),
	    &bytes, NULL, NULL);

	/* No need to detect errors here: if ptr is set, then we have a good
	   function pointer.  Otherwise, we should behave as if we had no
	   function pointer.
	*/
	return ptr;
}

static void
init_extension_functions(struct win32_extension_fns *ext)
{
	const GUID acceptex = WSAID_ACCEPTEX;
	const GUID connectex = WSAID_CONNECTEX;
	const GUID getacceptexsockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
	SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
	if (s == EVUTIL_INVALID_SOCKET)
		return;
	ext->AcceptEx = get_extension_function(s, &acceptex);
	//ext->ConnectEx中存储了ConnectEx函数指针
	ext->ConnectEx = get_extension_function(s, &connectex);
	ext->GetAcceptExSockaddrs = get_extension_function(s,
	    &getacceptexsockaddrs);
	closesocket(s);

	extension_fns_initialized = 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
上次更新: 2025/04/01, 20:53:14
4.8 connect 函数在阻塞和非阻塞模式下的行为
4.10 如何获取当前 socket 对应的接收缓冲区中有多少数据可读

← 4.8 connect 函数在阻塞和非阻塞模式下的行为 4.10 如何获取当前 socket 对应的接收缓冲区中有多少数据可读→

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
C++语言面试问题集锦 目录与说明
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式