在上一篇中遗留了一个问题:sendto函数产生的异步错误一般是不会返回给udp套接字的(主要是因为udp是无连接的原因),如果这个错误要返回给udp套接字,那么就需要调用connect函数。
是的,你没看错,udp也可以调用connect函数达到面向连接,但是这并不意味着udp也会产生三次握手的过程。我们调用connect函数最主要的目的只是记录对端的ip地址和端口,并让内核检查是否存在错误(例如端口不可达等),然后再返回给调用connect的进程。
这里我们来区分一下面向连接和面向无连接的udp套接字的:
1. 对于面向无连接的udp套接字,也就是调用socket函数创建的套接字。而面向连接的udp套接字就是调用了connect函数。
2. 对于面向无连接的udp套接字,我们可以通过sendto函数向指定的目的ip地址和端口号发送数据,而对于面向连接的udp套接字来说,则是使用write或send函数向connect函数指定的目的ip地址和端口号发送数据。
需要注意的是,由于已连接的udp套接字调用了connect函数绑定了目的ip地址和端口,所以在调用sendto函数就不能指定目的地址了,即忽略第五,六参数(指定为null)。同理,在接收数据时,因为udp套接字只接收connect函数绑定的目的ip地址和端口的数据报,所以也不用调用recvfrom函数了,而是调用read或recv函数。
3. 面向连接的udp套接字会把错误返回给当前进程,未连接的套接字则不会返回错误,另外POSIX规定:未连接的udp套接字调用sendto函数发送数据时如果不指定目的地址会返回ENOTCONN错误,需要注意的是,对于不同的linux实现可能返回的错误也不尽相同的,例如4.4BSD实现会返回EDESTADDRREQ错误,而不是ENOTCONN错误。
修改udp客户端程序:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
#include <errno.h>
#define MAXLINE 1024
#define SERV_PORT 10001
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
int sockfd, n;
char buf[MAXLINE];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "192.168.1.22", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
//调用connect函数
int ret = connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr));
if(ret != 0){
perror("connect error:");
}
while(fgets(buf, MAXLINE, stdin) != NULL){
n = sendto(sockfd, buf, strlen(buf), 0, NULL, 0);
if(errno == ENOTCONN){
perror("sendto error:");
}
n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
if (n == -1)
perror("recvfrom error");
write(STDOUT_FILENO, buf, n);
}
close(sockfd);
return 0;
}
程序执行结果如下:
当udp客户端调用connect函数后,通过tcpdump抓包我们可以看到客户端和服务端双方并没有三次握手的过程。
此时停止服务端,再运行客户端,我们发现udp客户端的recvfrom函数返回了错误。