说明
- udp是一个无连接的通讯协议,普通通信模型如下:
服务器 ------------------------------------------- 客户端
- 在该模型中,只有服务器需要使用到bind函数,不需要使用connect函数。
bind和connect这两个函数在udp编程中该如何使用?
- 实际上,以上基本模式是通用模型,在一些情况下性能不是最好,在特殊情况下可以采用bind,connect函数来提高程序性能。
bind函数使用
- bind函数作用为套接字绑定本地地址和端口号。
- 服务器调用bind函数的作用很明显,但是客户端呢?客户端如果没有使用bind函数绑定自身的地址/端口,发送数据时,操作系统隐含的处理是:为该套接字随机分配一个合适的udp端口,确定使用的本地地址(以太网还是无线网以及其它网络),将该套接字和本地地址信息绑定,手动绑定可以避免这些隐含操作,从而提高性能。
connect函数使用
- connect函数作用:指明套接字的目的地址/端口。
- UDP的connect函数不像TCP,不会触发三次握手,只是在内核中记录对端的参数。
客户端使用connect函数
- 基本模式下客户端每次发送数据都需要给sendto函数传递参数目标地址/端口,可以使用connect函数来减小这部分开销。
- 基本模式下客户端每次发送数据,内核都有可能要做路由查询,connect可以减小这部分开销。
- 客户端调用connect函数先指明目的地址/端口,然后就可以使用send函数发送数据了,因为此时套接字已经记录了目的地址/端口。
- 客户端调用connect函数后,就可以直接使用recv来获取服务器返回,但是这样只能一对一通信了。
int main(int argc, char *argv[])
{
int sd;
struct sockaddr_in svr_addr;
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
char buf[BUFSZ] = {};
if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket");
exit(EXIT_FAILURE);
}
//先调用connect()函数,为套接字指定目的地址/端口
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(PORT);
svr_addr.sin_addr.s_addr = inet_addr("192.168.1.166");
connect(sd, (struct sockaddr* )&svr_addr, addrlen);
while (1)
{
memset(buf, 0, BUFSZ);
printf("ple input: ");
fgets(buf, BUFSZ, stdin);
//sendto(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, addrlen);
send(sd, buf, BUFSZ, 0);
ret = recvfrom(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, &addrlen);
printf("client: IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(svr_addr.sin_addr), ntohs(svr_addr.sin_port), buf);
}
close(sd);
return 0;
}
服务器使用connect函数
- 服务器可以使用connect,将导致服务器只接受这特定一个主机的请求。
- 例子:java udp网络编程中,创建udp socket时可以传入对端ip地址,这样就能只接收指定ip的数据包,实现了过滤。
注意
- UDP可以多次调用connect函数,当需要给同一个目的地址发送多次数据时,显式调用connect效率更高,未调用connect的UDP套接字发送数据前的环境准备大约会消耗每个UDP传输三分之一的开销。
- 清除之前的记录,包括路由环境等
- 记录新的IP和端口,准备好新的路由环境
- UDP显式调用connect函数也可以避免一些错误,例如:高并发服务中,假设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.
- 根据connect的原理可知
- 设置目标节点的参数是单方向的,客户端使用了connect,客户端即可使用send,直接向服务器发送数据,如果服务器没有使用connect,服务器不能使用send向客户端发送数据。
- connect后也可以使用sendto等函数,只是会清除已设置的参数,发送完后需要重新connect(需要确认)。