在这章要完成一个练习:
在电脑上建立三个IP,可以用IP别名,然后编写如下程序:
建立
A: UDP客户端
B: UDP服务端
C: UDP服务端
A向B传递C的IP及端口号外加数据,然后B向C发送A的IP及端口号外加A的数据
就像是一个代理一样。
这一章其实主要是学习UDP的创建及两个数据传输函数recvfrom和sendto。
ssize_t recvfrom(int sockfd,
void *buf,
size_t nbytes,
int flags,
struct sockaddr *from,
socklen_t *addrlen);
ssize_t sendto(int sockfd,
const void *buf,
size_t nbytes,
int flags,
const struct sockaddr *to,
soclen_t addrlen);
在UPD使用connect时,如果服务器端相应端口启用,是不会出错的,只是在使用书中的例子时,
在客户端读取终端输入后,调用read时,会返回-1,打印错误信息是connection refused。但是,
这里不要退出,只要服务端现在开启,又可以正常工作了。
在测试过程中,不论是服务器未开启,还开启后,再断开,再开启,都可以继续正常工作。
在书后面看到这样一句:“在UDP套接字上,调用connect并不给对端主机发送任何信息,它完全是一个本地操作,只是保存对端的IP地址和端口号”
这应该就是上面那种效果的原因了。
下面来实现刚开始说要实现的功能,从最基本的开始,最后可以实现成类似的聊天软件:
先是talker.c
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h>
#define MAXLINE 1024
#define MEDIATOR_IP "192.168.22.134" #define MEDIATOR_PORT 8888 /* 这是代理的IP和端口,这个应该是固定不变的,要不然无法得到 */
struct info { struct sockaddr_in addr; char data[MAXLINE]; }; /* 当时考虑怎么传这些数据,就定义了一个结构来保存IP、端口、还有数据*/ /*以上信息最好都放在头文件中,属于共用的 */
int main(int argc, char *argv[]) { if (argc != 3) { /* 通过参数来传递listener的IP及端口号 */ printf("Usgae: %s <toIP> <toPort>\n", argv[0]); exit(0); }
int sockfd; struct sockaddr_in servaddr; int ret; // socklen_t len; ssize_t n; struct info info; char mesg[MAXLINE];
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MEDIATOR_PORT); if ((ret = inet_pton(AF_INET, MEDIATOR_IP, &servaddr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
bzero(&info, sizeof(info)); info.addr.sin_family = AF_INET; info.addr.sin_port = htons(atoi(argv[2])); if ((ret = inet_pton(AF_INET, argv[1], &info.addr.sin_addr)) == 0) { printf("inet_pton() error: IP format error\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
while ((fgets(info.data, MAXLINE, stdin)) != NULL) { sendto(sockfd, &info, sizeof(info), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); // n = recvfrom(sockfd, mesg, MAXLINE, 0, NULL, NULL); // mesg[n] = 0; // fputs(mesg, stdout); /* 上面注释掉的是开始想实现双向的通信,后来注释了,先这样,再改进 */ /* 并且sendto这样传数据应该有很大的浪费 */ }
return 0; }
原本是想做成双方交流的,后来想想还是先来单向的开始,之后再改进成双向的。
下面是modiator.c,也就是转达者:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h>
#define MAXLINE 1024
#define MODIATOR_IP "192.168.22.134" #define MODIATOR_PORT 8888
struct info { struct sockaddr_in addr; char data[MAXLINE]; };
int main(int argc, char *argv[]) { int sockfd, ret; struct sockaddr_in self_addr, listener_addr, talker_addr; struct info info; socklen_t len;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&self_addr, sizeof(self_addr)); self_addr.sin_family = AF_INET; self_addr.sin_port = htons(MODIATOR_PORT); if ((ret = inet_pton(AF_INET, MODIATOR_IP, &self_addr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
if ((bind(sockfd, (struct sockaddr *)&self_addr, sizeof(self_addr))) == -1) { printf("bind() error: %s\n", strerror(errno)); exit(1); }
while (1) { len = sizeof(talker_addr); recvfrom(sockfd, &info, sizeof(info), 0, (struct sockaddr *)&talker_addr, &len); printf("talker say: %s\n", info.data); bzero(&listener_addr, sizeof(listener_addr)); memcpy(&listener_addr, &info.addr, sizeof(listener_addr)); sendto(sockfd, info.data, strlen(info.data) + 1, 0, (struct sockaddr *)&listener_addr, sizeof(listener_addr)); }
return 0; }
下面是真正的听众listener.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h>
#define LISTENER_IP "192.168.22.233" #define LISTENER_PORT 9999 #define MAXLINE 1024
int main(int argc, char *argv[]) { int sockfd, ret; struct sockaddr_in listener_addr, modiator_addr; char mesg[MAXLINE]; ssize_t n;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&listener_addr, sizeof(listener_addr)); listener_addr.sin_family = AF_INET; listener_addr.sin_port = htons(LISTENER_PORT); if ((ret = inet_pton(AF_INET, LISTENER_IP, &listener_addr.sin_addr)) == 0) { } else if (ret < 0) { }
if ((bind(sockfd, (struct sockaddr *)&listener_addr, sizeof(listener_addr))) == -1) { }
while (1) { n = recvfrom(sockfd, mesg, MAXLINE, 0, NULL, NULL); mesg[n] = 0; fputs(mesg, stdout); }
return 0; }
接下来实现双向一问一答试的例子,只需要添加一些内容就可以实现了,不过这样其实有很多问题在里面,
比如,如果不按照顺序来进行,就会出现问题,因为可以阻塞到某一个读操作上,这都是需要改进的地方。
不过慢慢来,我可以通过改进这些东西来学习新知识。
下面是改过的talker.c
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h>
#define MAXLINE 1024
#define MEDIATOR_IP "192.168.22.134" #define MEDIATOR_PORT 8888
struct info { struct sockaddr_in addr; char data[MAXLINE]; };
int main(int argc, char *argv[]) { if (argc != 3) { printf("Usgae: %s <toIP> <toPort>\n", argv[0]); exit(0); }
int sockfd; struct sockaddr_in servaddr; int ret; // socklen_t len; ssize_t n; struct info info; char mesg[MAXLINE];
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MEDIATOR_PORT); if ((ret = inet_pton(AF_INET, MEDIATOR_IP, &servaddr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
bzero(&info, sizeof(info)); info.addr.sin_family = AF_INET; info.addr.sin_port = htons(atoi(argv[2])); if ((ret = inet_pton(AF_INET, argv[1], &info.addr.sin_addr)) == 0) { printf("inet_pton() error: IP format error\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
/* 要实现双向通信,就要在发送信息之后,等待发回来的信息 */ while ((fgets(info.data, MAXLINE, stdin)) != NULL) { sendto(sockfd, &info, sizeof(info), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); /* 这里不需要对端的IP,因为这个是已知的 */ n = recvfrom(sockfd, mesg, MAXLINE, 0, NULL, NULL); mesg[n] = 0; fputs(mesg, stdout); }
return 0; }
下面是modiator.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h>
#define MAXLINE 1024
#define MODIATOR_IP "192.168.22.134" #define MODIATOR_PORT 8888
struct info { struct sockaddr_in addr; char data[MAXLINE]; };
int main(int argc, char *argv[]) { int sockfd, ret; struct sockaddr_in self_addr, listener_addr, talker_addr; struct info info; socklen_t len; char mesg[MAXLINE]; ssize_t n;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&self_addr, sizeof(self_addr)); self_addr.sin_family = AF_INET; self_addr.sin_port = htons(MODIATOR_PORT); if ((ret = inet_pton(AF_INET, MODIATOR_IP, &self_addr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
if ((bind(sockfd, (struct sockaddr *)&self_addr, sizeof(self_addr))) == -1) { printf("bind() error: %s\n", strerror(errno)); exit(1); }
/* 要实现双向通信,需要在发送给listener信息后等待回复,再发送给talker */ while (1) { len = sizeof(talker_addr); recvfrom(sockfd, &info, sizeof(info), 0, (struct sockaddr *)&talker_addr, &len); printf("talker say: %s\n", info.data); bzero(&listener_addr, sizeof(listener_addr)); memcpy(&listener_addr, &info.addr, sizeof(listener_addr)); sendto(sockfd, info.data, strlen(info.data) + 1, 0, (struct sockaddr *)&listener_addr, sizeof(listener_addr)); n = recvfrom(sockfd, mesg, sizeof(mesg), 0, NULL, NULL); mesg[n] = 0; printf("listener say: %s\n", mesg); sendto(sockfd, mesg, strlen(mesg) + 1, 0, (struct sockaddr *)&talker_addr, len); }
return 0; }
下面是listener.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h>
#define LISTENER_IP "192.168.22.233" #define LISTENER_PORT 9999 #define MAXLINE 1024
int main(int argc, char *argv[]) { int sockfd, ret; struct sockaddr_in listener_addr, modiator_addr; socklen_t len; char mesg[MAXLINE]; ssize_t n;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&listener_addr, sizeof(listener_addr)); listener_addr.sin_family = AF_INET; listener_addr.sin_port = htons(LISTENER_PORT); if ((ret = inet_pton(AF_INET, LISTENER_IP, &listener_addr.sin_addr)) == 0) { } else if (ret < 0) { }
if ((bind(sockfd, (struct sockaddr *)&listener_addr, sizeof(listener_addr))) == -1) { }
/* 这里由于要传回信息需要记录modiator的IP信息 */ while (1) { len = sizeof(modiator_addr); n = recvfrom(sockfd, mesg, MAXLINE, 0, (struct sockaddr *)&modiator_addr, &len); mesg[n] = 0; fputs(mesg, stdout); fgets(mesg, MAXLINE, stdin); sendto(sockfd, mesg, strlen(mesg) + 1, 0, (struct sockaddr *)&modiator_addr, len); }
return 0; }
现在这种情况每次启动talker都要输入对方的IP和端口号,这样其实很不方便,我们使用qq的时候,
都是直接点对方的名字就可以了,那么我们现在来实现,通过输入对方名字来完成双方的通信功能。
下面的版本是限定只有这两个人聊天,通过modiator来相互转发,这样每个人只要知道服务器的IP和端口就可以通信了,
但是需要modiator先启动,然后先后启用talker和listener,其实这两个文件talker.c和listener.c功能相同,
只是里面的user_name不同而已,所以可以用一个来代替,通过传递参数来完成user_name的赋值。
但是有一点要注意,在里面都是用bind绑定了地址和端口,如果使用一个程序,那么IP和端口号也需要传参了
不过我没有改,还是三个文件来写的,如下:
talker.c
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h>
#define max(a, b) ((a) < (b) ? (b) : (a))
#define MAXLINE 1024
#define MEDIATOR_IP "192.168.22.134" #define MEDIATOR_PORT 8888
#define SELF_IP "192.168.22.232" #define SELF_PORT 9999
int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in servaddr, self_addr; int ret; // socklen_t len; ssize_t n; char mesg[MAXLINE]; char user_name[] = "lilei";
/* 下面这两个用于select */ fd_set rset; int maxfdp1; FD_ZERO(&rset);
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&self_addr, sizeof(self_addr)); self_addr.sin_family = AF_INET; self_addr.sin_port = htons(SELF_PORT); if ((ret = inet_pton(AF_INET, SELF_IP, &self_addr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); } if ((bind(sockfd, (struct sockaddr *)&self_addr, sizeof(self_addr))) == -1) { printf("bind() error: %s\n", strerror(errno)); exit(1); }
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MEDIATOR_PORT); if ((ret = inet_pton(AF_INET, MEDIATOR_IP, &servaddr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
/* 发送自己的名字到modiator */ sendto(sockfd, user_name, sizeof(user_name), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) { FD_SET(fileno(stdin), &rset); FD_SET(sockfd, &rset); maxfdp1 = max(fileno(stdin), sockfd) + 1; select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) { n = recvfrom(sockfd, mesg, MAXLINE, 0, NULL, NULL); mesg[n] = 0; fputs(mesg, stdout); }
if (FD_ISSET(fileno(stdin), &rset)) { fgets(mesg, MAXLINE, stdin); sendto(sockfd, mesg, strlen(mesg) + 1, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); } }
return 0; }
modiator.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h>
#define MAXLINE 1024
#define MODIATOR_IP "192.168.22.134" #define MODIATOR_PORT 8888
struct user_info { char user_name[12]; struct sockaddr_in user_addr; };
int main(int argc, char *argv[]) { int sockfd, ret, i; struct sockaddr_in self_addr, user_addr; socklen_t len; char recv_mesg[MAXLINE]; char send_mesg[MAXLINE]; ssize_t n;
/* 这里限制就两个用户 */ struct user_info info[2];
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&self_addr, sizeof(self_addr)); self_addr.sin_family = AF_INET; self_addr.sin_port = htons(MODIATOR_PORT); if ((ret = inet_pton(AF_INET, MODIATOR_IP, &self_addr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
if ((bind(sockfd, (struct sockaddr *)&self_addr, sizeof(self_addr))) == -1) { printf("bind() error: %s\n", strerror(errno)); exit(1); }
/* 这里假定两个用户先后登录 */ len = sizeof(user_addr); for (i = 0; i < 2; i ++) { recvfrom(sockfd, recv_mesg, MAXLINE, 0, (struct sockaddr *)&user_addr, &len); memcpy(&info[i].user_addr, &user_addr, len); memcpy(info[i].user_name, recv_mesg, strlen(recv_mesg) + 1); }
/* 这里就是来回的转发了 */ while (1) { len = sizeof(user_addr); recvfrom(sockfd, recv_mesg, sizeof(recv_mesg), 0, (struct sockaddr *)&user_addr, &len);
if (memcmp(&user_addr, &info[0].user_addr, sizeof(user_addr)) == 0) { snprintf(send_mesg, sizeof(send_mesg), "%s:%s", info[0].user_name, recv_mesg); printf("%s\n", send_mesg); sendto(sockfd, send_mesg, strlen(send_mesg) + 1, 0, (struct sockaddr *)&info[1].user_addr, sizeof(info[1].user_addr)); } else if (memcmp(&user_addr, &info[1].user_addr, sizeof(user_addr)) == 0) { snprintf(send_mesg, sizeof(send_mesg), "%s:%s", info[1].user_name, recv_mesg); printf("%s\n", send_mesg); sendto(sockfd, send_mesg, strlen(send_mesg) + 1, 0, (struct sockaddr *)&info[0].user_addr, sizeof(info[0].user_addr)); } }
return 0; }
listener.c
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h>
#define max(a, b) ((a) < (b) ? (b) : (a))
#define MAXLINE 1024
#define MEDIATOR_IP "192.168.22.134" #define MEDIATOR_PORT 8888
#define SELF_IP "192.168.22.233" #define SELF_PORT 9999
int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in servaddr, self_addr; int ret; // socklen_t len; ssize_t n; char mesg[MAXLINE]; char user_name[] = "lily";
/* 下面这两个用于select */ fd_set rset; int maxfdp1; FD_ZERO(&rset);
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("socket() error: %s\n", strerror(errno)); exit(1); }
bzero(&self_addr, sizeof(self_addr)); self_addr.sin_family = AF_INET; self_addr.sin_port = htons(SELF_PORT); if ((ret = inet_pton(AF_INET, SELF_IP, &self_addr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); } if ((bind(sockfd, (struct sockaddr *)&self_addr, sizeof(self_addr))) == -1) { printf("bind() error: %s\n", strerror(errno)); exit(1); }
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MEDIATOR_PORT); if ((ret = inet_pton(AF_INET, MEDIATOR_IP, &servaddr.sin_addr)) == 0) { printf("inet_pton() error: Not in presentation format\n"); exit(1); } else if (ret < 0) { printf("inet_pton() error: %s\n", strerror(errno)); exit(1); }
/* 发送自己的名字到modiator */ sendto(sockfd, user_name, sizeof(user_name), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) { FD_SET(fileno(stdin), &rset); FD_SET(sockfd, &rset); maxfdp1 = max(fileno(stdin), sockfd) + 1; select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) { n = recvfrom(sockfd, mesg, MAXLINE, 0, NULL, NULL); mesg[n] = 0; fputs(mesg, stdout); }
if (FD_ISSET(fileno(stdin), &rset)) { fgets(mesg, MAXLINE, stdin); sendto(sockfd, mesg, strlen(mesg) + 1, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); } }
return 0; }
这个就先实现到这了,第八章算是学完了,继续往下看书
这个例子以后还可以用libpcap和libnet来实现自己写个在链路层以上的协议,来完成自己的信息传输