注:博主只关注编程实现的方面以及linux部分,部分网络原理讲解和windows实现部分跳过
第 12 章 I/O 复用
12.1 基于 I/O 复用的服务器端
12.2 理解 select 函数并实现服务端
Q:select函数将多个文件描述符集中到一起统一监视的3个项目是
- 是否存在套接字接收数据?
- 无需阻塞传输数据的套接字有哪些
- 哪些套接字发生了异常
Q:select函数的调用方法和顺序
- 1.设置文件描述符、指定监视范围、设置超时
- 2.调用select函数
- 3.查看调用结果
Q:操作select中的fd_set数组有哪四个宏?
FD_ZERO(fd_set *fdset)
:将 fd_set 变量的所有位初始化为0FD_SET(int fd, fd_set * fdset)
:在参数 fdset 指向的变量中注册文件描述符 fd 的信息FD_CLR(int fd, fd_set * fdset)
:从参数 fdset 指向的变量中清除文件描述符 fd 的信息FD_ISSET(int fd, fd_set * fdset)
:若参数 fdset 指向的变量中包含文件描述符 fd 的信息,则返回「真」
c12p199:select函数原型是怎样?
#include <sys/select.h> #include <sys/time.h> int select(int maxfd, fd_set * readset, fd_set * writeset, fd_set * exceptset, const struct timeval * timeout); // maxfd:监视对象文件描述符数量 // readset:将所有关注「是否存在待读取数据」的文件描述符 // 注册到 fd_set 型变量,并传递其地址值 // writeset:将所有关注「是否可传输无阻塞数据」的文件描述符 // 注册到 fd_set 型变量,并传递其地址值 // exceptset:将所有关注「是否发生异常」的文件描述符 // 注册到 fd_set 型变量,并传递其地址值 // timeout:调用 select 函数后,为防止陷入无限阻塞的状态, // 传递超时(time-out)信息 // 返回值:1.发生错误时返回 -1, // 2.超时返回时返回 0 // 3.因发生关注的事件返回时,返回大于 0 的值, // 该值是发生事件的文件描述符数
select 函数用来验证 3 种监视项的变化情况。根据监视声明 3 个 fd_set 型变量,分别向其注册文件描述符信息,并把变量的地址值传递到上述函数的第二到第四个参数
文件描述符的监视范围:
- select 函数要求通过第一个参数传递监视对象文件描述符的数量。因每次新建文件描述符时,其值都会增1,故只需将最大的文件描述符值加 1 再传递即可,加 1 是因为文件描述符的值从 0 开始
select 函数的超时时间:
timeout传递NULL,select 函数只有在监视的文件描述符发生变化时才返回。如果未发生变化,就会进入阻塞状态。
只要过了设置的超时时间,也可返回,此时返回值为 0
struct timeval { long tv_sec; // seconds long tv_usec; // microseconds }
select 函数返回正整数时,向其传递的 fd_set 变量将发生变化,可通过值为 1 的位置上的文件描述符获知哪些 fd 发生了变化
c12p201:select调用示例select.c
#include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <sys/select.h> #define BUF_SIZE 30 int main() { fd_set reads, temps; int result, str_len; char buf[BUF_SIZE]; struct timeval timeout; // 初始化变量 FD_ZERO(&reads); // 将文件描述符0对应的位设置为1 // 即监视标准输入的变化 FD_SET(0, &reads); while (1) { // 将准备好的fd_set变量reads的内容复制到temps变量, // 因为调用select函数后,除发生变化的文件描述符对应位外, // 剩下的位将初始化为0。为了记住初始化值,必须复制 temps = reads; // 设置select函数的超时,因调用select后,timeval的成员的值 // 将被替换为超时前剩余时间。所以在调用select前均需初始化 timeout.tv_sec = 5; timeout.tv_usec = 0; // 如果控制台输入数据会返回大于0的整数 // 如果没有输入数据而引发超时,返回0 result = select(1, &temps, 0, 0, &timeout); if (result == -1) { puts("select() error!"); break; } else if (result == 0) { puts("Time-out!"); } else { if (FD_ISSET(0, &temps)) { // 验证发生变化的文件描述符是否为标准输入 // 若是,从标准输入读取数据并向控制台输出 str_len = read(0, buf, BUF_SIZE); buf[str_len] = 0; printf("message from concole: %s", buf); } } } return 0; }
shiqi@pc:~/network/ch12$ gcc select.c -o select shiqi@pc:~/network/ch12$ ./select Hi~~ message from concole: Hi~~ hahaahhaha message from concole: hahaahhaha Time-out! Time-out! ^C
Q:select函数实现I/O复用服务器端
服务端echo_selectserv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h> #define BUF_SIZE 100 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char *argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; struct timeval timeout; fd_set reads, cpy_reads; socklen_t adr_sz; int fd_max, str_len, fd_num, i; char buf[BUF_SIZE]; if (argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) { error_handling("bind() error"); } if (listen(serv_sock, 5) == -1) { error_handling("listen() error"); } FD_ZERO(&reads); // 注册服务端套接字 FD_SET(serv_sock, &reads); fd_max = serv_sock; while (1) { cpy_reads = reads; timeout.tv_sec = 5; timeout.tv_usec = 5000; if ((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout)) == -1) { break; } if (fd_num == 0) { continue; } for (i = 0; i < fd_max + 1; ++i) { if (!FD_ISSET(i, &cpy_reads)) { // 查找发生状态变化的(有接收数据套接字的) // 文件描述符 continue; } if (i == serv_sock) { // 服务端套接字有变化,受理连接请求 adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz); // 注册与客户端连接的套接字文件描述符 FD_SET(clnt_sock, &reads); if (fd_max < clnt_sock) { fd_max = clnt_sock; } printf("connected client: %d \n", clnt_sock); } else { str_len = read(i, buf, BUF_SIZE); // 确认接收的数据是字符串还是断开连接的EOF if (str_len == 0) { FD_CLR(i, &reads); close(i); printf("closed client: %d \n", i); } else { write(i, buf, str_len); } } } } close(serv_sock); return 0; }
客户端同ehco_client.c
编译运行
shiqi@pc:~/network/ch12$ gcc echo_selectserv.c -o selserv shiqi@pc:~/network/ch12$ ./selserv 9190 connected client: 4 connected client: 5 closed client: 5 closed client: 4
shiqi@pc:~/network/ch04$ ./eclient2 127.0.0.1 9190 Connceted........ Input message(Q to quit): ur Message from server: ur Input message(Q to quit): d Message from server: d Input message(Q to quit): q
shiqi@pc:~/network/ch04$ ./eclient2 127.0.0.1 9190 Connceted........ Input message(Q to quit): x Message from server: x Input message(Q to quit): p Message from server: p Input message(Q to quit): m Message from server: m Input message(Q to quit): a Message from server: a Input message(Q to quit): q
第 13 章 多种I/O函数
13.1 send&recv函数
c13p211:linux send函数原型
#include <sys/socket.h> ssize_t send(int sockfd, const void * buf, size_t nbytes, int flags); // sockfd:表示与数据传输对象的连接的套接字文件描述符 // buf:保存待传输数据的缓冲地址值 // nbytes:待传输的字节数 // flags:传输数据时指定的可选项信息 // 成功时返回发送的字节数,失败时返回 -1
c13p212:linux write函数原型
#include <sys/socket.h> ssize_t recv(int sockfd, void * buf, size_t nbytes, int flags); // sockfd:表示数据接收对象的连接的套接字文件描述符 // buf:保存接收数据的缓冲地址值 // nbytes:可接收的最大字节数 // flags:接收数据时指定的可选项信息 // 成功时返回接收的字节数(收到EOF时返回0),失败时返回 -1
c13p212:send和recv函数最后一个 flag 参数的可选项有哪些?
- 该可选项可利用位或(bit OR)运算(|运算符)传递多个信息
c13p212:使用 MSG_OOB 发送紧急消息的程序示例 oob_send.c 和 oob_recv.c
oob_send.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #define BUF_SIZE 30 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char * argv[]) { int sock; struct sockaddr_in recv_adr; if (argc != 3) { printf("Usage: %s <IP> <port>\n", argv[0]); exit(1); } sock = socket(PF_INET, SOCK_STREAM, 0); memset(&recv_adr, 0, sizeof(recv_adr)); recv_adr.sin_family = AF_INET; recv_adr.sin_addr.s_addr = inet_addr(argv[1]); recv_adr.sin_port = htons(atoi(argv[2])); if (connect(sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1) { error_handling("connect() error!"); } // 此处使用 send 紧急传输数据 write(sock, "123", strlen("123")); send(sock, "4", strlen("4"), MSG_OOB); write(sock, "567", strlen("567")); send(sock, "890", strlen("890"), MSG_OOB); close(sock); return 0; }
oob_recv.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <sys/socket.h> #include <netinet/in.h> #include <fcntl.h> #define BUF_SIZE 30 int acpt_sock; int recv_sock; void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } void urg_handler(int signo) { int str_len; char buf[BUF_SIZE]; str_len = recv(recv_sock, buf, sizeof(buf)-1, MSG_OOB); buf[str_len] = 0; printf("Urgent message: %s\n", buf); } int main(int argc, char * argv[]) { struct sockaddr_in recv_adr, serv_adr; int str_len, state; socklen_t serv_adr_sz; struct sigaction act; char buf[BUF_SIZE]; if (argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } act.sa_handler = urg_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; acpt_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&recv_adr, 0, sizeof(recv_adr)); recv_adr.sin_family = AF_INET; recv_adr.sin_addr.s_addr = htonl(INADDR_ANY); recv_adr.sin_port = htons(atoi(argv[1])); if (bind(acpt_sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1) { error_handling("bind() error"); } listen(acpt_sock, 5); serv_adr_sz = sizeof(serv_adr); recv_sock = accept(acpt_sock, (struct sockaddr *)&serv_adr, &serv_adr_sz); // 指定当前进程为处理 SIGURG 信号的主体 fcntl(recv_sock, F_SETOWN, getpid()); state = sigaction(SIGURG, &act, 0); while ((str_len = recv(recv_sock, buf, sizeof(buf), 0)) != 0) { if (str_len == -1) { continue; } buf[str_len] = 0; puts(buf); } close(recv_sock); close(acpt_sock); return 0; }
编译运行
shiqi@pc:~/network/ch13$ gcc oob_recv.c -o recv shiqi@pc:~/network/ch13$ ./recv 9190 Urgent message: 0 123456789 shiqi@pc:~/network/ch13$ ./recv 9190 123456789
shiqi@pc:~/network/ch13$ gcc oob_send.c -o send shiqi@pc:~/network/ch13$ ./send 127.0.0.1 9190
c13p217:紧急模式的工作原理是怎样?
- 调用如下函数后的输出缓冲状态如下图:
send(sock, "890", strlen("890"), MSG_OOB)
- 如果将缓冲最左端的位置视作偏移量为 0,字符 0 保存与偏移量为 2 的位置。另外,字符 0 右侧偏移量为 3 的位置存有紧急指针(Urgent Pointer)。紧急指针指向紧急消息的下一个位置。
- 实际只用 1 个字节表示紧急消息信息,见如下 TCP 数据包结构
- 指定 MSG_OOB 选项的数据包本身就是紧急数据包,并通过紧急紧急指针表示紧急消息所在位置
- 除紧急指针的前面 1 个字节外,数据接收方将通过调用常用输入函数读取剩余部分
c13p218:同时设置 MSG_PEEK 和 MSG_DONTWAIT 选项来验证输入缓冲中是否存在接收的数据的程序示例 peek_send.c 和 peek_recv.c
设置 MSG_PEEK 选项并调用 recv 函数时,即使读取了输入缓冲中的数据也不会删除。因此通常与 MSG_DONTWAIT 合作,用于非阻塞方式验证待读数据存在与否
通过运行结果可以验证,仅发送1次的数据被读取了2次,因为第一次调用recv函数时设置了MSG_PEEK可选项。以上就是MSG_PEEK可选项的功能
peek_send.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char * argv[]) { int sock; struct sockaddr_in send_adr; if (argc != 3) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); } sock = socket(PF_INET, SOCK_STREAM, 0); memset(&send_adr, 0, sizeof(send_adr)); send_adr.sin_family = AF_INET; send_adr.sin_addr.s_addr = inet_addr(argv[1]); send_adr.sin_port = htons(atoi(argv[2])); if (connect(sock, (struct sockaddr *)&send_adr, sizeof(send_adr)) == -1) { error_handling("connect() error!"); } write(sock, "123", strlen("123")); close(sock); return 0; }
peek_recv.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #define BUF_SIZE 30 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char *argv[]) { int acpt_sock, recv_sock; struct sockaddr_in acpt_adr, recv_adr; int str_len, state; socklen_t recv_adr_sz; char buf[BUF_SIZE]; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); exit(1); } acpt_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&acpt_adr, 0, sizeof(acpt_adr)); acpt_adr.sin_family = AF_INET; acpt_adr.sin_addr.s_addr = htonl(INADDR_ANY); acpt_adr.sin_port = htons(atoi(argv[1])); if (bind(acpt_sock, (struct sockaddr *)&acpt_adr, sizeof(acpt_adr)) == -1) { error_handling("bind() error"); } listen(acpt_sock, 5); recv_adr_sz = sizeof(recv_adr); recv_sock = accept(acpt_sock, (struct sockaddr *)&recv_adr, &recv_adr_sz); while (1) { str_len = recv(recv_sock, buf, sizeof(buf)-1, MSG_PEEK|MSG_DONTWAIT); if (str_len > 0) { break; } } buf[str_len] = '\0'; printf("Buffering %d byets: %s\n", str_len, buf); str_len = recv(recv_sock, buf, sizeof(buf)-1, 0); buf[str_len] = '\0'; printf("Read again: %s\n", buf); close(acpt_sock); close(recv_sock); return 0; }
编译运行
shiqi@pc:~/network/ch13$ gcc peek_recv.c -o recv shiqi@pc:~/network/ch13$ ./recv 9190 Buffering 3 byets: 123 Read again: 123
shiqi@pc:~/network/ch13$ gcc peek_send.c -o send shiqi@pc:~/network/ch13$ ./send 127.0.0.1 9190
13.2 readv&writev函数
c13p221:将分散保存在多个缓冲中的数据一并发送的 writev 函数
#include <sys/uio.h> ssize_t writev(int filedes, const struct iovec * iov, int iovcnt); // filedes: 表示数据传输对象的套接字文件描述符。但该函数并不只 // 限于套接字,因此可以像 read 函数一样向其传递文件或 // 标准输出描述符 // iov: iovec 结构体数组的地址值,结构体 iovec 中包含待发送 // 数据的位置和大小信息 // iovcnt: 向第二个参数传递的数组长度 // 成功时返回发送的字节数,失败时返回-1 struct iovec { void * iov_base; // 缓冲地址 size_t iov_len; // 缓冲大小 }
如图,writev 第一个参数 1 是文件描述符,向控制台输出数据,ptr 是存有待发送数据信息的 iovec 数组指针。第三个参数是 2,即从 ptr 指向的地址开始,共浏览 2 个 iovec 结构体变量,发送这些指针指向的缓冲数据。
c13p222:writev 函数的使用示例 writev.c
#include <stdio.h> #include <sys/uio.h> int main(int argc, char * argv[]) { struct iovec vec[2]; char buf1[] = "ABCDEFG"; char buf2[] = "1234567"; int str_len; vec[0].iov_base = buf1; vec[0].iov_len = 3; vec[1].iov_base = buf2; vec[1].iov_len = 4; str_len = writev(1, vec, 2); puts(""); printf("Write byets: %d\n", str_len); return 0; }
shiqi@pc:~/network/ch13$ gcc writev.c -o wv shiqi@pc:~/network/ch13$ ./wv ABC1234 Write byets: 7
c13p223:由多个缓冲分别接收的 readv 函数
#inclde <sys/uio.h> ssize_t readv(int filedes, const struct iovec * iov, int iovcnt); // filedes: 表示数据传输对象的套接字文件描述符 // iov: iovec 结构体数组的地址值 // iovcnt: 向第二个参数传递的数组长度 // 成功时返回发送的字节数,失败时返回-1
c13p223:readv 函数的使用示例 readv.c
#include <stdio.h> #include <sys/uio.h> #define BUF_SIZE 100 int main() { struct iovec vec[2]; char buf1[BUF_SIZE] = {0,}; char buf2[BUF_SIZE] = {0,}; int str_len; vec[0].iov_base = buf1; vec[0].iov_len = 5; vec[1].iov_base = buf2; vec[1].iov_len = BUF_SIZE; str_len = readv(0, vec, 2); printf("Read bytes: %d\n", str_len); printf("First message: %s\n", buf1); printf("Second message: %s\n", buf2); return 0; }
shiqi@:~/network/ch13$ gcc readv.c -o rv shiqi@:~/network/ch13$ ./rv I want to fly~~~~~~~~~~~. gogogo! Read bytes: 34 First message: I wan Second message: t to fly~~~~~~~~~~~. gogogo!
第 14 章 多播与广播
14.1 多播
c14p230:多播的数据传输特点是什么?
- 1.多播服务器端针对特定多播组,只发送 1 次数据
- 2.即使只发送一次数据,但所有的客户端都会收到数据
- 3.多播组数可在 IP 范围内任意增加
- 4.加入特定组即可接收发往该多播组的数据
- 5.多播是基于 UDP 完成的,当其向网络传递 1 个多播数据包时,路由器将复制该数据包并传递到多个主机
c14p232:如何在代码中设置 TTL?
协议层是 IPPROTO_IP,选项名为 IP_MULTICAST_TTL
int send_sock; int time_live = 64; ... send_sock = socket(PF_INET, SOCK_DGRAM, 0); setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&time_live, sizeof(time_live)); ...
c14p232:加入多播组的代码是怎样?
协议层是 IPPROTO_IP,选项名为 IP_ADD_MEMBERSHIP
int send_sock; struct ip_mreq join_adr; ... send_sock = socket(PF_INET, SOCK_DGRAM, 0); ... join_adr.imr_multiaddr.s_addr = "多播组地址信息"; join_adr.imr_imterface.s_addr = "加入多播组的主机地址信息"; setsockopt(send_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&join_adr, sizeof(join_adr)); ...
其中 ip_mreq 的定义为
struct ip_mreq { // 写入加入的组 IP 地址 struct in_addr imr_multiaddr; // 加入该组的套接字所属主机的 IP 地址,也可用 INADDR_ANy struct in_addr imr_interface; }
c14p233:实现多播 Sender 和 Receiver 的程序示例 news_sender.c 和 news_receiver.c
Sender:向AAA组广播( Broadcasting )文件中保存的新闻信息,news_sender.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define TTL 64 #define BUF_SIZE 30 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char * argv[]) { int send_sock; struct sockaddr_in mul_adr; int time_live = TTL; FILE * fp; char buf[BUF_SIZE]; if (argc != 3) { printf("Usage : %s <GroupIP> <PORT>\n", argv[0]); exit(1); } send_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&mul_adr, 0, sizeof(mul_adr)); mul_adr.sin_family = AF_INET; mul_adr.sin_addr.s_addr = inet_addr(argv[1]); mul_adr.sin_port = htons(atoi(argv[2])); setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&time_live, sizeof(time_live)); if ((fp = fopen("news.txt", "r")) == NULL) { error_handling("fopen() error"); } while (!feof(fp)) { fgets(buf, BUF_SIZE, fp); sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr *)&mul_adr, sizeof(mul_adr)); sleep(2); } fclose(fp); close(send_sock); return 0; }
Receiver:接收传递到AAA组的新闻信息,news_receiver.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 30 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char * argv[]) { int recv_sock; int str_len; char buf[BUF_SIZE]; struct sockaddr_in adr; struct ip_mreq join_adr; if (argc != 3) { printf("Usage : %s <GroupIP> <PORT>\n", argv[0]); exit(1); } recv_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&adr, 0, sizeof(adr)); adr.sin_family = AF_INET; adr.sin_addr.s_addr = htonl(INADDR_ANY); adr.sin_port = htons(atoi(argv[2])); if (bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1) { error_handling("bind() error"); } join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]); join_adr.imr_interface.s_addr = htonl(INADDR_ANY); setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&join_adr, sizeof(join_adr)); while (1) { str_len = recvfrom(recv_sock, buf, BUF_SIZE-1 , 0, NULL, 0); if (str_len < 0) { break; } buf[str_len] = 0; fputs(buf, stdout); } close(recv_sock); return 0; }
编译运行
shiqi@pc:~/network/ch14$ cat news.txt 111111111111 222222222 3333 shiqi@pc:~/network/ch14$ gcc news_receiver.c -o receiver shiqi@pc:~/network/ch14$ ./receiver 224.1.1.2 9190 111111111111 222222222 3333 3333 shiqi@pc:~/network/ch14$ gcc news_sender.c -o sender shiqi@pc:~/network/ch14$ ./sender 224.1.1.2 9190
14.2 广播
c14p237:如何代码设置进行数据广播?
调用 setsockopt,将 SO_BROADCAST 设置为 bcast 变量中的 1
int send_sock; int bcast = 1; ... send_sock = socket(PF_INET, SOCK_DGRAM, 0); ... setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&bcast, sizeof(bcast));
c14p237:实现广播 Sender 和 Receiver 的程序示例 news_sender_brd.c 和 news_receiver_brd.c
news_receiver_brd.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 30 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char * argv[]) { int recv_sock; int str_len; char buf[BUF_SIZE]; struct sockaddr_in adr; if (argc != 2) { printf("Usage : %s <PORT>\n", argv[0]); exit(1); } recv_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&adr, 0, sizeof(adr)); adr.sin_family = AF_INET; adr.sin_addr.s_addr = htonl(INADDR_ANY); adr.sin_port = htons(atoi(argv[1])); if (bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1) { error_handling("bind() error"); } while (1) { str_len = recvfrom(recv_sock, buf, BUF_SIZE-1 , 0, NULL, 0); if (str_len < 0) { break; } buf[str_len] = 0; fputs(buf, stdout); } close(recv_sock); return 0; }
news_sender_brd.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 30 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char * argv[]) { int send_sock; struct sockaddr_in mul_adr; FILE * fp; char buf[BUF_SIZE]; int so_brd = 1; if (argc != 3) { printf("Usage : %s <Broadcast IP> <PORT>\n", argv[0]); exit(1); } send_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&mul_adr, 0, sizeof(mul_adr)); mul_adr.sin_family = AF_INET; mul_adr.sin_addr.s_addr = inet_addr(argv[1]); mul_adr.sin_port = htons(atoi(argv[2])); setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void *)&so_brd, sizeof(so_brd)); if ((fp = fopen("news.txt", "r")) == NULL) { error_handling("fopen() error"); } while (!feof(fp)) { fgets(buf, BUF_SIZE, fp); sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr *)&mul_adr, sizeof(mul_adr)); sleep(2); } fclose(fp); close(send_sock); return 0; }
编译运行
shiqi@pc:~/network/ch14$ cat news.txt 111111111111 222222222 3333 shiqi@pc:~/network/ch14$ gcc news_receiver_brd.c -o receiver shiqi@pc:~/network/ch14$ ./receiver 9190 111111111111 222222222 3333 3333 shiqi@pc:~/network/ch14$ gcc news_sender_brd.c -o sender shiqi@pc:~/network/ch14$ ./sender 9190 Usage : ./sender <Broadcast IP> <PORT> shiqi@pc:~/network/ch14$ ./sender 255.255.255.255 9190