UDP也可以有connect连接

根据《TCP/IP,UDP通信——C++实现》文章,知道了:UDP 等于无连接协议。 但是有的时候,UDP又有了connect连接,这是怎么回事呢?以下简单探究一番。

调用 connect 将 UDP 套接字和 IPv4 地址进行了“绑定”,这里 connect 函数的名称有点让人误解,其实可能更好的选择是叫做 setpeername;

UDP 套接字调用 connect 函数,但是和 TCP connect 调用引起 TCP 三次握手、建立 TCP 有效连接不同,UDP connect 函数的调用,并不会引起和服务器目标端的网络交互,也就是说,并不会触发所谓的“握手”报文发送和应答。主要是为了让应用程序能够接收“异步错误”的信息。

在对 UDP 进行 connect 之后,关于收发函数的使用,很多书籍是这样推荐的:

  • 使用 send 或 write 函数来发送,如果使用 sendto 需要把相关的 to 地址信息置零;
  • 使用 recv 或 read 函数来接收,如果使用 recvfrom 需要把对应的 from 地址信息置零。
  • 所以,为了保险起见,还是用推荐的函数进行传输。

服务器端不会主动发起 connect 操作,因为一旦如此,服务器端就只能响应一个客户端了。从后面的代码例子中,也可以看出。

不过,有时候也不排除这样的情形,一旦一个客户端和服务器端发送 UDP 报文之后,该服务器端就要服务于这个唯一的客户端。

客户端通过 connect 绑定服务端的地址和端口,对 UDP 而言,可以有一定程度的性能提升。原因:

  1. 如果不使用 connect 方式,每次发送报文都会需要这样的过程:连接套接字→发送报文→断开套接字→连接套接字→发送报文→断开套接字 →………
  2. 如果使用 connect 方式,就会变成下面这样:连接套接字→发送报文→发送报文→……→最后断开套接字

我们知道,连接套接字是需要一定开销的,比如需要查找路由表信息。所以,UDP 客户端程序通过 connect 可以获得一定的性能提升。

以下程序,是基于VS2010  WIN32控制台应用程序写的。

UdpConnServer.cpp 文件:

#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
using namespace std;

#define    MAXLINE     100
#define SERV_PORT 6000

int main() 
{
	// 加载socket库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2||
		HIBYTE(wsaData.wVersion) != 2)
	{
		WSACleanup();
		return 0;
	}

	// 创建套接字
	SOCKET socket_fd;
	socket_fd = socket(AF_INET, SOCK_DGRAM, 0);

	// 创建服务器地址,并绑定
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERV_PORT);
	server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	int server_len = sizeof(SOCKADDR);//sizeof(server_addr);
	bind(socket_fd, (SOCKADDR*)&server_addr, server_len);

	// 创建客户端对象
	SOCKADDR_IN client_addr;
	int client_len = sizeof(SOCKADDR);   

	char recvBuf[MAXLINE];// 存储接收数据
	char sendBuf[MAXLINE];// 存储发送数据

	// 在没有连接之前,先调用recvfrom()测试接收数据。接收成功,说明UDP是无连接的
	int n = recvfrom(socket_fd, recvBuf, MAXLINE, 0, (SOCKADDR*)&client_addr, &client_len);
	if (n < 0)
	{
		cout << "recv error!" << endl;
		system("pause");
		return 0;
	}
	printf("before connect, receive message from client %d bytes : %s \n", sizeof(recvBuf), recvBuf);
	cout << endl;

	// 建立连接
	n = connect(socket_fd, (SOCKADDR*)&client_addr, client_len);
	if (n < 0)
	{
		cout << "connect failed!" << endl;
	}


	while(TRUE) 
	{
		printf("please input server sending:  ");
		gets(sendBuf);
		size_t rt = send(socket_fd, sendBuf, strlen(sendBuf)+1, 0);// 连接之后,用send进行发送
		if (rt < 0) 
		{
			cout << "send failed" << endl;
		}
		printf("send bytes: %d \n\n", rt);

		memset(recvBuf, 0, 100);// 重置接收空间
		n = recv(socket_fd, recvBuf, MAXLINE, 0);
		if (n < 0)
			cout << "recvfrom failed" << endl;
		printf("receive from client: %s \n", recvBuf);
		printf("receive  bytes: %d \n\n", rt);

		if(strncmp(recvBuf, "goodbye", 7) == 0)
		{
			printf("Client don't want to chat!");
			send(socket_fd, "goodbye", strlen("goodbye")+1, 0);
			break;
		}
	}

	closesocket(socket_fd);
	WSACleanup();
	system("pause");
	return 0;
}

UdpConnClient.cpp 文件:

#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
using namespace std;

#define    MAXLINE     100
#define SERV_PORT 6000

int main() 
{

	// 加载socket库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2||
		HIBYTE(wsaData.wVersion) != 2)
	{
		WSACleanup();
		return 0;
	}

	//int socket_fd;
	SOCKET socket_fd;
	socket_fd = socket(AF_INET, SOCK_DGRAM, 0);

	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERV_PORT);
	server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	int server_len = sizeof(SOCKADDR);//sizeof(server_addr);

	// 即使没有打开服务器端,客户端不会提示连接失败。说明UDP是无连接的。
	int ret = connect(socket_fd, (struct sockaddr *) &server_addr, server_len);
	// 此处没有报错,说明UDP也可以是 有连接的。
	if (ret < 0) 
	{
		 cout << "error" << endl;
		 system("pause");
		 return 0;
	}

	char send_line[MAXLINE];// 存储发送数据
	char recv_line[MAXLINE];// 存储接收数据

	int n;

	while(TRUE) 
	{
		printf("please input client sending :  ");
		gets(send_line);
		size_t rt = send(socket_fd, send_line, strlen(send_line)+1, 0);
		if (rt < 0) 
		{
			cout << "sendto failed" << endl;
		}
		printf("send bytes: %d \n\n", rt);

		int len = 0;
		recv_line[0] = 0;
		n = recv(socket_fd, recv_line, MAXLINE, 0);
		if (n < 0)
			cout << "recvfrom failed" << endl;
		recv_line[n] = 0;
		printf("receive from server: %s \n", recv_line);
		printf("receive  bytes: %d \n\n", n);
		if(strncmp(recv_line, "goodbye", 7) == 0)
		{
			printf("Server don't want to chat!");
			send(socket_fd, "goodbye", strlen("goodbye")+1, 0);
			break;
		}
	}

	closesocket(socket_fd);
	WSACleanup();
	system("pause");
	return 0;
}

 运行结果如下:

UDP不添加connect的时候,一个server端可以连接好几个client端,但是这里添加connect之后,server就具有排他性了,只能允许一个client连接。

从下面的运行结果,也可以看出,服务端加上connect之后,具有排他性。2号客户端,根本不会影响到1号客户端和服务器的对话,而且服务器也接收不到客户端2的信息。

下面把server端的connect 部分,注释掉,再调整一下其他代码的顺序。看看时候可以同时连接多个client端。

UdpConnServer.cpp 文件,去掉连接部分,调整其他顺序。(以下文件,不能发送数据,客户端也不能接收到)

#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
using namespace std;

#define    MAXLINE     100
#define SERV_PORT 6000

int main() 
{
	// 加载socket库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2||
		HIBYTE(wsaData.wVersion) != 2)
	{
		WSACleanup();
		return 0;
	}

	// 创建套接字
	SOCKET socket_fd;
	socket_fd = socket(AF_INET, SOCK_DGRAM, 0);

	// 创建服务器地址,并绑定
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERV_PORT);
	server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	int server_len = sizeof(SOCKADDR);//sizeof(server_addr);
	bind(socket_fd, (SOCKADDR*)&server_addr, server_len);

	// 创建客户端对象
	SOCKADDR_IN client_addr;
	int client_len = sizeof(SOCKADDR);   

	char recvBuf[MAXLINE];// 存储接收数据
	char sendBuf[MAXLINE];// 存储发送数据

	// 在没有连接之前,先调用recvfrom()测试接收数据。接收成功,说明UDP是无连接的
// 	int n = recvfrom(socket_fd, recvBuf, MAXLINE, 0, (SOCKADDR*)&client_addr, &client_len);
// 	if (n < 0)
// 	{
// 		cout << "recv error!" << endl;
// 		system("pause");
// 		return 0;
// 	}
//	printf("before connect, receive message from client %d bytes : %s \n", sizeof(recvBuf), recvBuf);
//	cout << endl;

// 	printf("receive from client: %s \n", recvBuf);
// 	printf("receive  bytes: %d \n\n", n);

	// 建立连接
// 	n = connect(socket_fd, (SOCKADDR*)&client_addr, client_len);
// 	if (n < 0)
// 	{
// 		cout << "connect failed!" << endl;
// 	}


	while(TRUE) 
	{
		memset(recvBuf, 0, 100);// 重置接收空间
		int n = recv(socket_fd, recvBuf, MAXLINE, 0);
		if (n < 0)
			cout << "recvfrom failed" << endl;
		printf("receive from client: %s \n", recvBuf);
		printf("receive  bytes: %d \n\n", n);

		if(strncmp(recvBuf, "goodbye", 7) == 0)
		{
			printf("Client don't want to chat!");
			send(socket_fd, "goodbye", strlen("goodbye")+1, 0);
			break;
		}

		printf("please input server sending:  ");
		gets(sendBuf);
		//size_t rt = send(socket_fd, sendBuf, strlen(sendBuf)+1, 0);// 连接之后,用send进行发送
		size_t rt = sendto(socket_fd, sendBuf, strlen(sendBuf)+1, 0, 
			(SOCKADDR*)&client_addr, client_len);  // 没有使用connect,用sendto发送。
		if (rt < 0) 
		{
			cout << "send failed" << endl;
		}
		printf("send bytes: %d \n\n", rt);

	}

	closesocket(socket_fd);
	WSACleanup();
	system("pause");
	return 0;
}

从运行结果,可以看到,server只能接收到信息,不能发送出去。然后,server端阻塞在发送函数处,client端阻塞在接收函数处。

猜想,这种仅在client端,添加connect 连接的情况,可能适合于:提升客户端发送性能,且服务器只接收,不发送的情况。也就是服务端仅作为消费者,多个客户端作为生产者。

 

参考链接:https://time.geekbang.org/column/article/129807 

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值