2:UDP中connect操作与TCP中connect操作有着本质区别。
TCP中调用connect会引起三次握手,client与server建立连结.UDP中调用connect内核仅仅把对端ip&port记录下来.
3:UDP中可以多次调用connect,TCP只能调用一次connect.
UDP多次调用connect有两种用途:1,指定一个新的ip&port连结. 2,断开和之前的ip&port的连结.
指定新连结,直接设置connect第二个参数即可.断开连结,需要将connect第二个参数中的sin_family设置成 AF_UNSPEC即可.
4:UDP中使用connect可以提高效率.原因如下:
普通的UDP发送两个报文内核做了如下:#1:建立连结#2:发送报文#3:断开连结#4:建立连结#5:发送报文#6:断开连结
采用connect方式的UDP发送两个报文内核如下处理:#1:建立连结#2:发送报文#3:发送报文另外一点, 每次发送报文内核都由可能要做路由查询.
5:采用connect的UDP发送接受报文可以调用send,write和recv,read操作.当然也可以调用sendto,recvfrom.
调用sendto的时候第五个参数必须是NULL,第六个参数是0.调用recvfrom,recv,read系统调用只能获取到先前connect的ip&port发送的报文.
UDP中使用connect的好处:1:会提升效率.前面已经描述了.2:高并发服务中会增加系统稳定性.原因:假设client A 通过非connect的UDP与server B,C通信.B,C提供相同服务.为了负载均衡,我们让A与B,C交替通信.A 与 B通信IPa:PORTa <----> IPb:PORTb;
A 与 C通信IPa:PORTa' <---->IPc:PORTc
假设PORTa 与 PORTa'相同了(在大并发情况下会发生这种情况),那么就有可能出现A等待B的报文,却收到了C的报文.导致收报错误.解决方法内就是采用connect的UDP通信方式.在A中创建两个udp,然后分别connect到B,C.
///
对于linux网络编程,UDP协议不是面向连接的协议,直接把数据报发送到链路层,至于能不能到达目的IP和端口,它不关注;大部分时候再编写代码时候只需要在sendto函数中指定你要发送的端口和IP地址就可以了,不用绑定IP和端口。不过你是否考虑过,UDP到底是否可以进行connect,如果对UDP进行connect函数的调用,会发生什么现象呢?
在进行socket网络编程代码编写时,我个人觉得有如下三个步骤在客户端和服务端是一致的,只是触发的时机不同。
- socket创建通信套接口句柄(fd)---------------创建插座(抽象比喻)
- bind绑定本地的IP和端口到句柄--------------在插座的后面连接上电源线
- connect连接远端的IP和端口号到句柄,建立数据交换线路------------在插座的前面连接上使用者
以上三个步骤在TCP协议通信中client是必须存在,但是UDP协议中,可能针对connect看到的不多。
依据上面的说明,UDP可以分为如下两种:
- 未连接的UDP,新创建的UDP套接字。
- 已连接的UDP,调用connect就会这样。
比较1/2两种UDP,已经连接的UDP有如下特性:
- 不需要给输出操作指定目的IP和目的端口,写到UDP的缓冲区里的数据,将自动发送到你调用connect指定的IP和端口。
- 在一个已连接的UDP套接字上,内核由输入操作返回的数据报只有那些来自connect所指定的协议地址的数据报。目的地为这个已连接的UDP套接字的本地协议地址(IP和端口),远端地址不是该套接字早先connect到的协议地址的数据报,不会投递到该套接字。这样就限制了已连接的UDP套接字能且只能与一个对端交换数据报。
- 由已连接的套接字引发的异步错误发回给他们所在的进程,而未连接的UDP套接字不接受任何异步错误。
- 读写的操作接口方法增多了,除了可以使用sendto和recvfrom的接口外,还可以使用tcp的那套操作接口--read/readv/readmsg和write/writev等
对一个UDP的套接口多次调用connect的情况如何?
- 连接新的IP和端口
- 断开前面的连接
第一个目的不同于TCP连接connect的使用:对于TCP连接,connect只能调用一次;针对UDP则可以connect到不同的server,eg:client需要和多个服务器同时通信。
第二个目的为了断开一个已连接的UDP连接,再次调用connect时,把套接字地址结构的地址簇成员(IPv4为sin_family,IPv6为sin6_family),设置为AF_UNSPEC即可。
具体的操作例子如下:
回显服务器
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
int main()
{
int sockListener,nMsgLen;
char szBuf[1024];
struct sockaddr_in addrListener;
socklen_t addrLen;
addrLen=sizeof(struct sockaddr_in);
bzero(&addrListener,sizeof(addrListener));
addrListener.sin_family=AF_INET;
addrListener.sin_port=htons(8000);
if((sockListener=socket(AF_INET,SOCK_DGRAM,0))==-1)
{
perror("error in getting a socket");
exit(1);
}
if(bind(sockListener,(struct sockaddr*)&addrListener,sizeof(addrListener))==-1)
{
perror("bind a listener for a socket");
exit(2);
}
struct sockaddr_in addrClient;
cout<<"callback server begin to listen"<<endl;
while(true)
{
nMsgLen=recvfrom(sockListener,szBuf,1024,0,(struct sockaddr*)&addrClient,&addrLen);
if(nMsgLen>0)
{
szBuf[nMsgLen]='\0';
cout<<"send back:"<<szBuf<<endl;
sendto(sockListener,szBuf,nMsgLen,0,(struct sockaddr*)&addrClient,addrLen);
}
}
}
客户端1的例子
刚开始是connect,收到一个数据包后断开connect,再收到一个包后再次connect,依次.......
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
using namespace std;
int main()
{
int sockClient,nMsgLen,nReady;
char szRecv[1024],szSend[1024],szMsg[1024];
struct sockaddr_in addrServer,addrClient,addrLocal;
socklen_t addrLen;
fd_set setHold,setTest;
sockClient=socket(AF_INET,SOCK_DGRAM,0);
addrLen=sizeof(struct sockaddr_in);
bzero(&addrServer,sizeof(addrServer));
addrServer.sin_family=AF_INET;
addrServer.sin_addr.s_addr=inet_addr("127.0.0.1");
addrServer.sin_port=htons(8000);
addrLocal.sin_family=AF_INET;//bind to a local port
addrLocal.sin_addr.s_addr=htonl(INADDR_ANY);
addrLocal.sin_port=htons(9000);
if(bind(sockClient,(struct sockaddr*)&addrLocal,sizeof(addrLocal))==-1)
{
perror("error in binding");
exit(2);
}
int f = 0;
if(connect(sockClient,(struct sockaddr*)&addrServer,sizeof(addrServer))==-1)
{
perror("error in connecting");
exit(1);
}
f = 1;
FD_ZERO(&setHold);
FD_SET(STDIN_FILENO,&setHold);
FD_SET(sockClient,&setHold);
cout<<"you can type in sentences any time"<<endl;
while(true)
{
setTest=setHold;
nReady=select(sockClient+1,&setTest,NULL,NULL,NULL);
if(FD_ISSET(0,&setTest))
{
nMsgLen=read(0,szMsg,1024);
write(sockClient,szMsg,nMsgLen);
}
if(FD_ISSET(sockClient,&setTest))
{
if ( 1 == f )
{
// 1:by read/write........
//nMsgLen=read(sockClient,szRecv,1024);
// 2:by recvfrom/sendto
nMsgLen = recvfrom(sockClient,szRecv,1024,0,NULL,NULL);
perror("error in connecting recvfrom");
// unconnect
addrServer.sin_family=AF_UNSPEC;
connect(sockClient,(struct sockaddr*)&addrServer,sizeof(addrServer));
f = 0;
}
else
{
f = 1;
nMsgLen = recvfrom(sockClient,szRecv,1024,0,(struct sockaddr*)&addrServer,&addrLen);
// connect
bzero(&addrServer,sizeof(addrServer));
addrServer.sin_family=AF_INET;
addrServer.sin_addr.s_addr=inet_addr("127.0.0.1");
addrServer.sin_port=htons(8000);
connect(sockClient,(struct sockaddr*)&addrServer,sizeof(addrServer));
}
szRecv[nMsgLen]='\0';
cout<<"read:"<<szRecv<<endl;
}
}
}
客户端2,主动向客户端1发送数据
主要是为了证明,在client1向server建立连接后,不会接受client2的数据,断开连接后会接受client2的数据
#include<string.h>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
int main()
{
socklen_t addrLen=sizeof(struct sockaddr_in);
struct sockaddr_in addrServer;
char szMsg[1024];
int sockClient;
addrServer.sin_family=AF_INET;
addrServer.sin_addr.s_addr=inet_addr("127.0.0.1");
addrServer.sin_port=htons(9000);
sockClient=socket(AF_INET,SOCK_DGRAM,0);
while(true)
{
static int id=0;
snprintf(szMsg,sizeof(szMsg),"this is %d",id++);
sendto(sockClient,szMsg,strlen(szMsg),0,(struct sockaddr*)&addrServer,sizeof(addrServer));
sleep(1);
}
}
通过connect建立的UDP套接口,可以有效的提高系统的整体性能,减少未连接的UDP上每次发送数据报时的连接建立/连接拆除的过程,只需要建立一次即可