目录
1. udp
1.1 udp通信流程
1.2 操作函数
send、sendto
recv、recvfrom
2. 广播
2.1 广播通信流程
2.2 设置广播属性函数:setsockopt
2.3 广播代码
3 组播
3.1 组播地址
3.2 组播通信流程
3.3 设置组播属性函数:setsockopt
3.4 组播代码
4. 本地套接字
4.1 结构体sockaddr_un
4.2 本地套接字—进程间通信流程
4.3 本地套接字—进程间通代码
- 1.创建通信的套接字
- int fd = socket( af_inet, SOCK_DGRAM, 0)
- 2. 绑定 -> 通信的fd 和本地 IP / port 绑定
- struct sockaddr_in addr;
- 3.通信
- 接收数据: recvfrom
- 发送数据: sendto
- 4.关闭通信的fd
- close(fd);
- 1.创建一个通信的套接字
- 2.通信
- 接收数据: recvfrom
- 发送数据: sendto
- 3.关闭通信的文件描述符
- close();
#include
#include
#include //也可以只用这一个头文件,包含了上面2个头文件
// tcp 发送数据的函数 write
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// udp 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
- sockfd: 通信的fd
- buf: 要发送的数据
- len: 要发送的数据的长度
- flags: 0
- dest_addr: 通信的另外一端的地址信息
- addrlen: dest_addr的内存大小
#include
#include
#include //也可以只用这一个头文件,包含了上面2个头文件
// tcp 接收数据 read
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// udp 接收数据函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
- sockfd: 通信的fd
- buf: 接收数据的一块内存
- len: 接收数据的内存(第二个参数)大小
- flags: 0
- src_addr: 接收谁的数据, 就写入了那个终端的地址信息, 如果不要这部分数据 -> NULL
- addrlen: src_addr参数对应内存大小(传入传出参数)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息。
- 只能在局域网中使用
- 客户端只要绑定了服务器广播使用的端口, 就可以接收到广播数据
服务器端 -> 广播的一端:
- - 创建通信的套接字
int fd = socket( af_inet, SOCK_DGRAM, 0);
- - 设置udp广播属性
setsockopt();
- - 通信 -> 发送广播数据
struct sockaddr_in cliaddr;
cliaddr.sin_port(8888); // 广播数据发送到客户端的8888端口, 客户端需要绑定该端口
cliaddr.sin_addr.s_addr -> 初始化一个广播地址
发送数据: sendto(fd, buf, len, 0, &cliaddr, len);
- - 关闭通信的fd
close(fd);
客户端
- - 创建一个通信的套接字
int fd = socket( af_inet, SOCK_DGRAM, 0);
- - 如果想接收广播数据, 需要绑定以固定端口(服务器广播数据使用的端口)
struct sockaddr_in cliaddr;
cliaddr.sin_port(8888);
bind(fd, cliaddr, len);
- - 通信
接收数据: recvfrom
- - 关闭通信的文件描述符
close();
这个函数有许多功能,这里只讨论设置广播属性函数功能
#include
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
参数:
- sockfd: 文件描述符
- level: SOL_SOCKET
- optname: SO_BROADCAST
- optval: int数值为1, 允许广播
- optlen: optval内存大小
服务器:
#include
#include
#include
#include
#include
int main()
{
// 1. 创建通信的套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 设置广播属性
int op = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &op, sizeof(op));
// 将数据发送给客户端, 使用广播地址和固定端口
// 初始化客户端的地址信息
struct sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(8989); // 客户端也需要绑定这端口
inet_pton(AF_INET, "192.168.247.255", &cliaddr.sin_addr.s_addr);
int num = 0;
// 3. 通信
while(1)
{
// 接收数据
char buf[128];
// 发送数据
sprintf(buf, "hello, client...%d\n ", num++);
sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
printf("广播的数据: %s\n", buf);
sleep(1);
}
close(fd);
return 0;
}
客户端:
#include
#include
#include
#include
#include
int main()
{
// 1. 创建通信的套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 2. 通信的fd绑定本地的IP和端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8989);
addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
//inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr.s_addr);
// 3. 通信
while(1)
{
// 接收数据
char buf[128];
recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
printf("server say: %s\n", buf);
}
close(fd);
return 0;
}
广播:无论连接到局域网的客户端想不想接收该数据,Server都会给客户端发送该数据。
进而造成客户端上数据的拥塞,因此引出了组播:Server可以将数据包只发送给指定组内的客户端,而不发送给指定组外的客户端。
特点:
- 可以在internet中进行组播
- 加入到广播的组织中才可以收到数据
IP 组播通信必须依赖于 IP 组播地址,在 IPv4 中它的范围从 `224.0.0.0` 到 `239.255.255.255`,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:
服务器端 -> 播的一端:
- - 1.创建通信的套接字
int fd = socket( af_inet, SOCK_DGRAM, 0);
- 设置udp组播属性
setsockopt();
- - 2.通信 -> 发送组播数据
struct sockaddr_in cliaddr;
cliaddr.sin_port(8888); // 广播数据发送到客户端的8888端口, 客户端需要绑定该端口
cliaddr.sin_addr.s_addr -> 初始化一个组播地址
发送数据: sendto(fd, buf, len, 0, &cliaddr, len);
- - 3.关闭通信的fd
close(fd);
客户端
- - 1.创建一个通信的套接字
int fd = socket( af_inet, SOCK_DGRAM, 0);
- - 2.如果想接收组播数据, 需要绑定以固定端口(服务器组播数据使用的端口)
struct sockaddr_in cliaddr;
cliaddr.sin_port(8888);
bind(fd, cliaddr, len);
- - 3.客户端加入到组播网络中
setsockopt():
- - 4.通信
接收数据: recvfrom
- - 5.关闭通信的文件描述符
close();
这个函数有许多功能,这里只讨论设置组播属性函数功能
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
// 服务器端 -> 进程组播
参数:
- sockfd: 通信的文件描述符
- level: IPPROTO_IP
- optname: IP_MULTICAST_IF
- optval: struct in_addr
- optlen: optval 的内存大小
// 客户端 -> 加入到多播组
参数:
- sockfd: 通信的文件描述符
- level: IPPROTO_IP
- optname: IP_ADD_MEMBERSHIP
- optval: struct ip_mreqn
- optlen: optval 的内存大小
struct ip_mreqn
{
// 组播组的IP地址.
struct in_addr imr_multiaddr;
// 本地某一网络设备接口的IP地址。
struct in_addr imr_address;
int imr_ifindex; // 网卡编号
};
服务器:
#include
#include
#include
#include
#include
int main()
{
// 1. 创建通信的套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 设置组播属性
struct in_addr imr_multiaddr;
// 初始化组播地址
inet_pton(AF_INET, "239.0.0.10", &imr_multiaddr.s_addr);
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr_multiaddr, sizeof(imr_multiaddr));
// 将数据发送给客户端, 使用广播地址和固定端口
// 初始化客户端的地址信息
struct sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(8989); // 客户端也需要绑定这端口
inet_pton(AF_INET, "239.0.0.10", &cliaddr.sin_addr.s_addr);
int num = 0;
// 3. 通信
while(1)
{
// 接收数据
char buf[128];
// 发送数据
sprintf(buf, "hello, client...%d\n ", num++);
sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
printf("组播的数据: %s\n", buf);
sleep(1);
}
close(fd);
return 0;
}
客户端:
#include
#include
#include
#include
#include
#include
int main()
{
// 1. 创建通信的套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 2. 通信的fd绑定本地的IP和端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8989);
addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
//inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr.s_addr);
// 加入到组播
struct ip_mreqn op;
op.imr_address.s_addr = INADDR_ANY; // 本地地址
inet_pton(AF_INET, "239.0.0.10", &op.imr_multiaddr.s_addr);
op.imr_ifindex = if_nametoindex("ens33");
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &op, sizeof(op));
// 3. 通信
while(1)
{
// 接收数据
char buf[128];
recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
printf("server say: %s\n", buf);
}
close(fd);
return 0;
}
本地套接字用于进程间通信,有没有血缘关系都可以。通信流程 -> 一般按照tcp流程处理
结构体 sockaddr、sockaddr_in用于网络通信
结构体 sockaddr_un用于进程间通信
结构体 sockaddr_in用于ipv6通信
由于结构体sockaddr需要用指针偏移添加IP地址,这样很麻烦,在网络通信中我们使用sockaddr_in来添加端口号、IP地址。再强转成sockaddr类型,因为这2个结构体大小一样,后面的服务器—客户端程序会有具体体现。
在进程间通信中,使用sockaddr_un
#include
#define UNIX_PATH_MAX 108
struct sockaddr_un
{
sa_family_t sun_family; // 地址族协议 af_local
char sun_path[UNIX_PATH_MAX]; // 套接字文件的路径, 这是一个伪文件, 大小永远=0
};
进程1:服务器端
- 1. 创建监听的套接字
int lfd = socket(af_local, sock_stream, 0);
第一个参数: AF_UNIX, AF_LOCAL
- 2. 监听的套接字绑定本地的 套接字文件-> server端
struct sockaddr_un addr;
// 绑定成功之后, 指定的sun_path中的套接字文件会自动生成
bind(lfd, addr, len);
- 3. 监听
listen(lfd, 100);
- 4. 等待并接受连接请求
struct sockaddr_un cliaddr;
int connfd = accept(lfd, cliaddr, len);
- 5. 通信
接收数据: read/recv
发送数据: write/send
- 6. 关闭连接
close();
进程2:客户端
- 1. 创建通信的套接字
int fd = socket(af_local, sock_stream, 0);
- 2. 监听的套接字绑定本地的IP 端口
struct sockaddr_un addr;
// 绑定成功之后, 指定的sun_path中的套接字文件会自动生成
bind(fd, addr, len);
- 3. 连接服务器
struct sockaddr_un serveraddr;
connect(fd, serveraddr, sizeof(serveraddr));
- 4. 通信
接收数据: read/recv
发送数据: write/send
- 5. 关闭连接
close();
进程1:服务器
#include
#include
#include
#include
#include
#include
int main()
{
unlink("server.sock");
// 1. 创建监听的套接字
int lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket");
exit(0);
}
// 2. 绑定本地套接字文件
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, "server.sock"); //套接字文件是伪文件,会自动生成,名字后缀随便取
int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
// 3. 监听
ret = listen(lfd, 100);
if(ret == -1)
{
perror("listen");
exit(0);
}
// 4. 等待并接受连接请求
struct sockaddr_un cliaddr;
int len = sizeof(cliaddr);
int connfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
if(connfd == -1)
{
perror("accept");
exit(0);
}
printf("client socket fileName: %s\n", cliaddr.sun_path);
// 5. 通信
while(1)
{
// 接收数据
char buf[128];
int nums = recv(connfd, buf, sizeof(buf), 0);
if(nums == -1)
{
perror("recv");
exit(0);
}
else if(nums == 0)
{
printf("client disconnect...\n");
break;
}
else
{
printf("client say: %s\n", buf);
send(connfd, buf, nums, 0);
}
}
close(connfd);
close(lfd);
return 0;
}
进程2:客户端
#include
#include
#include
#include
#include
#include
int main()
{
unlink("client.sock");
// 1. 创建通信的套接字
int cfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if(cfd == -1)
{
perror("socket");
exit(0);
}
// 2. 绑定本地套接字文件
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, "client.sock");
// 绑定成功, client.sock会自动生成
int ret = bind(cfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
// 3. 连接服务器
struct sockaddr_un seraddr;
seraddr.sun_family = AF_LOCAL;
strcpy(seraddr.sun_path, "server.sock");
ret = connect(cfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
if(ret == -1)
{
perror("connect");
exit(0);
}
int num = 0;
// 5. 通信
while(1)
{
// 发送数据
char buf[128];
sprintf(buf, "hello, everyone... %d\n", num++);
send(cfd, buf, strlen(buf)+1, 0);
printf("client say: %s\n", buf);
// 接收数据
int nums = recv(cfd, buf, sizeof(buf), 0);
if(nums == -1)
{
perror("recv");
exit(0);
}
else if(nums == 0)
{
printf("server disconnect...\n");
break;
}
sleep(1);
}
close(cfd);
return 0;
}