IPv6网络编程
文章目录
垃圾在玉泉三本的有线好歹是有IPv6的,做计网作业的时候顺便研究了一下IPv6的网络编程。
基本上是我按照自己的经验写出来的,如果有不对的地方还请指正。
生成socket、连接server、绑定地址
socket和AF_INET6
调用socket()
的时候,把第一个参数设成AF_INET6
即可,例如生成一个TCP的socket:socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)
。有趣的是Windows和Linux上这个宏的实际值并不一样。
sockaddr_in6
和sockaddr_in
类似,用来存放IPv6的地址信息,除sin6_family
、sin6_port
、sin6_addr
之外,还有两个额外的成员sin6_flowinfo
、sin6_scope_id
,所以在使用时要注意先用memset
把全部成员清零,否则有可能造成连接失败。
sockaddr_storage
这个结构体可以容纳下所有类型的sockaddr,对于IPv4和IPv6混合编程,可以使用这个结构来统一存放地址。但是这个结构体使用时,除了一个ss_family
能直接访问,其它的总要类型转换,有点麻烦,我一般用一个sockaddr_in
、sockaddr_in6
、sockaddr_storage
组成的union来保存地址,这样调试的时候也方便查看。
connect
connect()
并没有什么特别的地方,注意addrlen
参数不要填得太短了导致失败,send和recv和IPv4的一样。
IN6ADDR_ANY_INIT、IN6ADDR_LOOPBACK_INIT
类似IPv4的INADDR_ANY
、INADDR_LOOPBACK
,可以直接赋值给sin6_addr
,分别用来bind到[::]
和[::1]
。
地址的转换
可以用inet_pton()
把字符串格式的地址转换为sin6_addr
的二进制表示。inet_ntop()
作用与其相反。
连接到链路本地地址
前面提到的手工填入各项参数的方法比较适合IPv6单播地址,但对于没有接入到IPv6互联网的情况,只有链路本地地址可以使用(当然链路本地地址只能在同一个“链路”内使用,比如同一个交换机内),链路本地地址的前缀为fe80::/10
,后面跟着64位的后缀。
原理
直接把链路本地地址填进去是不能连接的,因为链路本地地址并没有像单播地址那样的前缀机制,没法区分出哪些地址处于一个子网中,路由表中也没有链路本地地址的路由信息,如果电脑有多块网卡,就无法判断该由哪块网卡发送数据。
ipv6(7)中提到了sin6_scope_id
的用法:
sin6_scope_id is an ID depending on the scope of the address. It is new in Linux 2.4. Linux supports it only for link-local addresses, in that case sin6_scope_id contains the interface index (see netdevice(7))
也就是说,使用链路本地地址时,需要在sin6_scope_id
里填上网卡的序号,运行ip a
(Linux)或者ipconfig /all
(Windows)可以查看当前所有的地址:
lo、ens33前面的1、2就是index。在地址后面加上%x可以表示scope id,例如直接ping6 fe80::20c:29ff:fe09:31c6
会提示参数无效,改成ping6 fe80::20c:29ff:fe09:31c6%2
就能使用,ping6 fe80::20c:29ff:fe09:31c6%ens33
也能运行。
getaddrinfo
可以直接把index填入sin6_scope_id
中,但是对于后面给出网卡名称的形式,就需要调用getaddrinfo()
,这个函数经常用于解析IPv4地址,但也能用来填写IPv6地址,例如:
|
|
单播地址或者带有scope id的链路本地地址都可以用getaddrinfo()
来获取sockaddr。
getnameinfo
这个函数作用和getaddrinfo()
相反,如果已知某个sockaddr_in6
结构体,可以用这个函数来转换成字符串,如果是链路本地地址,后面也会有scope id。
同时监听IPv6和IPv4
想写一个服务器的话肯定需要同时支持两种网络层协议,然而accept()
是阻塞的,在IPv4上accept的时候IPv6就没法连接,所以必须使用多线程同时监听,或者采用I/O复用机制。
select()
应该算是最原始的I/O复用机制了,把IPv6和IPv4的socket都加入到同一个fd_set
里面,传给select()
的readfds,当有连接进入的时候,用于accept的socket会变成可读,在可读的socket上调用accept()
就可以避免被阻塞了。
最后
IPv6是大势所趋,三本的无线网啥时候也加上IPv6啊…