UDP中使用connect函数

先看一个例子,下面是一个采用UDP协议的客户程序,调用sendto向服务器端发送数据,而服务器并不启动,sendto返回,表示套接字缓冲区中有可用空间,程序会阻塞在recvfrom。


net.h

    #ifndef MY_NET_H  
    #define MY_NET_H  
      
    #include <sys/types.h>        
    #include <sys/socket.h>  
    #include <stdio.h>  
    #include <stdlib.h>  
    #include <arpa/inet.h>  
    #include <unistd.h>  
    #include <time.h>  
    #include <string.h>  
    #include <sys/select.h>  
    #include <sys/time.h>  
    #include <errno.h>
    #include <signal.h>
      
    #define MAXLINE 4096  
    #define SA struct sockaddr  
    #define LISTENEQ 10  
      
    /*err_quit*/  
    void err_quit(const char* err_string)  
    {  
        printf("%s\n", err_string);  
        exit(-1);  
    }  
      
    /*err_sys*/  
    void err_sys(const char* err_string)  
    {  
        perror(err_string);  
        exit(-1);  
    }  
      
    /*Socket*/  
    int Socket(int domain, int type, int protocol)  
    {  
        int sockfd = socket(domain, type, protocol);  
        if (sockfd == -1)  
            err_sys("socket error");  
        return sockfd;  
    }  
      
    /*Inet_pton*/  
    int Inet_pton(int af, const char *src, void *dst)  
    {  
        int r;  
        if ((r = inet_pton(af, src, dst)) <= 0)  
            err_sys("inet_pton error");  
        return r;  
    }  
      
    /*Connect*/  
    int Connect(int sockfd,   
                const struct sockaddr *addr,  
                socklen_t addrlen)  
    {  
        int r;  
        if ((r = connect(sockfd, addr, addrlen)) == -1)  
            err_sys("connect error");  
        return r;  
    }  
      
    /*Bind*/  
    int Bind(int sockfd,   
            const struct sockaddr *addr,  
            socklen_t addrlen)  
    {  
        int r;  
        r = bind(sockfd, addr, addrlen);  
        if (r == -1)  
            err_sys("bind error");  
        return r;  
    }  
      
    /*Listen*/  
    int Listen(int sockfd, int backlog)  
    {  
        int r;  
        r = listen(sockfd, backlog);  
        if (r == -1)  
            err_sys("listen error");  
        return r;  
    }  
      
    /*Accept*/  
    int Accept(int sockfd,   
               struct sockaddr *addr,   
               socklen_t *addrlen)  
    {  
        int r;  
        r = accept(sockfd, addr, addrlen);  
        if (r == -1)  
            err_sys("accept error");  
        return r;  
    }  
      
    /*Close*/  
    int Close(int fd)  
    {  
        int r = close(fd);  
        if (r == -1)  
            err_sys("close error");  
        return r;  
    }  
      
    /*Read*/  
    int Read(int fd, void *buf, size_t count)  
    {  
        int r;  
        r = read(fd, buf, count);  
        if (r == -1)  
            err_sys("read error");  
        return r;  
    }  
      
    /*Write*/  
    int Write(int fd, const void *buf, size_t count)  
    {  
        int r;  
        r = write(fd, buf, count);  
        if (r == -1)  
            err_sys("write error");  
        return r;  
    }  
      
    /*Writen*/  
    //返回剩余的字符  
    int Writen(int fd, const void *buf, int len)  
    {  
        int r;  
        const char* ptr;  
        int nleft = len;  
          
        if(len <= 0)  
            return -1;  
          
        ptr = buf;  
        while (1)  
        {  
            r = Write(fd, ptr, len);  
            nleft -= r;  
            ptr += r;  
            len -= r;  
      
            if (nleft == 0)  
                return 0;  
        }     
    }  
      
    /*Readn*/  
    int Readn(int fd, void *buf, int len)  
    {  
        int r;  
        char* ptr;  
          
        ptr = buf;  
        while (1)  
        {  
            r = Read(fd, ptr, len);  
            if (r == 0)  
                return 0;  
            if (len-r > 0)  
            {  
                len -= r;  
                ptr += r;  
            }  
            else if (len-r == 0)  
                return len;  
        }  
    }  
      
    #endif  

客户程序

#include "net.h"

main()
{
	int sockfd;
	struct sockaddr_in addr;
	char buf[1024];
	int r;

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	addr.sin_family = AF_INET;
	addr.sin_port = htons(22222);
	//addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

	while(fgets(buf, 1024, stdin) != NULL)
	{
		sendto(sockfd, buf, strlen(buf), 0, 
			   (struct sockaddr*)&addr, sizeof(addr));
		r = recvfrom(sockfd, buf, 1024, 0, NULL, NULL);
		if (r == -1)
		{
			perror("");
			exit(-1);
		}
		buf[r] = 0;
		printf("%s\n", buf);
	}
}


用tcpdump工具查看,

发现第一行发送数据。
第二行服务器响应一个ICMP消息,提示端口不可达。这个ICMP错误是在sendto调用之后才得到,而这个错误不会返回给进程。


recvfrom可以返回的信息只有errno值,它没有办法返回出错数据报的目的ip和目的端口。既然端口不可达,程序又无法获得通知,如果UDP客户程序运行时,假如服务程序还没启动,那么客户程序如何才能知道服务器地址不可达呢?
一个基本规则是:对于一个UDP套接字,除非它已连接,否则由它引发的异步错误并不返回给它。


TCP套接字调用connect会引发三路握手,而UDP套接字则不会引发三路握手,只是检查是否存在错误,然后立即返回。
修改后的程序

#include "net.h"

main()
{
	int sockfd;
	struct sockaddr_in addr;
	char buf[1024];
	int r;

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	addr.sin_family = AF_INET;
	addr.sin_port = htons(22222);
	//addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
	
	Connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));

	while(fgets(buf, 1024, stdin) != NULL)
	{
		sendto(sockfd, buf, strlen(buf), 0, 
			   (struct sockaddr*)&addr, sizeof(addr));
		r = recvfrom(sockfd, buf, 1024, 0, NULL, NULL);
		if (r == -1)
		{
			perror("");
			exit(-1);
		}
		buf[r] = 0;
		printf("%s\n", buf);
	}
}

执行结果

启动客户进程后时并没有提示错误,因为connect不会引发三次握手,
输入后,将提示connection refused.
如果指定的ip地址并不存在,那么即使connect了,recvfrom也不会返回。
connect之后recvfrom返回使得程序得到通知的前提是该ip的主机存在,并且没有运行服务器程序。


调用connect并不给对端主机发送任何信息,只保存对端ip地址和端口号。在一个未绑定的套接字上调用connect也会给该套接字指派一个临时端口。

#include "net.h"

main()
{
	int sockfd;
	struct sockaddr_in addr1, addr2;
	char buf[1024];
	int r;
	int len;

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	addr1.sin_family = AF_INET;
	addr1.sin_port = htons(22222);
	inet_pton(AF_INET, "192.168.1.12", &addr1.sin_addr);
	
	Connect(sockfd, (SA*)&addr1, sizeof(addr1));
	Getsockname(sockfd, (SA*)&addr2, &len);
	printf("ip:%s\n", inet_ntoa(addr2.sin_addr));
	r = ntohs(addr2.sin_port);
	printf("port:%d\n", r);	
}

多次调用connect的情况:
1.为了指定新的对端ip和端口。
此时,connect函数的参数应为指向新指定对端的结构的指针,和结构的长度。
如下面的程序,先connect指定一个对端,之后再指定第二个对端,每次只可以与指定的对端之间通信。


客户

#include "net.h"

main()
{
	int sockfd;
	struct sockaddr_in addr1, addr2;
	char buf[1024];
	int r;
	int len;

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	
	addr1.sin_family = AF_INET;
	addr1.sin_port = htons(22222);
	inet_pton(AF_INET, "127.0.0.1", &addr1.sin_addr);
	
	addr2.sin_family = AF_INET;
	addr2.sin_port = htons(22223);
	inet_pton(AF_INET, "127.0.0.1", &addr2.sin_addr);
	
	Connect(sockfd, (SA*)&addr1, sizeof(addr1));
	fgets(buf, sizeof(buf), stdin);
	sendto(sockfd, buf, strlen(buf), 0, NULL, 0);
	r = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
	if (r == -1)
		perror("");
	
	Connect(sockfd, (SA*)&addr2, sizeof(addr2));//更改唯一对应的一个对端
	fgets(buf, sizeof(buf), stdin);
	sendto(sockfd, buf, strlen(buf), 0, NULL, 0);
	r = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
	if (r == -1)
		perror("");
}

服务器

#include "net.h"

main()
{
	int sockfd;
	struct sockaddr_in addr;
	char buf[1024];
	int r;
	int len;

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	
	addr.sin_family = AF_INET;
	addr.sin_port = htons(22223);
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
	
	Bind(sockfd, (SA*)&addr, sizeof(addr));
	r = recvfrom(sockfd, buf, sizeof(buf),
	             0, (SA*)&addr, &len);
	if (r == -1)
	{
		perror("recvfrom error");
		exit(-1);
	}
	buf[r] = 0;
	printf("%s\n", buf);
	sendto(sockfd, buf, strlen(buf), 
		   0, (SA*)&addr, len);
}


2.已连接的套接字,如果第二次connect前将套接字地址族设置为AF_UNSPEC,再调用connect,那么将断开之前的连接。也就是说不会返回错误,并且之后的发送数据要在函数里指定对端地址。

下面的例子,假如服务器没启动对应的服务程序,(服务器已启动)

#include "net.h"

main()
{
	int sockfd;
	struct sockaddr_in addr1, addr2;
	char buf[1024];
	int r;
	int len;

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	addr1.sin_family = AF_INET;
	addr1.sin_port = htons(22222);
	inet_pton(AF_INET, "127.0.0.1", &addr1.sin_addr);
	
	Connect(sockfd, (SA*)&addr1, sizeof(addr1));
	fgets(buf, sizeof(buf), stdin);
	sendto(sockfd, buf, strlen(buf), 0, NULL, 0);
	r = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
	if (r == -1) //返回错误
		perror("");
	
	addr1.sin_family = AF_UNSPEC; //设置地址族
	Connect(sockfd, (SA*)&addr1, sizeof(addr1));//在已连接的udp套接字上再次调用connect,将断开连接,之后recvfrom会阻塞,而不会返回错误
	fgets(buf, sizeof(buf), stdin);
	sendto(sockfd, buf, strlen(buf), 0, (SA*)&addr1, sizeof(addr1));
	r = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
	if (r == -1)
		perror("");
}




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值