LwIP C TCP/IP Stack 正确的TCP连接数据发送姿态

21 篇文章 2 订阅

注意,本文提供的代码来自本人搞起耍的 netstack,有一些类似 tun2socks LwIP 实现,目前不会考虑集成到产品上面作为可选 TCP/IP 网络栈,当然不会是基于 go-gvisor、go-netstack 栈,C#/C++ 搞的无第三方代码专用 TCP/IP 网络栈。

LwIP协议栈是无法被多线程驱动的,否则可能出现难以处理的内存安全问题,另外与操作系统TCP/IP协议栈提供接口类似,每个 TCP/IP Socket 实例的发送缓冲区大小是固定的,意味着不可以一直向LwIP写入段数据(TCP_SEG)到缓冲区,LwIP上为 TCP_PCB(TCP协议控制块)本质上它就代表着一个确切的TCP实例。

LwIP上面虽然外层接口使用TCP_PCB参数数字签名来做监听控制块,但步入内部会被重分配重叠TCP_PCB_LISTEN结构内存,具体细节自行阅读:tcp_listen_with_backlog_and_err

LwIP 由于需要工作在 STA(单线程)架构环境上面,包括关联的TCP事件回调(如:tcp_recv、tcp_sent)所以为了可以让 LwIP 在单个线程上面处理尽可能大的I/O网络吞吐量,所以不可以让用户增加的代码对单个核心CPU负担很大,网络游戏加速器不需要考虑,类似需要为流做 AES-256-CFB 加密,那可就不行,单个核心CPU算力负担太大,那么好的办法是通过 Asynchronous Socket I/O 把负担偏重的任务交付到另外的 MTA 架构由 Coroutine、EDSM 驱动的模型处理。

本文例子是由 C/C++ 11 语言实现,依赖 boost::asio,不喜欢 boost::asio,可以考虑使用 libevent、libuv,一个 C/C++ 语言库,一个 C 语言库,需要逼格,大家了解!

另外可以把整个 LwIP 一些结构还有代码魔改以下,直接支持 C/C++ std::function、std::shared_ptr 等等,如果不希望过多的魔改 LwIP 协议栈实现代码,那么关联每个 PCB 实力具体的用户结构,本文代码为:“netstack_tcp_socket”,否则可以 tcp_arg(PCB, XXX) or PCB->callback_arg = XXX 来设置 LwIP 协议栈TCP事件触发时传入的 arg 参数值,目前方法有几类,Key 2 Instance 表、放结构的安全/不安全指针上去。

关于:

netstack_tcp_send 网络栈TCP发送数据

inline static err_t
netstack_tcp_send(struct tcp_pcb* pcb, void* data, u16_t len, const std::function<void(struct tcp_pcb*)>& callback) {
	err_t err = ERR_ARG;
	if (pcb) {
		std::shared_ptr<netstack_tcp_socket> socket_ = netstack_tcp_getsocket(pcb->callback_arg);
		if (!socket_) {
			err = ERR_ABRT;
			goto ret_;
		}

		err = ERR_OK;
		if (data && len) {
			LinkedList<netstack_tcp_socket::send_context>& sents = socket_->sents[netstack_tcp_socket::ENETSTACK_TCP_SENT_LWIP];
			if (sents.Count() > 0) {
			queue_:
				char* chunk_ = (char*)malloc(len);
				memcpy(chunk_, data, len);

				LinkedListNode<netstack_tcp_socket::send_context>* node_ = new LinkedListNode<netstack_tcp_socket::send_context>();
				netstack_tcp_socket::send_context& context_ = node_->Value;
				context_.buf = { chunk_, len };
				context_.cb = callback;

				sents.AddLast(node_);
				goto ret_;
			}

			err = tcp_write(pcb, data, len, TCP_WRITE_FLAG_COPY);
			if (err == ERR_OK) {
				if (callback) {
					callback(pcb);
				}
				goto ret_;
			}

			if (err == ERR_MEM) {
				err = ERR_OK;
				goto queue_;
			}
		}

	ret_:
		tcp_output(pcb);
	}
	return err;
}

朋友们或许可能会疑惑为什么,上述代码只调用 tcp_write 向 LwIP 协议栈写入欲发送的TCP段数据,而不提前使用 tcp_sndbuf 宏获取TCP_PCB上面的当前可用发送滑块窗口大小,来决定当前可立即传送多少字节。

那么我的答案是:没有意义,tcp_write 函数内部实现已经处理发送缓冲区不足或内存分配失败的情况会返回 ERR_MEM,不工作在超低配的单片机设备上面,ERR_MEM 返回只有一个可能是发送缓冲区大小不足。

netstack_tcp_dosent TCP已传送字节数回调函数,需要为每个TCP_PCB控制块,使用 tcp_sent(pcb, netstack_tcp_dosent ); 进行回调绑定,不喜欢调用函数,可以直接设置TCP_PCB结构上面的 sent 字段。

inline static err_t
netstack_tcp_dosent(void* arg, struct tcp_pcb* pcb, u16_t len) {
	if (pcb) {
		std::shared_ptr<netstack_tcp_socket> socket_ = netstack_tcp_getsocket(pcb->callback_arg);
		if (socket_) {
			LinkedList<netstack_tcp_socket::send_context>& sents = socket_->sents[netstack_tcp_socket::ENETSTACK_TCP_SENT_LWIP];
			while (sents.Count() > 0) { // tcp_sndbuf
				LinkedListNode<netstack_tcp_socket::send_context>* node = sents.First();
				netstack_tcp_socket::send_context context_ = node->Value;

				char* unseg_ = (char*)context_.buf.p;
				int unsent_ = context_.buf.sz;

				err_t err_ = tcp_write(pcb, unseg_, unsent_, TCP_WRITE_FLAG_COPY);
				if (err_ != ERR_OK) {
					break;
				}
				else {
					free(unseg_);

					sents.RemoveFirst();
					delete node;
				}

				if (context_.cb) {
					context_.cb(pcb);
				}
			}
		}
		tcp_output(pcb);
	}
	return ERR_OK;
}

关于 netstack_tcp_socket 结构的定义,其实把你不一定需要这么多字段,具体看您的代码实现,本文仅提供正确使用 LwIP 网络协议栈发送TCP/IP协议数据,并确保传送流字节顺序一致性。

typedef struct {
	typedef struct {
		void*										p;
		int											sz;
	} buffer_chunk;

	typedef struct {
		buffer_chunk								buf;
		std::function<void(struct tcp_pcb*)>		cb;
	} send_context;

	typedef enum {
		ENETSTACK_TCP_SENT_LWIP,
		ENETSTACK_TCP_SENT_SOCK,
		ENETSTACK_TCP_SENT_MAX
	} ENETSTACK_TCP_SENT_BUFS;

	LinkedList<send_context>						sents[ENETSTACK_TCP_SENT_MAX];
	std::shared_ptr<boost::asio::ip::tcp::socket>	socket;
	bool											open;

	struct tcp_pcb* pcb;
	ip_addr_t										local_ip;
	u16_t											local_port;
	ip_addr_t										remote_ip;
	u16_t											remote_port;
	u8_t											buf[16384];
} netstack_tcp_socket;

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lwIP(lightweight IP)是一个轻量级的网络协议栈,主要用于嵌入式设备和嵌入式系统中。它被设计成高效、可移植和易于使用的,可以在资源受限的嵌入式平台上实现TCP/IP协议栈功能。 对于lwIP中的TCP客户端,它可以用于实现从嵌入式设备向服务器发起TCP连接发送数据的功能。以下是使用lwIP实现TCP客户端的一般步骤: 1. 初始化lwIP协议栈:首先需要调用lwIP提供的初始化函数来初始化lwIP协议栈,准备好网络资源。这个初始化过程主要是为了分配和配置lwIP所需要的内存和数据结构。 2. 创建TCP套接字:使用lwIP的API函数创建一个TCP套接字。套接字是一个抽象概念,用于在应用程序和网络之间进行数据传递。 3. 设置服务器地址和端口:使用lwIP提供的函数设置服务器的IP地址和端口号。这些信息用于建立与服务器的TCP连接。 4. 建立TCP连接:调用lwIP的API函数,向服务器发起TCP连接请求。lwIP会处理与服务器之间的握手过程,建立可靠的TCP连接。 5. 发送数据:使用套接字发送函数,向服务器发送数据数据可以是应用程序要发送的任何有效数据。 6. 接收数据:使用套接字接收函数,接收服务器返回的数据lwIP提供了接收缓冲区来存储接收到的数据。 7. 关闭连接发送完所有数据后,调用函数关闭TCP连接lwIP会处理TCP连接的正常关闭过程。 值得注意的是,使用lwIPTCP客户端需要对TCP连接数据发送和接收进行适当的错误处理和超时处理,以确保通信的可靠性和稳定性。 总而言之,lwIP是一个适用于资源受限的嵌入式设备的轻量级TCP/IP协议栈,通过使用lwIP的API函数,可以方便地实现TCP客户端功能,从嵌入式设备与服务器之间建立TCP连接发送和接收数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值