非阻塞tcp连接示例及getsockopt的作用

本文探讨了非阻塞模式在网络通信中的优势与复杂性,重点讲解了非阻塞TCP连接的原理、connect函数的工作以及getsockopt和select在确认连接状态中的作用。
摘要由CSDN通过智能技术生成

非阻塞模式的基本概念
非阻塞模式是一种在网络通信中常用的技术手段,它允许程序在等待一个操作完成时继续执行其他任务。这种模式在处理多个网络连接或需要高效率的场景中非常有用。在非阻塞模式下,系统调用(如connect或read)会立即返回,而不是等待操作完成。

非阻塞模式的优势
效率提升:允许程序同时处理多个操作,提高整体效率。
响应性增强:程序可以更快地响应其他事件,不会因单一操作的等待而阻塞。
非阻塞模式的劣势
复杂性增加:程序需要正确处理立即返回的系统调用,这可能增加编程的复杂性。
资源管理:需要有效管理多个并发操作,避免资源冲突和死锁。

非阻塞 TCP 连接的常见用途
非阻塞 TCP 连接在现代网络编程中扮演着重要角色。它们使得服务器能够同时处理数千个客户端连接,而不会因为单个连接的延迟或阻塞而影响整体性能。

非阻塞 TCP 连接的应用场景
高性能服务器:如网络游戏服务器、大规模并发处理系统。
实时通信应用:如即时通讯工具、实时数据传输。

connect 函数的基本工作原理
connect 函数(连接函数)是用于建立客户端和服务器之间的连接的关键函数。在阻塞模式下,connect 会等待直到连接成功或失败。然而,在非阻塞模式下,connect 的行为有所不同。

在非阻塞模式下,当我们调用 connect 函数时(当我们尝试建立连接时),如果连接不能立即建立,connect不会阻塞等待连接完成。相反,它会立即返回一个错误码,通常是 EINPROGRESS,表示连接尝试正在进行中。

// 非阻塞 connect 示例
	int status = connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (status < 0) {
		if (errno != EINPROGRESS) {
			// 处理连接错误
		}
	}

 

非阻塞 connect 的特殊情况处理
在处理非阻塞 connect 时,我们需要考虑到连接的不确定性。这就是 select 或 poll 函数发挥作用的地方。

当 connect 返回 EINPROGRESS 时,我们可以使用 select 函数来等待 socket 变得可写。这表示连接要么已经建立,要么发生了错误。但是,这还不足以确定连接的状态。我们需要进一步的确认。

这就是 getsockopt 函数的发挥作用的地方。通过检查 SO_ERROR 选项,我们可以获取并确认 socket 的实际状态,无论是成功还是失败。这一步骤是必不可少的,因为它提供了连接状态的最终确认。

// 使用 getsockopt 检查连接状态
	int error = 0;
	socklen_t len = sizeof(error);
	getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &error, &len);
	if (error != 0) {
		// 处理连接错误
	}

select 函数的角色和限制
使用 select 实现超时机制
select 函数在网络编程中扮演着监控多个文件描述符(File Descriptors, FDs)的角色,等待一个或多个 FDs 成为“就绪”状态,即它们可以进行非阻塞的读写操作。这在非阻塞模式下尤为重要,因为它允许程序在没有数据可读或写时不会被阻塞,从而可以同时处理多个连接或执行其他任务。

select 提供了一种机制来设置超时,它可以等待一段预定时间,如果没有 FDs 变为就绪状态,它就会超时返回。这种机制使得程序可以在等待一段合理的时间后继续执行,而不是无限期地等待。

select 在确认连接状态中的局限性
尽管 select 在处理多个 FDs 和实现超时机制方面非常有效,但它在确认特定操作(如连接建立)的状态方面存在局限性。例如,在非阻塞的 TCP 连接过程中,即使 select 报告 socket 可写,并不意味着连接已经成功建立。这里,select 只能告诉我们 socket 的状态发生了变化,但具体是什么变化,还需要进一步的检查。

在 select 的情况下,这意味着我们需要使用其他方法,如 getsockopt,来获取更具体的错误信息或状态确认。

getsockopt 的核心作用
网络状态是不断变化的,而 getsockopt 正是用来掌握这种变化的工具。

getsockopt 函数的基础知识
getsockopt(获取套接字选项)是一个用于检索与特定套接字关联的选项的函数。它可以用来获取套接字的各种状态信息,包括错误代码。

函数原型
在 C++ 中,getsockopt 的函数原型如下:
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
这个函数用于获取 sockfd(套接字文件描述符)上的选项值。level 指定了选项的类型,optname 指定了要检索的选项,optval 是指向保存选项值的缓冲区的指针,而 optlen 是指向缓冲区长度的指针。

如何使用 getsockopt 确认连接状态
在非阻塞模式下的 TCP 连接中,getsockopt 的一个关键用途是检查 SO_ERROR 选项,以确定连接尝试是否成功。这是因为即使 select 函数表明套接字可写,也不一定意味着连接已成功建立。可能存在诸如网络中断或目标服务器拒绝连接等情况,这些都会导致连接失败。

使用示例
以下是一个使用 getsockopt 来检查连接状态的示例:

int error = 0;
	socklen_t len = sizeof(error);
	if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
		// 错误处理
	} else {
		if (error != 0) {
			// 连接失败
		} else {
			// 连接成功
		}
	}

在这个示例中,我们首先定义了一个用于存储错误代码的变量 error,然后调用 getsockopt 来获取 SO_ERROR 选项的值。
如果 error 不为 0,则表示连接过程中出现了错误。

getsockopt 与 select 的互补性
在网络编程中,select 和 getsockopt 是相辅相成的。select 能够告诉我们套接字的状态改变(例如,从不可写变为可写),但它不能告诉我们状态改变的具体原因。这就是 getsockopt 发挥作用的地方,它提供了更深层次的信息,帮助我们理解背后的原因。

互补关系的实际应用
在实际应用中,我们通常先使用 select 来检测套接字状态的改变,然后使用 getsockopt 来获取更详细的信息。这种方法结合了两者的优点,使我们能够更准确地掌握网络连接的真实状态。

通过以上分析,我们可以看到 getsockopt 在非阻塞 TCP 连接中的核心作用。它不仅是一个技术工具,更是我们理解和应对不断变化网络环境的一种方式。在网络编程的世界中,getsockopt 就是我们掌握这种变化的关键。

getsockopt 在非阻塞 TCP 连接中的重要性
在非阻塞 TCP 连接的实现中,getsockopt(获取套接字选项)扮演着至关重要的角色。它不仅是一个编程接口,更是我们理解和控制网络通信过程的窗口。getsockopt 允许我们查询套接字的状态,特别是在连接尝试过程中。这一点在非阻塞模式下尤为重要,因为在这种模式下,连接的建立不会立即完成。

在 C++ 中,getsockopt 的使用通常如下所示:

int error = 0;
socklen_t len = sizeof(error);
int retval = getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &error, &len);

这段代码展示了如何使用 getsockopt 来检查套接字上是否有错误。如果 error 为 0,则表示连接成功;否则,表示出现了问题。

以下是一个简易完整的非阻塞TCP客户端的连接代码示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/select.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>



static int netctrl_tcp_connect(char ipv4addr[16],int port){

	int ret=0;
	int sockfd;
	struct sockaddr_in name;
	int flage=0;

	struct timeval tm;
    fd_set set, rset;
    socklen_t len = sizeof(int);
    int error = -1;
    int iResult = 0;
	unsigned short s_port = port;

	sockfd= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sockfd<0){
		printf("socket error tcp_sock:%d,errno:%d,%s\n",
		sockfd, errno, strerror(errno));
		return -1;
	} else
		printf("socket success tcp_sock:%d\n",sockfd);

	memset(&name, 0, sizeof(name));
	name.sin_family = AF_INET;
	name.sin_port = htons(s_port);

	if (inet_pton(AF_INET, ipv4addr, &name.sin_addr) <= 0){
        printf("ip:%s inet_pton error:%d %s\n", ipv4addr, errno, strerror(errno));
        close(sockfd);
        return -1;
    }

    flage = fcntl(sockfd, F_GETFL, 0);
    ret = fcntl(sockfd, F_SETFL, (int)flage | O_NONBLOCK);
	if(ret == -1){
		close(sockfd);
		return -1;
	}

	if(connect(sockfd, (struct sockaddr*)&name, sizeof(name)) == -1){
		if (errno != EINPROGRESS) {
            printf("connect error:%d %s\n", errno, strerror(errno));
            close(sockfd);
            return -1;
        }

        tm.tv_sec = 2;
        tm.tv_usec = 0;
        FD_ZERO(&set);
        FD_ZERO(&rset);
        FD_SET(sockfd, &set);
        FD_SET(sockfd, &rset);
        ret = select(sockfd + 1, &rset, &set, NULL, &tm);
        if (0 > ret){
            printf("select error:%d %s,Sockfd=%d\n", errno, strerror(errno), sockfd);
            iResult = -1;
        }
        else if (0 == ret){
            printf("connect %s timeout. error:%d %s\n", ipv4addr, errno, strerror(errno));
            iResult = -1;
        }
        else{
            ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void*)&error, &len);
            if (ret == -1){
                iResult = -1;
            }
            if (error == 0){
                iResult = 0;
            }
            else{
                printf("getsockopt SO_ERROR=%d errno:%d %s\n", error, errno, strerror(errno));
                iResult = -1;
            }
        }

	}

	flage &= ~O_NONBLOCK;
    ret = fcntl(sockfd, F_SETFL, flage);

    if (ret == -1) {
        close(sockfd);
        return -1;
    }

    if (iResult != 0) {
        close(sockfd);
        return -1;
    }

	return sockfd;

}

static int netctrl_tcp_send(int socket_fd,const char* sendbuff,int sendlen,int s32timeout)
{

	fd_set	 struWriteFdSet;
    struct   timeval struTimeout;
    int   s32SendRtn, s32SelRtn;
    int   s32SendSize = 0;
    int   s32Retry = 0;

	FD_ZERO(&struWriteFdSet);
    FD_SET(socket_fd, &struWriteFdSet);
    struTimeout.tv_sec = s32timeout;
    struTimeout.tv_usec = 0;
    s32SelRtn = select(socket_fd + 1, NULL, &struWriteFdSet, NULL, &struTimeout);
	if (s32SelRtn <= 0){
		printf("select error:%d %s", errno, strerror(errno));
		return -1;
	}

	while (s32SendSize < sendlen)
    {
        s32SendRtn = send(socket_fd, sendbuff + s32SendSize, sendlen - s32SendSize, 0);

        if (s32SendRtn <= 0)
        {
            if ( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
            {
                usleep(5000);
                s32Retry++;

                if (s32Retry < 10)
                {
                    continue;
                }
            }

            printf("send error:%d %s", errno, strerror(errno));
            return -1;
        }

        s32SendSize += s32SendRtn;
    }

    return 0;

}





int main()
{
	int sockfd=0;
	int ret = 0;
	int s32Retry = 0;
	int port = 7000;
	char IPv4addr[16]="192.168.16.24";
	char parseGpsBuf[256]="yuiderfiohbaeworfhaerho";

	while(1)
	{
		sockfd=netctrl_tcp_connect(IPv4addr, port);
		if(sockfd >= 0){
			ret=netctrl_tcp_send(sockfd, parseGpsBuf, strnlen(parseGpsBuf, 256), 2)	;
			if(0 != ret){
				printf("netcrcl_tcp_send failed,ClientIP[%s] Disconnect!\n", IPv4addr);
			}

			printf("send message %s to client [%s] return %#x\n", parseGpsBuf, IPv4addr, ret);
			close(sockfd);
			//break;
		}
		s32Retry++;
		usleep(1000*400);
	}
	if (s32Retry >= 5){

		printf("netctrl_tcp_Connect failed,ClientIP[%s] Disconnect!\n", IPv4addr);
	}










	return 0;

}

  • 21
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用 ESP-IDF 中提供的 FreeRTOS API 和 LWIP API 来实现 ESP32-C3 上的非阻塞TCP client。以下是一个简单的实现示例: ```c #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "lwip/err.h" #include "lwip/sys.h" #include "lwip/netdb.h" #include "lwip/api.h" static const char *TAG = "tcp_client"; void tcp_client_task(void *pvParameters) { const char *server_ip = "192.168.1.100"; const int server_port = 12345; struct sockaddr_in dest_addr; dest_addr.sin_addr.s_addr = inet_addr(server_ip); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(server_port); int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { ESP_LOGE(TAG, "Failed to create socket"); vTaskDelete(NULL); } // Set socket to non-blocking mode int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); // Connect to server int ret = connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if (ret < 0 && errno != EINPROGRESS) { ESP_LOGE(TAG, "Failed to connect to server"); vTaskDelete(NULL); } // Wait for connection to complete fd_set write_fds; FD_ZERO(&write_fds); FD_SET(sockfd, &write_fds); ret = select(sockfd + 1, NULL, &write_fds, NULL, NULL); if (ret < 0) { ESP_LOGE(TAG, "Failed to wait for connection to complete"); vTaskDelete(NULL); } // Check if connection was successful int error = 0; socklen_t error_len = sizeof(error); getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &error_len); if (error != 0) { ESP_LOGE(TAG, "Failed to connect to server: %d", error); vTaskDelete(NULL); } ESP_LOGI(TAG, "Connected to server"); char rx_buffer[128]; while (1) { // Receive data int len = recv(sockfd, rx_buffer, sizeof(rx_buffer), 0); if (len < 0 && errno != EAGAIN) { ESP_LOGE(TAG, "Failed to receive data"); break; } else if (len > 0) { ESP_LOGI(TAG, "Received %d bytes: %.*s", len, len, rx_buffer); } // Send data const char *tx_buffer = "Hello, server!"; len = send(sockfd, tx_buffer, strlen(tx_buffer), MSG_DONTWAIT); if (len < 0 && errno != EAGAIN) { ESP_LOGE(TAG, "Failed to send data"); break; } else if (len > 0) { ESP_LOGI(TAG, "Sent %d bytes: %s", len, tx_buffer); } vTaskDelay(pdMS_TO_TICKS(1000)); } close(sockfd); vTaskDelete(NULL); } void app_main() { xTaskCreate(tcp_client_task, "tcp_client_task", 4096, NULL, 5, NULL); } ``` 在这个示例中,我们首先创建了一个非阻塞式的 socket,并设置其连接状态为非阻塞式。在连接服务器时,我们使用了 `select()` 函数来等待连接完成。在主循环中,我们使用 `recv()` 函数接收数据,并使用 `send()` 函数发送数据。注意,在发送数据时,我们使用了 `MSG_DONTWAIT` 标志来确保数据发送不会阻塞。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值