第八章:基本UDP套接字编程

在这章要完成一个练习:

在电脑上建立三个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来实现自己写个在链路层以上的协议,来完成自己的信息传输

你可能感兴趣的:(UDP套接字,聊天功能)