网络编程-select模型、UDP上传下载客户端

目录

1.select-TCP服务器

2.select-TCP客户端

3.poll-TCP客户端

4.基于UDP上传下载客户端


1.select-TCP服务器

// 服务器端
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>


// 服务器端口号
#define POST 8080
// 服务器ip
#define IP "192.168.2.146"
int main(int argc, const char* argv[])
{
    // 1.创建套接字
    int serfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == serfd) {
        perror("socket");
        return -1;
    }
    // 2.允许端口快速重用
    int reuse = 1;
    if (-1 == setsockopt(serfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))) {
        perror("setsockopt");
        return -1;
    }
    // 3.定义实际地址信息结构体
    struct sockaddr_in ser_addr;
    ser_addr.sin_family = AF_INET; // 地址簇
    ser_addr.sin_port = htons(POST); // 端口号, 需要转网络字节序
    ser_addr.sin_addr.s_addr = inet_addr(IP); // 本机ip, 需要转网络字节序

    // 4.绑定ip和端口
    if (-1 == bind(serfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr))) {
        perror("bind");
        return -1;
    }

    // 5.设置套接字为监听状态
    if (-1 == listen(serfd, 10)) {
        perror("listfd");
        return -1;
    }
    // 6.定义存储成功连接客户端的地址信息
    struct sockaddr_in cli_addr;
    socklen_t addrlen = sizeof(cli_addr);

    // 7.定义读文件描述符集合
    fd_set readfds, tmpfds;
    // 清空集合
    FD_ZERO(&readfds);
    // 添加fd到集合
    FD_SET(0, &readfds);
    FD_SET(serfd, &readfds);

    int ret = 0; // select返回值
    char buf[128] = ""; // 定义保存接收到的数据
    ssize_t size = 0; // 保存每次接收到数据的大小
    int clifd = -1; // 成功连接客户端的套接字
    int maxfd = serfd; // 保存当前最大的fd
    struct sockaddr_in saveCli[1024 - 4]; // 保存成功连接的客户端地址信息
    while (1) {
        tmpfds = readfds;
        ret = select(maxfd + 1, &tmpfds, NULL, NULL, NULL);
        if (ret < 0) {
            perror("select");
            return -1;
        } else if (0 == ret) {
            printf("时间到\n");
            return -1;
        }
        // 监听事件
        for (int i = 0; i <= maxfd; i++) {
            if (!FD_ISSET(i, &tmpfds)) { // 如果不在集合中,则直接循环下一次
                continue;
            }
            // 运行到当前位置,说明i在集合中
            // 判断i对应文件描述符需要走的对应函数
            if (0 == i) {
                printf("事件触发>>>");
                fgets(buf, sizeof(buf), stdin);
                buf[strlen(buf) - 1] = '\0';
                printf("%s\n", buf);

            } else if (serfd == i) {
                clifd = accept(serfd, (struct sockaddr*)&cli_addr, &addrlen);
                if (-1 == clifd) {
                    perror("accept");
                    return -1;
                }
                printf("clifd=%d [%s][%d]客户端连接成功\n", clifd, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
                // 将成功连接的客户端地址信息保存到数据中
                saveCli[clifd - 4] = cli_addr;
                // 将clifd添加到读集合中
                FD_SET(clifd, &readfds);
                // 更新maxfd
                maxfd = maxfd > clifd ? maxfd : clifd;
            } else {
                // 清空buf
                bzero(buf, sizeof(buf));
                // 接收
                size = recv(i, buf, sizeof(buf), 0);
                if (-1 == size) {
                    perror("recv");
                    return -1;
                } else if (0 == size || strcasecmp(buf, "quit") == 0) {
                    printf("clifd=%d [%s][%d]客户端下线\n", i, inet_ntoa(saveCli[i - 4].sin_addr), ntohs(saveCli[i - 4].sin_port));
                    // 关闭文件描述符
                    close(i);
                    // 从读集合中删除该文件描述符
                    FD_CLR(i, &readfds);
                    // 更新maxfd
                    int j = 0;
                    for (j = maxfd; j >= 0; j--) { // 找到当前存在集合中最大的fd
                        if (FD_ISSET(j, &readfds)) {
                            maxfd = j;
                            break;
                        }
                    }
                    if (j < 0) {
                        maxfd = -1;
                    }
                    continue;
                }
                printf("clifd=%d [%s][%d]buf=%s\n", i, inet_ntoa(saveCli[i - 4].sin_addr), ntohs(saveCli[i - 4].sin_port), buf);
                strcat(buf, "*_*");
                if (-1 == send(i, buf, strlen(buf), 0)) {
                    perror("send");
                    return -1;
                }
            }
        }
    }

    // 关闭套接字
    // close(serfd);
    return 0;
}

2.select-TCP客户端

// 客户端
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

// 服务器端口号
#define POST 8080
// 服务器ip
#define IP "192.168.2.146"
int main(int argc, const char* argv[])
{
    // 1.创建套接字
    int clifd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == clifd) {
        perror("socket");
        return -1;
    }

    // 2.定义服务器地址信息结构体
    struct sockaddr_in ser_addr;
    ser_addr.sin_family = AF_INET; // 地址簇
    ser_addr.sin_port = htons(POST); // 端口号, 需要转网络字节序
    ser_addr.sin_addr.s_addr = inet_addr(IP); // 服务器ip, 需要转网络字节序
    // 3.连接服务器
    if (-1 == connect(clifd, (struct sockaddr*)&ser_addr, sizeof(ser_addr))) {
        perror("connect");
        return -1;
    }

    // 4.定义读文件描述符集合
    fd_set readfds, tmpfds;
    // 添加fd到集合
    FD_SET(0, &readfds);
    FD_SET(clifd, &readfds);

    int ret = 0; // select返回值
    char buf[128] = ""; // 定义保存接收到的数据
    ssize_t size = 0; // 保存每次接收到数据的大小
    // 5.监听文件描述符集合
    while (1) {
        tmpfds = readfds; // 保存集合
        // 阻塞监听
        ret = select(clifd + 1, &tmpfds, NULL, NULL, NULL);
        if (ret < 0) {
            perror("select");
            return -1;
        } else if (ret == 0) {
            printf("时间到\n");
            return -1;
        }
        // 判断触发的文件描述符
        if (FD_ISSET(0, &tmpfds)) {
            // 发送
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = '\0';
            if (-1 == send(clifd, buf, strlen(buf), 0)) {
                perror("send");
                return -1;
            }
        }
        // 判断触发的文件描述符
        if (FD_ISSET(clifd, &tmpfds)) {
            // 清空buf
            bzero(buf, sizeof(buf));
            // 接收
            size = recv(clifd, buf, sizeof(buf), 0);
            if (-1 == size) {
                perror("recv");
                return -1;
            } else if (0 == size) {
                printf("[%s][%d]服务器下线\n", inet_ntoa(ser_addr.sin_addr), ntohs(ser_addr.sin_port));
                break;
            }
            printf("[%s][%d]buf=%s\n", inet_ntoa(ser_addr.sin_addr), ntohs(ser_addr.sin_port), buf);
        }
    }

    // 关闭套接字
    close(clifd);
    return 0;
}

3.poll-TCP客户端

// 客户端
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

// 服务器端口号
#define POST 8080
// 服务器ip
#define IP "192.168.2.146"
int main(int argc, const char* argv[])
{
    // 1.创建套接字
    int clifd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == clifd) {
        perror("socket");
        return -1;
    }

    // 2.定义服务器地址信息结构体
    struct sockaddr_in ser_addr;
    ser_addr.sin_family = AF_INET; // 地址簇
    ser_addr.sin_port = htons(POST); // 端口号, 需要转网络字节序
    ser_addr.sin_addr.s_addr = inet_addr(IP); // 服务器ip, 需要转网络字节序
    // 3.连接服务器
    if (-1 == connect(clifd, (struct sockaddr*)&ser_addr, sizeof(ser_addr))) {
        perror("connect");
        return -1;
    }

    // 4.定义读文件描述符集合
    struct pollfd fds[2];
    // 添加fd到集合
    fds[0].fd = 0;
    fds[0].events = POLLIN;
    fds[1].fd = clifd;
    fds[1].events = POLLIN;

    int ret = -1; // select返回值
    char buf[128] = ""; // 定义保存接收到的数据
    ssize_t size = 0; // 保存每次接收到数据的大小
    // 5.监听文件描述符集合
    while (1) {

        // 阻塞监听
        ret = poll(fds, 2, -1);
        if (ret < 0) {
            perror("poll");
            return -1;
        } else if (ret == 0) {
            printf("时间到\n");
            return -1;
        }
        // 判断触发的POLLIN
        if (fds[0].revents & POLLIN) {
            // 发送
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = '\0';
            if (-1 == send(clifd, buf, strlen(buf), 0)) {
                perror("send");
                return -1;
            }
        }
        // 判断触发的POLLIN
        if (fds[1].revents & POLLIN) {
            // 清空buf
            bzero(buf, sizeof(buf));
            // 接收
            size = recv(clifd, buf, sizeof(buf), 0);
            if (-1 == size) {
                perror("recv");
                return -1;
            } else if (0 == size) {
                printf("[%s][%d]服务器下线\n", inet_ntoa(ser_addr.sin_addr), ntohs(ser_addr.sin_port));
                break;
            }
            printf("[%s][%d]buf=%s\n", inet_ntoa(ser_addr.sin_addr), ntohs(ser_addr.sin_port), buf);
        }
    }

    // 关闭套接字
    close(clifd);
    return 0;
}

4.基于UDP上传下载客户端

// 客户端(文件上传下载)
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

// 服务器端口号
#define POST 69
// 服务器ip
// #define IP "192.168.2.140" // 使用命令行传参

/// @brief 下载
/// @param serfd 服务器套接字
/// @param sin 地址信息
/// @return 成功0 失败-1
int download(int serfd, struct sockaddr_in sin);

/// @brief 上传
/// @param serfd 服务器套接字
/// @param sin 地址信息
/// @return 成功0 失败-1
int uploading(int serfd, struct sockaddr_in sin);

int main(int argc, const char* argv[])
{
    // 0.校验参数
    if(argc != 2){
        printf("input error\n");
        printf("usage: ./a.out ip(192.168.2.140)\n");
        return -1;
    }
    // 1.创建报式套接字
    int serfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == serfd) {
        perror("socket");
        return -1;
    }

    // 2.定义服务器地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET; // 地址簇
    sin.sin_port = htons(POST); // 服务器端口号, 需要本地字节序-->网络字节序
    sin.sin_addr.s_addr = inet_addr(argv[1]); // 服务器ip, 需要点分十进制-->网络字节序

    int options = -1; // 菜单选项
    while (1) {
        puts("------1.下载------");
        puts("------2.上传------");
        puts("------0.退出------");
        scanf("%d", &options);
        switch (options) {
        case 0:
            return 0;
        case 1:
            download(serfd, sin);
            break;
        case 2:
            uploading(serfd, sin);
            break;

        default:
            printf("输入错误,请重新输入\n");
            break;
        }
    }
}

int download(int serfd, struct sockaddr_in sin)
{
    char buf[516] = { 0 }; // 数据包
    // 1.初始化请求下载包
    printf("input filename:");
    char filename[128] = "";
    scanf("%s", filename);
    short* opcode = (short*)buf;
    *opcode = htons(1);
    sprintf(buf + 2, "%s%c%s%c", filename, '\0', "octet", '\0');

    socklen_t sin_size = sizeof(sin); // 地址信息结构体大小
    // 2.发送下载请求
    if (-1 == sendto(serfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sin_size)) {
        perror("sendto");
        return -1;
    }

    ssize_t len = 0; // 保存接收的数据大小
    unsigned short num = 0; // 保存数据包块编号
    int filefd = -1; // 初始化文件fd

    while (1) {
        bzero(buf, sizeof(buf));
        // 3.接收数据
        len = recvfrom(serfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &sin_size);
        if (-1 == len) {
            perror("recvfrom");
            return -1;
        }
        // 校验数据包的操作码是否==3
        if (3 == buf[1]) {
            // 校验数据包的块编号是否与本地记录一致
            if (*(unsigned short*)(buf + 2) == htons(num + 1)) {
                num++;
                // 校验数据包块编号是否==1
                if (1 == ntohs(*(unsigned short*)(buf + 2))) {
                    filefd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
                    if (-1 == filefd) {
                        perror("open");
                        return -1;
                    }
                }
                // 保存数据到本地
                if (-1 == write(filefd, buf + 4, len - 4)) {
                    perror("write");
                    close(filefd);
                    return -1;
                }

                // 回复ACK
                buf[1] = 4;
                if (-1 == sendto(serfd, buf, 4, 0, (struct sockaddr*)&sin, sin_size)) {
                    perror("recvfrom");
                    close(filefd);
                    return -1;
                }

                // 判断是否下载完毕
                if (len - 4 < 512) {
                    printf("%s下载完毕\n", filename);
                    break;
                }
            }
        } else if (5 == buf[1]) {
            fprintf(stderr, "DOWNLOAD_ERROR:%d : %s\n", ntohs(*(short*)(buf + 2)), buf + 4);
            return -1;
        }
    }
    close(filefd);
    return 0;
}

int uploading(int serfd, struct sockaddr_in sin)
{
    char buf[516] = { 0 }; // 数据包
    // 1.初始化请求上传包
    printf("input filename:");
    char filename[128] = "";
    scanf("%s", filename);
    short* opcode = (short*)buf;
    *opcode = htons(2);
    sprintf(buf + 2, "%s%c%s%c", filename, '\0', "octet", '\0');

    socklen_t sin_size = sizeof(sin); // 地址信息结构体大小
    // 2.发送上传请求
    if (-1 == sendto(serfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sin_size)) {
        perror("sendto");
        return -1;
    }

    ssize_t len = 0; // 保存接收的数据大小
    ssize_t size = 0; // 保存读取文件的数据大小
    unsigned short num = 0; // 保存数据包块编号
    int filefd = -1; // 初始化文件fd

    while (1) {
        bzero(buf, sizeof(buf));
        // 3.接收ACK
        len = recvfrom(serfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &sin_size);
        if (-1 == len) {
            perror("recvfrom");
            return -1;
        }
        // printf("操作码:%d  块编号:%d\n", buf[1], buf[3]);

        // 校验ACK的操作码是否==4
        if (4 == buf[1]) {
            // 校验ACK的块编号是否与本地记录一致
            if (*(unsigned short*)(buf + 2) == htons(num)) {
                num++;
                // 校验ACK块编号是否==0
                if (0 == ntohs(*(unsigned short*)(buf + 2))) {
                    filefd = open(filename, O_RDONLY); // 打开要上传的文件
                    if (-1 == filefd) {
                        perror("open");
                        continue;
                    }
                }
                // 读数据到buf
                size = read(filefd, buf + 4, sizeof(buf) - 4);
                if (-1 == size) {
                    perror("read");
                    close(filefd);
                    continue;
                } else if (0 == size) {
                    printf("%s上传完毕\n", filename);
                    break;
                }

                // 上传数据到服务器
                buf[1] = 3; // 设置操作码
                buf[3] = num; // 设置块编号
                if (-1 == sendto(serfd, buf, size + 4, 0, (struct sockaddr*)&sin, sin_size)) {
                    perror("recvfrom");
                    close(filefd);
                    return -1;
                }
            } else { // 如果服务器回复ACK块编号错误,将文件光标移动到上次的位置
                lseek(filefd, -size, SEEK_CUR);
                continue;
            }
        } else if (5 == buf[1]) {
            fprintf(stderr, "DOWNLOAD_ERROR:%d : %s\n", ntohs(*(short*)(buf + 2)), buf + 4);
            return -1;
        }
    }
    close(filefd);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CG Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值