上一篇我们提到,除非套接字已连接,否则异步错误是不会返回到UDP套接字的。我们确实可以给UDP套接字调用connect,然而这样做的结果却与TCP连接大相径庭:没有三次握手。内核只是检查是否存在立即可知的错误(例如一个显然不可达的目的地),记录对端的IP地址和端口号(取自传递给connect的套接字地址结构),然后立即返回到调用进程。

有了这个能力后,我们必须区分:

(1)未连接UDP套接字,新创建UDP套接字默认如此;

(2)已连接UDP套接字,对UDP套接字调用connect的结果。

对于已连接UDP套接字,与默认的未连接UDP套接字相比,发生了三个变化:

(1)我们再也不能给输出操作指定目的IP地址和端口号。也就是说,我们不使用sendto,而改用write或send。写到已连接UDP套接字上的任何内容都自动发送到由connect指定的协议地址(例如IP地址和端口号)。(其实我们可以给已连接UDP套接字调用sendto,但是不能指定目的地址。sendto的第五个参数必须为空,第六个参数应该为0)。

后面有在ubuntu 10.04系统下的验证。

(2)我们不必使用recvfrom以获悉数据报的发送者,而改用read,recv或recvmsg。在一个已连接UDP套接字上,由内核为输入操作返回的数据报只有那些来自connect所指定协议地址的数据报。确切的说,一个已连接的UDP套接字仅仅与一个IP地址交换数据报,因为connect到多播或广播地址是可能的)。

(3)由已连接的UDP套接字引发的异步错误会返回给他们所在的进程,而未连接UDP套接字不接受任何异步错误

应用进程首先调用connect指定对端的IP地址和端口号,然后使用read和write与对端进程交换数据。来自任何其他IP地址或端口的数据报(上中我们用“???”表示)不投递给这个已连接套接字,因为他们要么源IP地址要么源UDP端口不与该套接字connect到的协议地址相匹配。这些数据报可能投递给同一个主机上的其他某个UDP套接字。如果没有相匹配的其他套接字,UDP将丢弃他们并生成相应的ICMP端口不可达错误。

1.给一个UDP套接字多次调用connect

拥有一个已连接UDP套接字的进程可出于下列两个目的之一再次调用connect:

  • 指定新的IP地址和端口号;
  • 断开套接字。

第一个目的(即给一个已连接UDP套接字指定新的对端)不同于TCP套接字中的connect的使用:对于TCP套接字,connect只能调用一次

为了断开一个已UDP套接字连接,我们再次调用connect时把套接字地址结构的地址族成员(sin_family)设置为AF_UNSPEC。使套接字断开连接的是在已连接UDP套接字上调用connect的进程。

2.性能

在一个未连接的UDP套接字上给两个数据报调用sendto函数于是涉及内核执行下列6个步骤:

(1)连接套接字;

(2)输出第一个数据报;

(3)断开套接字连接;

(4)连接套接字;

(5)输出第二个数据报;

(6)断开套接字连接。

调用connect后调用两次write涉及内核执行3个步骤:

(1)连接套接字;

(2)输出第一个数据报;

(3)输出第二个数据报。

客户端程序:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define SERV_PORT 3333
#define MAXLINE 1024
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) typedef struct sockaddr SA;
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
/////////////////////////////////////////////////////////////////////////
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "192.168.2.103", &servaddr.sin_addr);
/////////////////////////////////////////////////////////////////////////////
connect(sockfd, (SA *) pservaddr, servlen); while (fgets(sendline, MAXLINE, fp) != NULL) { n = write(sockfd, sendline, strlen(sendline));
//n = sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
//n = sendto(sockfd, sendline, strlen(sendline), 0, &servaddr, sizeof(servaddr));
//n = sendto(sockfd, sendline, strlen(sendline), 0, NULL, 0);
if (n == -1)
{
if (errno == EISCONN)
ERR_EXIT("sendto");
else
perror("sendto huangcheng");
} //struct sockaddr_in preply_addr;
//socklen_t addrlen;
n = read(sockfd, recvline, MAXLINE);
//n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
//n = recvfrom(sockfd, recvline, MAXLINE, 0, (SA*)&preply_addr, &addrlen);
if (n == -1)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
}
//printf("reply from %s \n",inet_ntoa(preply_addr.sin_addr));
recvline[n] = 0; /* null terminate */
fputs(recvline, stdout);
}
} int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr; if (argc != 2)
ERR_EXIT("usage: udpcli <IPaddress>"); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr); sockfd = socket(AF_INET, SOCK_DGRAM, 0); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0);
}

运行结果:

huangcheng@ubuntu:~$ ./cli 127.0.0.1
huangcheng
recvfrom: Connection refused

虚拟机:

huangcheng@ubuntu:~$ ifconfig
eth0 Link encap:以太网 硬件地址 00:0c:29:88:e0:1f
inet 地址:192.168.2.103 广播:192.168.2.255 掩码:255.255.255.0
inet6 地址: fe80::20c:29ff:fe88:e01f/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 跃点数:1
接收数据包:43472 错误:0 丢弃:0 过载:0 帧数:0
发送数据包:19785 错误:0 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:1000
接收字节:52561935 (52.5 MB) 发送字节:1925585 (1.9 MB)
中断:19 基本地址:0x2000 lo Link encap:本地环回
inet 地址:127.0.0.1 掩码:255.0.0.0
inet6 地址: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 跃点数:1
接收数据包:396 错误:0 丢弃:0 过载:0 帧数:0
发送数据包:396 错误:0 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:0
接收字节:38912 (38.9 KB) 发送字节:38912 (38.9 KB) huangcheng@ubuntu:~$

验证UDP套接字,已连接:

write或send:可以

不指定目的地址的sendto:可以

指定目的地址的send:

(1)connect指定的IP地址是:127.0.0.1,sendto指定的IP地址为:127.0.0.1或者192.168.2.103  均正常。

(2)connect指定的IP地址是:127.0.0.1,sendto指定的IP地址为:192.168.4.103 即不为虚拟机的IP地址时,运行结果sendto huangcheng:Invalid argument,即出错。

注意:在<<UNIX网络编程——基于UDP协议的网络程序>>中也有UDP connect的介绍。

UNIX网络编程——UDP 的connect函数(改进版)的更多相关文章

  1. UNIX网络编程——UDP缺乏流量控制(改进版)

    现在我们查看无任何流量控制的UDP对数据报传输的影响.首先我们把dg_cli函数修改为发送固定数目的数据报,并不再从标准输入读.如下,它写2000个1400字节大小的UDP数据报给服务器. 客户端程序 ...

  2. UNIX网络编程——getsockname和getpeername函数

    UNIX网络编程--getsockname和getpeername函数   来源:网络转载   http://www.educity.cn/linux/1241293.html     这两个函数或者 ...

  3. UNIX网络编程——非阻塞connect:时间获取客户程序

    #include "unp.h" int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec) ...

  4. UNIX网络编程-非阻塞connect和非阻塞accept

    1.非阻塞connect 在看了很多资料之后,我自己的理解是:在socket发起一次连接的时候,这个过程需要一段时间来将三次握手的过程走完,如果在网络状况不好或者是其他的一些情况下,这个过程需要比较长 ...

  5. UNIX网络编程——send与recv函数详解

    #include <sys/socket.h> ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags); ssize_ ...

  6. UNIX网络编程——UDP 中的外出接口的确定

    已连接UDP套接字还可用来确定用于特定目的地的外出接口.这是由connect函数应用到UDP套接字时的一个副作用造成的:内核选择本地IP地址.这个本地IP地址通过为目的IP地址搜索路由表得到外出接口, ...

  7. UNIX网络编程——UDP编程模型

    使用UDP编写的一些常见得应用程序有:DNS(域名系统),NFS(网络文件系统)和SNMP(简单网络管理协议). 客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目 ...

  8. <unix网络编程>UDP套接字编程

    典型的UDP客户/服务器程序的函数调用如下: 1.缓冲区 发送缓冲区用虚线表示,任何UDP套接字都有发送缓冲区,不过该缓冲区仅能表示写到该套接字的UDP数据报的上限.如果应用进程写一个大于套接字缓冲区 ...

  9. UNIX网络编程学习指南--epoll函数

    epoll是select/poll的强化版,都是多路复用的函数,epoll有了很大的改进. epoll的功能 1.支持监听大数目的socket描述符 一个进程内,select能打开的fd是有限制的,有 ...

随机推荐

  1. POJ 3261 可重叠k次最长重复子串

    Milk Patterns Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 13127   Accepted: 5842 Ca ...

  2. Scrapy框架

    Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中.其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以 ...

  3. jquery 跨域请求数据问题

    昨天参加了一个前端的面试,被问到一个跨域请求数据问题,我们之前一直用的是apicloud的api进行请求的,跨域是被apicloud封装起来的,也就没有注意跨域请求数据的问题.当被问到用jquery跨 ...

  4. c# datatable增加列并赋值

    DataView dv = DataObj.GetBmfzr("03").Tables[0].DefaultView; dv.Sort = "bmbh"; Da ...

  5. git报错:'fatal:remote origin already exists'怎么处理?附上git常用操作以及说明。

    git添加远程库的时候有可能出现如下的错误, 怎么解决? 只要两步: 1.先删除 $ git remote rm origin 2.再次执行添加就可以了. ---------------------- ...

  6. 使用FFMPEG在windows平台下推rtmp流

    使用FFMPEG在windows平台下推rtmp流 工作中习惯在Linux下面使用FFmpeg模拟推rtmp流,无奈家中的电脑都是windows系统,需要利用家中的带宽来测试流媒体服务器的性能.所以研 ...

  7. JSON 封装函数

    var eventUtil = { addHandler:function(element,type,handler) { //添加句柄 if(element.addEventListener) { ...

  8. 给定 n×n 的实数矩阵,每行和每列都是递增的,求这 n^2 个数的中位数。

    #define COL 4 #define ROW 4 int findMedian(int matrix[][COL], int row, int col) { int* arr = new int ...

  9. 安卓热修复之AndFIX

    我致力于最新的前沿安卓技术分析和使用教学,不打算将很多很深的东西,因为有多少人愿意沉下你的心境去学习难点?我一般只会简单提及.文字错漏在所难免还希望同学们喜欢 热修复介绍 热修复是什么? 如果你一个项 ...

  10. Dynamics CRM2016 Web Api之根据时间查询数据

    我的博文里已经有多次提到CRM中的时间处理问题了,本篇继续探讨在web api的场景下时间字段如何处理,本篇只涉及查询,针对2016中新增的时间行为"用户当地时间"和"无 ...