1. udp
1.1 udp通信流程
- 服务器端 - 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();
1.2 操作函数
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> //也可以只用这一个头文件,包含了上面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的内存大小
recv、recvfrom
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> //也可以只用这一个头文件,包含了上面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);
2. 广播
向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息。
- 只能在局域网中使用
- 客户端只要绑定了服务器广播使用的端口, 就可以接收到广播数据
2.1 广播通信流程
服务器端 -> 广播的一端:
- 创建通信的套接字 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();
2.2 设置广播属性函数:setsockopt
这个函数有许多功能,这里只讨论设置广播属性函数功能
#include <arpa/inet.h> 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内存大小
2.3 广播代码
服务器:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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; }
3 组播
广播:无论连接到局域网的客户端想不想接收该数据,Server都会给客户端发送该数据。
进而造成客户端上数据的拥塞,因此引出了组播:Server可以将数据包只发送给指定组内的客户端,而不发送给指定组外的客户端。
特点:
- 可以在internet中进行组播
- 加入到广播的组织中才可以收到数据
3.1 组播地址
IP 组播通信必须依赖于 IP 组播地址,在 IPv4 中它的范围从 `224.0.0.0` 到 `239.255.255.255`,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:
3.2 组播通信流程
服务器端 -> 播的一端:
- 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();
3.3 设置组播属性函数:setsockopt
这个函数有许多功能,这里只讨论设置组播属性函数功能
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; // 网卡编号 };
3.4 组播代码
服务器:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <net/if.h> 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; }
4. 本地套接字
本地套接字用于进程间通信,有没有血缘关系都可以。通信流程 -> 一般按照tcp流程处理
4.1 结构体sockaddr_un
结构体 sockaddr、sockaddr_in用于网络通信
结构体 sockaddr_un用于进程间通信
结构体 sockaddr_in用于ipv6通信
由于结构体sockaddr需要用指针偏移添加IP地址,这样很麻烦,在网络通信中我们使用sockaddr_in来添加端口号、IP地址。再强转成sockaddr类型,因为这2个结构体大小一样,后面的服务器—客户端程序会有具体体现。
在进程间通信中,使用sockaddr_un
#include <sys/un.h> #define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; // 地址族协议 af_local char sun_path[UNIX_PATH_MAX]; // 套接字文件的路径, 这是一个伪文件, 大小永远=0 };
4.2 本地套接字—进程间通信流程
进程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();
4.3 本地套接字—进程间通代码
进程1:服务器
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <sys/un.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <sys/un.h> 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; }
收藏的用户(0)
X
正在加载信息~
2
最新回复 (0)