根据《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 而言,可以有一定程度的性能提升。原因:
- 如果不使用 connect 方式,每次发送报文都会需要这样的过程:连接套接字→发送报文→断开套接字→连接套接字→发送报文→断开套接字 →………
- 如果使用 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 连接的情况,可能适合于:提升客户端发送性能,且服务器只接收,不发送的情况。也就是服务端仅作为消费者,多个客户端作为生产者。