要掌握的异步connect 用法

在 socket 是阻塞模式下 connect 函数会一直到有明确的结果才会返回(或连接成功或连接失败),如果服务器地址“较远”,连接速度比较慢,connect 函数在连接过程中可能会导致程序阻塞在 connect 函数处好一会儿(如两三秒之久),虽然这一般也不会对依赖于网络通信的程序造成什么影响,但在实际项目中,我们一般倾向使用所谓的异步的 connect 技术,或者叫非阻塞的 connect。这个流程一般有如下步骤:

1
2
3
1. 创建socket,并将 socket 设置成非阻塞模式;
2. 调用 connect 函数,此时无论 connect 函数是否连接成功会立即返回;如果返回 -1 并不一定表示连接出错,如果此时错误码是EINPROGRESS,则表示正在尝试连接;
3. 接着调用 select 函数,在指定的时间内判断该 socket 是否可写,如果可写说明连接成功,反之则认为连接失败。

按上述流程编写代码如下:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
 * 异步的connect写法,nonblocking_connect.cpp
 * zhangyl 2018.12.17
 */
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       "helloworld"

int main(int argc, char* argv[])
{
    //1.创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        std::cout << "create client socket error." << std::endl;
        return -1;
    }
	
	//将 clientfd 设置成非阻塞模式	
	int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
	int newSocketFlag = oldSocketFlag | O_NONBLOCK;
	if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1)
	{
		close(clientfd);
		std::cout << "set socket to nonblock error." << std::endl;
		return -1;
	}

    //2.连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
	for (;;)
	{
		int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
		if (ret == 0)
		{
			std::cout << "connect to server successfully." << std::endl;
			close(clientfd);
			return 0;
		} 
		else if (ret == -1) 
		{
			if (errno == EINTR)
			{
				//connect 动作被信号中断,重试connect
				std::cout << "connecting interruptted by signal, try again." << std::endl;
				continue;
			} else if (errno == EINPROGRESS)
			{
				//连接正在尝试中
				break;
			} else {
				//真的出错了,
				close(clientfd);
				return -1;
			}
		}
	}
	
	fd_set writeset;
    FD_ZERO(&writeset);
    FD_SET(clientfd, &writeset);
	//可以利用tv_sec和tv_usec做更小精度的超时控制
    struct timeval tv;
    tv.tv_sec = 3;  
    tv.tv_usec = 0;
    if (select(clientfd + 1, NULL, &writeset, NULL, &tv) == 1)
    {
		std::cout << "[select] connect to server successfully." << std::endl;
	} else {
		std::cout << "[select] connect to server error." << std::endl;
	}

	//5. 关闭socket
	close(clientfd);

    return 0;
}

为了区别到底是在调用 connect 函数时判断连接成功还是通过 select 函数判断连接成功,我们在后者的输出内容中加上了“[select]”标签以示区别。

我们先用 nc 命令启动一个服务器程序:

1
nc -v -l 0.0.0.0 3000

然后编译客户端程序并执行:

1
2
3
[root@localhost testsocket]# g++ -g -o nonblocking_connect nonblocking_connect.cpp 
[root@localhost testsocket]# ./nonblocking_connect 
[select] connect to server successfully.

我们把服务器程序关掉,再重新启动一下客户端,这个时候应该会连接失败,程序输出结果如下:

1
2
[root@localhost testsocket]# ./nonblocking_connect 
[select] connect to server successfully.

奇怪?为什么连接不上也会得出一样的输出结果?难道程序有问题?这是因为:

  • 在 Windows 系统上,一个 socket 没有建立连接之前,我们使用 select 函数检测其是否可写,能得到正确的结果(不可写),连接成功后检测,会变为可写。所以,上述介绍的异步 connect 写法流程在 Windows 系统上是没有问题的。

  • 在 Linux 系统上一个 socket 没有建立连接之前,用 select 函数检测其是否可写,你也会得到可写的结果,所以上述流程并不适用于 Linux 系统。正确的做法是,connect 之后,不仅要用 select 检测可写,还要检测此时 socket 是否出错,通过错误码来检测确定是否连接上,错误码为 0 表示连接上,反之为未连接上。完整代码如下:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
 * Linux 下正确的异步的connect写法,linux_nonblocking_connect.cpp
 * zhangyl 2018.12.17
 */
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       "helloworld"

int main(int argc, char* argv[])
{
    //1.创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        std::cout << "create client socket error." << std::endl;
        return -1;
    }
	
	//将 clientfd 设置成非阻塞模式,
	int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
	int newSocketFlag = oldSocketFlag | O_NONBLOCK;
	if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1)
	{
		close(clientfd);
		std::cout << "set socket to nonblock error." << std::endl;
		return -1;
	}

    //2.连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
	for (;;)
	{
		int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
		if (ret == 0)
		{
			std::cout << "connect to server successfully." << std::endl;
			close(clientfd);
			return 0;
		} 
		else if (ret == -1) 
		{
			if (errno == EINTR)
			{
				//connect 动作被信号中断,重试connect
				std::cout << "connecting interruptted by signal, try again." << std::endl;
				continue;
			} else if (errno == EINPROGRESS)
			{
				//连接正在尝试中
				break;
			} else {
				//真的出错了,
				close(clientfd);
				return -1;
			}
		}
	}
	
	fd_set writeset;
    FD_ZERO(&writeset);
    FD_SET(clientfd, &writeset);
	//可以利用tv_sec和tv_usec做更小精度的超时控制
    struct timeval tv;
    tv.tv_sec = 3;  
    tv.tv_usec = 0;
    if (select(clientfd + 1, NULL, &writeset, NULL, &tv) != 1)
    {
		std::cout << "[select] connect to server error." << std::endl;
		close(clientfd);
		return -1;
	}
	
	int err;
    socklen_t len = static_cast<socklen_t>(sizeof err);
    if (::getsockopt(clientfd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
	{
        close(clientfd);
		return -1;
	}
        
    if (err == 0)
        std::cout << "connect to server successfully." << std::endl;
    else
    	std::cout << "connect to server error." << std::endl;
    
	//5. 关闭socket
	close(clientfd);

    return 0;
}

当然,在实际的项目中,第 3 个步骤中 Linux 平台上你也可以使用 poll 函数来判断 socket 是否可写;在 Windows 平台上你可以使用 WSAEventSelect 或 WSAAsyncSelect 函数判断连接是否成功,关于这三个函数我们将在后面的章节中详细讲解,这里暂且仅以 select 函数为例。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 首先, 你需要在服务器端使用一种语言(例如: PHP, Python, Ruby, Java)来连接数据库, 并执行 SQL 查询. 然后你可以使用 AJAX 技术在前端 JavaScript 中调用这个服务器端脚本, 从而获取数据库查询的结果. 下面是一个简单的例子: 首先, 假设你的服务器上有一个叫做 "query.php" 的脚本, 它可以执行数据库查询并返回结果: ``` <?php $db_host = 'localhost'; $db_user = 'root'; $db_pass = 'password'; $db_name = 'mydatabase'; $conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name); if (!$conn) { die('Could not connect: ' . mysqli_error($conn)); } $result = mysqli_query($conn, 'SELECT * FROM users'); $rows = array(); while($row = mysqli_fetch_assoc($result)) { $rows[] = $row; } echo json_encode($rows); mysqli_close($conn); ?> ``` 然后你可以在前端 JavaScript 中使用 AJAX 调用这个脚本: ``` function getUsers() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var users = JSON.parse(this.responseText); console.log(users); // 处理返回的数据 } }; xhttp.open("GET", "query.php", true); xhttp.send(); } getUsers(); ``` 注意: 为了安全起见, 你应该使用 Prepared Statements 或者使用某种 ORM (Object-Relational Mapping) 框架来避免 SQL 注入攻击. ### 回答2: 要使用JavaScript连接数据库并查询内容,需要先了解以下几个步骤: 1. 建立数据库连接:在JavaScript中可以使用一些库或框架来建立数据库连接,例如Node.js中的mysql模块、MongoDB的官方驱动程序等。你需要使用正确的数据库连接字符串、用户名和密码等来连接数据库。 2. 编写查询语句:根据你的数据库类型和表结构,编写查询语句以请求所需的数据。查询语句的具体语法和用法因数据库类型而异,例如使用SQL语句进行关系型数据库查询,或使用特定的方法来查询文档型数据库。 3. 执行查询:在建立数据库连接后,使用JavaScript代码执行查询语句。根据库或框架的不同,你可以选择使用回调函数、Promise或异步/同步操作等方式来处理查询结果。 4. 处理查询结果:在获取查询结果后,你可以使用JavaScript代码对返回的数据进行处理,例如解析JSON格式的结果、提取所需信息、遍历数据等。 5. 显示或应用查询结果:最后,你可以将查询结果应用到你的网页、应用程序或其他项目中。你可以使用JavaScript来操作DOM,更新页面内容,或者将数据应用到其他逻辑中。 需要注意的是,使用JavaScript连接数据库查询内容需要注意安全性和数据验证,避免发生SQL注入等风险。并且,根据你使用的库和数据库类型,还需要了解和适应不同的错误处理机制和调试方法。 ### 回答3: 要使用JavaScript连接数据库查询内容,首先需要确保你的数据库和服务器已经配置好。 以下是一些基本步骤: 1.确保你已经安装了适当的数据库管理系统,如MySQL、PostgreSQL或MongoDB。 2.创建一个数据库,并在其中创建一个表来存储你的数据。确保表的结构符合你的需求。 3.在服务器端设置好数据库的连接,可以使用Node.js和Express等框架来处理这个任务。在服务器代码中,你需要指定数据库的连接信息,如数据库的主机名、用户名、密码等。 4.在前端页面中添加JavaScript代码,以便与服务器进行通信并执行数据库查询。 5.使用AJAX(Asynchronous JavaScript and XML)或Fetch API来发送一个请求到服务器,以获取你想要的数据库内容。你可以将查询参数作为请求的一部分发送到服务器。 6.服务器收到请求后,根据你的查询参数执行相应的数据库查询操作,然后将结果返回给前端。 7.在前端页面中,通过JavaScript将返回的查询结果展示出来。你可以使用DOM操作来动态创建、修改和删除元素,以便将查询结果显示在页面上。 需要注意的是,使用JavaScript连接数据库查询内容是一个复杂的任务,涉及服务器端和客户端的编程。为了确保数据的安全性和可靠性,你可能需要在服务器端实施一些安全措施,如身份验证和数据验证。另外,你还需要学习和掌握相应的数据库查询语言,如SQL或MongoDB查询语言。 希望这些指导对你有帮助,祝你成功连接数据库并实现查询功能!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值