第1章 Linux网络编程概念
1.1 网络体系结构
- 网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。
- 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务
- 网络体系结构即指网络的层次结构和每层所使用协议的集合
- 两类非常重要的体系结构: OSI与TCP/IP
1.1.1 OSI开放系统互联模型
-
OSI模型相关的协议已经很少使用,但模型本身非常通用
-
OSI模型是一个理想化的模型,尚未有完整的实现
-
OSI模型共有七层
1.1.2 TCP/IP协议族
1.1.2.1 体系结构
1、体系结构
2、TCP/IP与OSI参考模型的对应关系
1.1.2.2 TCP/IP协议
- 传输控制/网际协议(Transfer Control Protocol/Internet Protocol) 又称作网络通讯协议;
- Internet国际互联网络的基础,RFC79一组协议,通常称它为TCP/IP协议族。
- 使用举例
(1)qq
登录:HTTP、TCP
消息:UDP
(2)微信:HTTP短连接和TCP长连接
登录:HTTP
消息:TCP
1.1.2.2.1 TCP协议
1、概念
TCP(即传输控制协议)是一种面向连接的传输层协议,它能提供高可靠性通信(即
数据无误、数据无丢失、数据无失序、数据无重复到达的通信)
2、适用情况:可靠性
(1)适合于对传输质量要求较高,以及传输大量数据的通信。
(2)在需要可靠数据传输的场合,通常使用TCP协议
(3)MSN/QQ等即时通讯软件的用户登录账户管理相关的功能
通常采用TCP协议
1.1.2.2.2 UDP协议
1、概念
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
2、适用情况:实时性
(1)发送小尺寸数据(如对DNS服务器进行IP地址查询时)
(2)在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
(3)适合于广播/组播式通信中。
(4)MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
(5)流媒体、VOD、VoIP、IPTV
1.1.3 TCP/IP网络编程基础知识
1.1.3.1 Socket简介
进行网络通信的两个进程在不同的机器上,如何连接?网络协议具有多样性,如何进行统一的操作?
1、相关概念
socket通信其实属于管道通信,当没有数据是读取文件会阻塞当前线程,直到有数据时被读出了。 socket相当于插座的插孔,通信的接口。
-
socket是一种特殊的I/O接口,它也是一种文件描述符,通信的端点。
-
socket是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。
socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的。IP + PORT唯一标识一个进程 Inet_pton: 将IP地址的字符串赋值给 初始化socket的结构体 netstat -a 查看所有的连接。 netstat -a | grep “5002” -A 10 -B 10 查看某条socket连接是否存在
2、socket类型
(1)流式socket(SOCK_STREAM)
流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
(2)数据报socket(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
(3)原始socket(SOCK_RAW)
原始套接字允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。
3、协议层次
1.1.3.2 IP地址
-
IP地址是Internet中主机的标识
-
Internet中的主机要与别的机器通信必须具有一个IP地址
-
IP地址为32位( IPv4) 或者128位( IPv6)
-
每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
-
表示形式: 常用点分形式, 如202.38.64.10, 最后都会转换为一个32位的无符号整数。
IP地址分类
子网掩码
1.1.3.3 端口号
- 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别
TCP端口号与UDP端口号独立 - 端口号一般由IANA (Internet Assigned Numbers Authority)管理:
众所周知端口:1 ~ 1023(1 ~ 255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
注册端口: 1024~49150
动态或私有端口: 49151~65535
1.1.3.4 字节序
不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO):
1、字节序类型
(1)小端序(little-endian)
低序字节存储在低地址将低字节存储在起始地址,称为“ LittleEndian”字节序,Intel、AMD等采用的是这种方式;
(2)大端序(big-endian)
高序字节存储在低地址,将高字节存储在起始地址,称为“Big-Endian”字节序, 由ARM、 Motorola等所采用。
2、网络字节序(NBO - Network Byte Order)
(1)操作
网络中传输的数据必须按网络字节序,即大端字节序
在大部分PC机上,当应用进程将整数送入socket前,需要转化成网络字节序; 当应用进程从socket取出整数后,要转化成小端字节序(原因?)
(2)用途特点
- 使用统一的字节顺序,避免兼容性问题
- 主机字节序(HBO - Host Byte Order)
- 不同的机器HBO是不一样的,这与CPU的设计有关
- Motorola 68K系列、ARM系列,HBO与NBO是一致的
- Intel X86系列,HBO与NBO不一致
1.1.3.5 套接字和端口
第2章 socket编程
2.1 网络编程相关API
socket: 创建套接字
bind: 绑定本机地址和端口
connect: 建立TCP连接
listen: 设置监听端口
accept: 接受TCP连接
recv, read, recvfrom: 接收数据
send, write, sendto: 发送数据
colse, shutdown: 关闭套接字
2.1.1 通用知识
2.1.1.1 地址相关的数据结构
1、通用地址结构
struct sockaddr
{
u_short sa_family; // 地址族, AF_xxx
char sa_data[14]; // 14字节协议地址
};
2、Internet协议地址结构
struct sockaddr_in
{
u_short sin_family; // 地址族, AF_INET, 2 bytes
u_short sin_port; // 端口, 2 bytes
struct in_addr sin_addr; // IPV4地址, 4 bytes
char sin_zero[8]; // 8 bytes unused, 作为填充
};
3、IPv4地址结构
// internet address
struct in_addr
{
in_addr_t s_addr; // u32 network address
};
2.1.1.2 IP地址转换函数
1:int inet_aton(const char *strptr, struct in_addr *addrptr)
将strptr所指的字符串转换成32位的网络字节序二进制值
#include <arpa/inet.h>
2: unsigned long inet_addr(char *address); IP转地址
address是以’\0’结尾的点分IPv4字符串。该函数返回32位的地址。如果字符串包含的不是合法的IP地址,则函数返回-1。 例如:
struct in_addr addr;
addr.s_addr = inet_addr(" 192.168.1.100 ");
3: char* inet_ntoa(struct in_addr address); 地址转IP
address是IPv4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL。
#include <arpa/inet.h>
int inet_ntop(int family, void *addrptr, char *strptr, size_t len);
参数:family-->AF_INET IPv4协议
AF_INET6 IPv6协议
addrptr: 要转换的值
strptr: 存储转换后的字符
4:int inet_pton(int af, const char *src, void *dst)
将IPV4/IPV6的地址转换成binary格式。
#include <arpa/inet.h>
Int inet_pton(int family, const char *strptr, void *addrptr);
参数:family-->AF_INET IPv4协议
AF_INET6 IPv6协议
strptr: 要转换的字符串
addrptr: 存储转换后的值
返回值:成功0 失败-1
2.1.1.3 字节序转换函数
把给定系统所采用的字节序称为主机字节序。为了避免不同类别主机之间在数据交换时由于对于字节序的不同而导致的差错,引入了网络字节序。
-
主机字节序到网络字节序
u_long htonl (u_long hostlong); u_short htons (u_short short);
-
网络字节序到主机字节序
u_long ntohl (u_long hostlong); u_short ntohs (u_short short);
2.1.2 TCP编程
2.1.2.1 服务器(s)
1:socket创建套接字
int socket (int family, int type, int protocol);
参数:family --》协议族(即地址类型)
AF_INET Internet协议
AF_INET6 IPv6网际协议
AF_UNIX/AF_LOCAL unix internal协议
AF_ROUTE 路由协议
AF_KEY 密钥协议
type----》套接字类型
SOCK_STREAM 流式套接字
SOCK_DGRAM 数据报套接字
SOCK_RAM 原始套接字
protocol----》参数通常设置为0
返回值:套接字 ID, 出错 -1
2:bind绑定套接字的本机IP和端口
Int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
参数:sockfd: socket调用返回的文件描述符
my_addr: 存有本机socket地址信息的通用结构体
addrlen: sockaddr地址结构的长度
返回值:0或-1
在建立了socket连接之后,可对sockaddr或sockaddr_in结构进行初始化,以保存所建立的socket地址信息。
3:listen 监听套接字(端点)
服务端程序成功建立套接字和与地址进行绑定之后,创建一个等待队列,在其中存放未处理的客户端连接请求。
int listen(int sockfd, int backlog);
参数:Sockfd---》套接字描述符
Blacklog---》请求队列中允许的最大请求数,大多数系统缺省值为5
返回值:成功0 失败-1
4:accept 等待响应TCP连接请求
int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen );
参数:socket---》被监听的套接字描述符
Addr-----》存储请求连接的客户端地址的结构体
Addlen----》地址长度
返回值:已经建立好连接的套接字描述符, 出错 -1
5:recv /read接收对端信息
int recv (int sockfd, void *buf, int len, unsigned int flags);
参数 :sockfd----》已经建立连接的套接字描述
buf-------》存放接收数据的缓冲区
Len------》数据长度
Flags----》一般为0
返回值:接收的字节数, 失败 -1
6:send/write发送信息到对端
int send (int socket, const void *msg, int len, int flags);
参数 : sockfd----》已经建立连接的套接字描述
buf-------》指向发送数据的指针
Len------》数据长度
Flags----》一般为0
返回值:发送的字节数, 失败 -1
7:关闭已经建立连接的socket描述符(端点)
int close(int fd);
int shutdown(int sockfd, int howto);
参数:howto-------》howto = 0 关闭读通道,但是可以继续往套接字写数据。
howto = 1 关闭写通道,只能从套接字读取数据。
howto = 2 关闭读写通道,和close()一样
返回值:
2.1.2.2 客户端(c)
1:socket 创建套接字
2:bind 绑定套接字的本机IP和端口 <可选>
3:地址结构体初始化为服务器IP和端口
4:connect 发起连接对端socket端点
int connect(int socket, struct socketaddr *serv_addr, int addrlen);
参数:socket---》套接字描述符
serv_addr ---》服务器地址结构体
addrlen-------》地址长度
返回值:成功0 失败-1
该函数在TCP中是用于bind()服务器地址之后的client端,用于与服务器端建立连接,成功连接后,本端点变为已经连接的端点fd。
4:recv /read接收对端信息
5:send/write发送信息到对端
2.1.2.3 TCP通信过程
关闭连接时的四次握手中,TIME_WAIT:等待2MSL,原因:
- 等待服务器成功收到最后的ACK,如果服务器接收失败还有时间重新给客户端发送FIN信号
- 避免客户端的资源给了新创建的socket(相当于刚退出的socket),收到服务器上一次发来FIN信号
2.1.3 UDP编程
2.1.3.1 服务器(s)
1:socket创建套接字
2:bind 绑定套接字的本机IP和端口
3:recvfrom 接收对端信息
ssize_t recvftom(int socket, void *buffer, size_t length, int flags,
struct socketaddr *address, socklen_t address_len);
参数:buffer----》指向存储接收信息的缓存区的指针
Length---》接收缓冲区的最大长度
Flag------》一般为0
address----》存储发送信息来的对端地址(IP+PORT)
类型为:struct socketaddr_in
address_len---》地址长度
4: sendto 发送信息给对端
ssize_t sendto (int socket, void *messag, size_t length, int flags,
struct socketaddr *dest_addr, socklen_t dest_len);
参数:message---》指向存储发送信息的缓存区的指针
Length---》接收缓冲区的最大长度
Flag------》一般为0
address----》对端地址(IP+PORT)
类型为:struct socketaddr_in
2.1.3.2 客户端(c)
1:socket创建套接字
2:bind 绑定套接字的本机IP和端口 <可选>
3:recvfrom 接收对端信息
4: sendto 发送信息给对端
2.2 Linux下IO模型和服务器模型
2.2.1 IO模型
在UNIX/Linux下主要有4种I/O 模型:
▶ 阻塞I/O:
最常用、最简单、效率最低
▶ 非阻塞I/O:
可防止进程阻塞在I/O操作上,需要轮询,事倍功半,浪费CPU
▶ I/O 多路复用:
允许同时对多个I/O进行控制,同时监听多个通信端点
▶ 信号驱动I/O:
一种异步通信模型
2.2.1.1 阻塞I/O模式
(1)阻塞模式
系统默认方式。
(2)阻塞类型
A: 读阻塞:read、recv、recvfrom
当接收缓冲区中还没有数据,读取或接收操作会发生阻塞(以上几个函数),线程会一直阻塞下去,等待套接字的接收缓冲区中有数据可读后,解除阻塞并读取数据。
B: 写阻塞:write、send
在写操作时发生阻塞的情况要比读操作少;
写入的缓冲区的大小小于要写入的数据量的情况下会发生阻塞,写操作不进行任何拷贝工作。
特例:UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞
C: socket连接阻塞:
TCP等待对端连接:connect
握手等待对端应答,等待连接成功。
TCP发起连接对端:accept
等待客户端连接-
2.2.1.2 非阻塞I/O模式
1、原理描述
读写/连接等操作时不阻塞线程,即若没有数据/没有则立即返回、不阻塞,需要重新不断地检测那个IO操作发生,并执行相应的操作
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。
应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
2、非阻塞I/O实现
使用函数fcntl()设置一个套接字的标志为O_NONBLOCK 来实现非阻塞I/O。
(1)fcntl( )函数
int fcntl(int fd, int cmd, long arg);
int flag;
flag = fcntl(sockfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(sockfd, F_SETFL, flag);
(2)ioctl() 函数
int b_on =1;
ioctl(sock_fd, FIONBIO, &b_on);
2.2.1.3 多路复用I/O模式
1、原理描述
先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
2、select()/poll()实现多路复用
(1)select用法
A: 函数原型
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfd, fd_set *read_fds, fd_set *write_fds,
fd_set *except_fds, struct timeval, *timeout);
参数:maxfd --》 所有监控的文件描述符中最大的那一个加1
read_fds--》 所有要读的文件文件描述符的集合
write_fds--》所有要的写文件文件描述符的集合
except_fds--》其他要向我们通知的文件描述符----》(带外)紧急/异常等
timeout----》 超时设置.
Null: 一直阻塞,直到有文件描述符就绪或出错
时间值为0: 仅仅检测文件描述符集的状态,然后立即返回
时间值不为0: 在指定时间内,如果没有事件发生,则超时返回。
B: 用法详解
当描述符集没有I/O就绪时会监听等待(会阻塞),当有IO操作发生时则只保留发生的IO操作的文件描述符,没有发生的会被自动擦除掉。
1:在我们调用select时进程会一直阻塞直到以下的一种情况发生:
有文件可以读.
有文件可以写.
超时所设置的时间到.
2:为了设置文件描述符我们要使用几个宏:
void FD_SET(int fd, fd_set *set); //将fd加入到fdset集
void FD_CLR(int fd, fd_set *set); //将fd从fdset里面清除
int FD_ISSET(int fd, fd_set *set); //判断fd是否在fdset文件描述集中
void FD_ZERO(fd_set *set); //全部清零fdset集
2.2.1.4 信号驱动I/O模式
2.3 网络信息检索
2.3.1 网络信息检索函数
A: 获取get
gethostname() 获得主机名(本机)
getpeername() 获得与套接口相连的远程协议地址(socket发送方IP地址)
getsockname() 获得本地套接口协议地址
gethostbyname() 根据主机名取得主机信息
endhostent()
gethostbyaddr() 根据主机地址取得主机信息
getprotobyname() 根据协议名取得主机协议信息
getprotobynumber() 根据协议号取得主机协议信息
getservbyname() 根据服务名取得相关服务信息
getservbyport() 根据端口号取得相关服务信
B: 设置set
gethostname() 设置主机名(本机)
…
(2)使用详解
2.3.2 网络属性设置与获取
(1)获取网络相关属性:getsocketopt
(2)设置网络相关属性:setsocketop
(3)使用详解
int getsockopt(int socketfd, int level, int optname, void *opval, socklen_t *optlen);
Int setsockopt(int socket, int level, int optname, const void *optval, socklen_t *optlen);
参数:level指定控制套接字的层次--》SOL_SOCKET 通用嵌套字选项
IPPROTO_IP 使用IP协议选项
IPPROTO_TCP 使用TCP协议选项
optval------》存储获得或者是设置套接字选项的值.根据选项名称的数据类型进行转换
optname---》指定控制的方式(选项的名称),我们下面详细解释
=IPPROTO_IP之组播设置===============
1.选项IP_MULTICASE_TTL
选项IP_MULTICAST_TTL允许设置超时TTL,范围为0~255之间的任何值,例如:
unsigned char ttl=255;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));
2.选项IP_MULTICAST_IF
选项IP_MULTICAST_IF用于设置组播的默认默认网络接口,会从给定的网络接口发送,另一个网络接口会忽略此数据。例如:
struct in_addr addr;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr));
参数addr是希望多播输出接口的IP地址,使用INADDR_ANY地址回送到默认接口。
默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口。例如:
unsigned char loop;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));参数loop设置为0禁止回送,设置为1允许回送。
3.选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP
加入或者退出一个多播组,通过选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBER- SHIP,对一个结构struct ip_mreq类型的变量进行控制,struct ip_mreq原型如下:
struct ip_mreq
{
struct in_addr imn_multiaddr; /*加入或者退出的广播组IP地址*/
struct in_addr imr_interface; /*加入或者退出的网络接口IP地址*/
};
选项IP_ADD_MEMBERSHIP用于加入某个多播组,之后就可以向这个多播组发送数据或者从多播组接收数据。此选项的值为mreq结构,成员imn_multiaddr是需要加入的多播组IP地址,成员imr_interface是本机需要加入广播组的网络接口IP地址。例如:
struct ip_mreq mreq;
setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
2.3.3 网络超时检查与设置
2.3.3.1 相关概念
在网络通信中,很多操作会使得进程阻塞,所有有必要进行超时检查,根据需要处理。
超时检测的必要性:避免进程在没有数据时无限制地阻塞; 当设定的时间到时,进程从原操作返回继续运行。
2.3.3.2 超时设置、处理实现
1、socket属性设置函数:setsocketopt(SO_RCVTIMEO)
当超过设定时间,没有数据接收或读取时,程序会自动解除读IO阻塞,并执行读IO操作后的代码。
struct timeval tv;
tv.tv_sec = 5; // 设置5秒时间
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv,sizeof(tv)); // 设置接收超时
recv() / recvfrom() // 从socket读取数据
1、select:设置超时检测处理
当socket文件描述符中没有任何发生时,会阻塞,设置了超时检查,则超过设定的那个时间后会自动解除阻塞,返回0,退出select函数,继续执行后面的代码。
struct fd_set rdfs;
FD_ZERO(&rdfs);
FD_SET(sockfd, &rdfs);
struct timeval tv = {5 , 0}; // 设置5秒时间
if (select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0) // socket就绪
{
recv() / recvfrom() // 从socket读取数据
}
2、alarm设置定时器(timer), 捕捉SIGALRM信号
alarm:设置一个闹钟,在设定时间后如果没有重设该函数,则会收到一个SIGLRM信号;可以signal来设置捕获该信号并自定义处理函数。
void handler(int signo) { return; }
signal(SIGALRM, handler);//设定进程捕获到SIGANAL信号后执行的函数
alarm(5); // 设置闹钟
recv()/recvfrom... //没有数据读IO操作会阻塞
2.4 广播 组播
2.4.1 通信类别
2.4.1.1 TCP
TCP是面向连接、提供可靠服务,所以只能进行单播。
数据包发送时只有一个接受方,一对一的收发方式称为单播
2.4.1.2 UDP
UDP是无连接,可以进行广播、组播、单播通信。
2.4.2 广播
同时发给局域网中的所有主机,称为广播。
1、广播地址
某个网段的最大主机地址代表该网段的广播地址。
.以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址;发到该地址的数据包被该网段内所有的主机接收
. 255.255.255.255在所有网段中都代表广播地址
2、广播发送(广播IP)流程
1: 创建用户数据报套接字
2: 缺省创建的套接字不允许广播数据包,需要设置属性: setsockopt可以设置套接字属性
3:接收方地址指定为广播地址
4:指定端口信息
5:发送数据包
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
头文件:#include<sys/socket.h>
参数: level : 选项级别(例如SOL_SOCKET)
optname : 选项名(例如SO_BROADCAST)
optval : 存放选项值的缓冲区的地址
optlen : 缓冲区长度
返回值:成功返回0 失败返回-1并设置errno
3、广播接收流程
- 创建用户数据报套接字
- 绑定本机IP地址和端口
绑定的端口必须和发送方指定的端口相同 - 等待接收数据
2.4.3 组播
含义:组播(又称为多播)是一种折中的方式,只有加入某个多播组的主机才能收到数据。
优点:多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理),可以是局域网也可以是广域网。
1、组播发送(指定组播IP(D类地址))
-
创建用户数据报套接字
-
接收方地址指定为组播地址(D类地址)
// 第一步:使能广播属性 int on = 1; Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,&on, sizeof on); // 第二步:往约定好的组播地址发送消息 bzero(&peeraddr, sizeof(peeraddr)); peeraddr.sin_family = PF_INET; peeraddr.sin_port = htons(atoi(argv[2])); peeraddr.sin_addr.s_addr = inet_addr(argv[1]);
-
指定端口信息
-
发送数据包
2、组播接受(加入组播IP)
-
创建用户数据报套接字
-
加入多播组
struct ip_mreq mreq; bzero(&mreq, sizeof mreq); // 以下IP地址指明组播ID mreq.imr_multiaddr.s_addr = inet_addr(argv[1]); // 以下IP地址指明接收组播消息的网络接口 // INADDR_ANY表示任意网络接口,因为目前电脑只有一个接口 mreq.imr_interface.s_addr = htonl(INADDR_ANY); Setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof mreq);
-
绑定本机IP地址和端口: 绑定的端口必须和发送方指定的端口相同
-
等待接收数据
3、组播地址结构体封装
struct ip_mreq
{
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(“224.10.10.1”);//
mreq.imr_interface.s_addr = htonl(INADDR_ANY); //
inet_pton(AF_INET,”192.168.7.89”, (void *)& mreq.imr_interface);//
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));