非阻塞模式的基本概念
非阻塞模式是一种在网络通信中常用的技术手段,它允许程序在等待一个操作完成时继续执行其他任务。这种模式在处理多个网络连接或需要高效率的场景中非常有用。在非阻塞模式下,系统调用(如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;
}