作业08.16

一、基于UDP的TFTP文件传输

        客户端实现:
        头文件:
#ifndef CLI_H
#define CLI_H
#include <myhead.h>

#define SER_PORT 69   // 服务器端口
#define SER_IP "192.168.191.1"// 服务器ip地址

// 封装请求包
int _apply(int cfd, int flag, char *filename);

// 下载:读取数据包写入本地文件,发送数据包
int _download(int cfd, char *filename);

// 上传:读取本地文件写入数据包,发送数据包
int _upload(int cfd, char *filename);

#endif
        函数定义:
#include "cli.h"

char buf[516] = ""; // 数据包容器

// 封装请求包
int _apply(int cfd, int flag, char *filename)
{
    short *p1 = buf;    // 操作码
    *p1 = htons(flag);
    
    char *p2 = buf+2;   // 文件名
    strcpy(p2, filename);

    char *p4 = p2+strlen(p2)+1; // 模式位
    strcpy(p4, "octet");

    int size = 2+strlen(p2)+strlen(p4)+2;   // 请求包的总长度

    // 封装服务器地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);

    // 将请求包发送给服务器
    sendto(cfd, buf, size, 0, (struct sockaddr *)&sin, sizeof(sin));

    return 0;
}

// 下载:读取数据包写入文件
int _download(int cfd, char *filename)
{
    // 封装服务器地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);
    int size = sizeof(sin);

    int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0664);   // 以w的形式打开文件
    if(fd == -1)
    {
        perror("open error");
        return -1;
    }

    while(1)
    {
        
        int res = recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &size);  // 从服务器接收数据包

        short *opcode = (short *)buf;   // 操作码
        short *block_num = (short *)(buf + 2);// 块编号
        
        if(ntohs(*opcode) == 5) // 错误处理
        {
            fprintf(stderr, "服务器错误:%s\n", buf+4);
            close(fd);
            return -1;
        }

        if(ntohs(*opcode) != 3) // 检查操作码是否为3
        {
            fprintf(stderr, "收到非数据包\n");
            close(fd);
            return -1;
        }

        int data_size = res - 4;  // 实际数据大小
        write(fd, buf + 4, data_size);

        // 发送ACK
        uint16_t ack[2] = {htons(4), *block_num};
        sendto(cfd, ack, sizeof(ack), 0, (struct sockaddr *)&sin, sizeof(sin));

        if(res < 516)   // 如果读取数据包大小不足516,说明下载完成
        {
            break;
        }
    }
    printf("下载完成\n");

    close(fd);

    return 0;
}

// 上传:读取本地文件写入数据包,发送数据包
int _upload(int cfd, char *filename)
{
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);
    socklen_t addr_len = sizeof(sin);

    int fd = open(filename, O_RDONLY);
    if(fd == -1)
    {
        perror("打开文件失败");
        return -1;
    }

    short block_num = 0;    // 跟踪块编码

    while(1)
    {
        // 等待ACK
        bzero(buf, sizeof(buf));
        if(recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &addr_len) == -1)
        {
            perror("接收ACK失败");
            close(fd);
            return -1;
        }

        short *opcode = (short *)buf;   // 操作码
        short *pblock_num = (short *)(buf + 2);// 块编号

        if(ntohs(*opcode) == 5) // 错误处理
        {
            fprintf(stderr, "服务器错误:%s\n", buf+4);
            close(fd);
            return -1;
        }

        if(ntohs(*opcode) != 4 && ntohs(*pblock_num) != block_num)
        {
            close(fd);
            return -1;
        }

        // 准备发送下一个数据包
        bzero(buf, sizeof(buf));
        opcode = (short *)buf;
        pblock_num = (short *)(buf + 2);
        
        *opcode = htons(3);  // 数据包操作码
        *pblock_num = htons(++block_num);
        
        int res = read(fd, buf + 4, 512);

        // 发送数据包
        if(sendto(cfd, buf, res + 4, 0, (struct sockaddr *)&sin, addr_len) == -1)
        {
            perror("发送数据包失败");
            close(fd);
            return -1;
        }

        if(res < 512) // 如果读取的数据少于512字节,说明文件传输完成
        {
            break;
        }
    }

    // 等待最后一个数据包的ACK
    bzero(buf, sizeof(buf));
    if(recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &addr_len) == -1)
    {
        perror("接收最后的ACK失败");
        close(fd);
        return -1;
    }

    printf("上传完成\n");
    close(fd);
    return 0;
}
        主函数:
#include "cli.h"

int main(int argc, char const *argv[])
{
    // 创建用于通信的套接字文件描述符
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(cfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("socket success\n");

    int num = 0;
    while(1)
    {
        printf("\t======================\n");
        printf("\t----->>>1.下载\n");
        printf("\t----->>>2.上传\n");
        printf("\t----->>>3.退出\n");
        printf("\t======================\n");

        printf("请输入>>>");
        scanf("%d", &num);
        getchar();

        char filename[20] = "";
        if(num == 1 || num == 2)
        {
            printf("操作文件>>>");
            fgets(filename, sizeof(filename), stdin);
            filename[strlen(filename)-1] = 0;
        }

        switch (num)
        {
        case 1:
            _apply(cfd, 1, filename);   // 操作码1,下载请求
            _download(cfd, filename);
            break;
        case 2:
            _apply(cfd, 2, filename);   // 操作码2,上传请求
            _upload(cfd, filename);
            break;
        case 3:
            close(cfd);
            exit(0);
            break;
        default:
            printf("错误输入\n");
            break;
        }

        printf("按任意键继续......\n");
        getchar();
        system("clear");
    }
    
    return 0;
}

 二、基于UDP的网络聊天室

        服务器:
        头文件:
#ifndef UDPCHATSER_H
#define UDPCHATSER_H
#include <myhead.h>

#define SER_PORT 6666   // 服务器端口
#define SER_IP "192.168.191.129"// 服务器ip地址

// 用户信息结构体
struct Info
{
    char usrName[20];   // 用户名
    struct sockaddr_in cin; // 用户地址信息
};

// 存储用户信息链表
typedef struct Node 
{
    union
    {
        int len;    // 链表长度
        struct Info usrInfo;
    };
    struct Node * next;   // 指针域
}Node, *NodePtr;

// 用户发送消息类型
struct msgType
{
    char type;  // 消息类型, L:登录、C:聊天、Q:下线
    char usrName[20];   // 用户名
    char msgText[1024]; // 消息正文
};

// 线程间通信结构体
struct pthread_buf
{
    struct Node * L;
    int sfd;
};

// 定义无名信号量实现线程同步
sem_t sem_rcv, sem_snd;

struct sockaddr_in cin; // 客户端地址信息容器
struct msgType msgbuf;// 定义存储消息的容器

// 创建用户信息链表
NodePtr list_create();

// 申请节点封装用户信息链表数据
NodePtr apply_list(char *usrName, struct sockaddr_in cin);

// 用户信息链表判空
int list_empty(NodePtr L);

// 插入用户信息
int list_add(NodePtr L, char *usrName, struct sockaddr_in cin);

// 删除用户信息
int list_delete(NodePtr L, struct sockaddr_in cin);

// 通过地址信息查找返回要删除节点的前一个节点
NodePtr list_search(NodePtr L, struct sockaddr_in cin);

// 接收线程
void *task_rcv(void *arg);

// 发送线程
void *task_snd(void *arg);

#endif
        函数定义:
#include "udpChatSer.h"

// 创建用户信息链表
NodePtr list_create()
{
    NodePtr L = (NodePtr)malloc(sizeof(Node));  // 堆区申请头节点内存
    if(NULL == L)
    {
        printf("创建失败\n");
        return NULL;
    }

    // 初始化头节点
    L->next = NULL;
    L->len = 0;

    return L;
}

// 申请节点封装用户信息链表数据
NodePtr apply_list(char *usrName, struct sockaddr_in cin)
{
    NodePtr p = (NodePtr)malloc(sizeof(Node));  // 堆区申请一个节点的空间
    if(NULL == p)
    {
        printf("申请失败\n");
        return NULL;
    }

    // 初始化节点
    p->next = NULL;
    strcpy(p->usrInfo.usrName, usrName);
    p->usrInfo.cin = cin;

    return p;
}

// 用户信息链表判空
int list_empty(NodePtr L)
{
    return NULL == L->next;
}

// 插入用户信息
int list_add(NodePtr L, char *usrName, struct sockaddr_in cin)
{
    if(NULL == L)
    {
        printf("插入失败\n");
        return -1;
    }

    NodePtr p = apply_list(usrName, cin);

    // 头插
    p->next = L->next;
    L->next = p;
    L->len++;

    return 0;
}

// 删除用户信息
int list_delete(NodePtr L, struct sockaddr_in cin)
{
    if(NULL == L || list_empty(L))
    {
        printf("删除失败\n");
        return -1;
    }

    // 通过地址信息查找返回要删除节点的前一个节点
    NodePtr p = list_search(L, cin);

    NodePtr q = p->next;
    p->next = q->next;
    free(q);
    q = NULL;

    L->len--;

    return 0;
}

// 通过地址信息查找返回要删除节点的前一个节点
NodePtr list_search(NodePtr L, struct sockaddr_in cin)
{
    if(NULL == L || list_empty(L))
    {
        printf("查找失败\n");
        return NULL;
    }

    NodePtr p = L;
    while(p->next != NULL)
    {
        // 判断信息结构体是否相同
        if(p->next->usrInfo.cin.sin_addr.s_addr == cin.sin_addr.s_addr\
            && p->next->usrInfo.cin.sin_family == cin.sin_family\
            && p->next->usrInfo.cin.sin_port == cin.sin_port)
        {
            // 返回的是要删除的节点的上一个节点
            return p;
        }
        p = p->next;
    }

    return NULL;
}

// 接收线程
void *task_rcv(void *arg)
{
    struct pthread_buf * tidbuf;
    tidbuf = (struct pthread_buf *)arg;
    int sfd = tidbuf->sfd;
    NodePtr L = tidbuf->L;

    int size = sizeof(cin);
    while(1)
    {
        // 申请无名信号量
        sem_wait(&sem_rcv);

        // 接收客户端传过来的数据
        recvfrom(sfd, &msgbuf, sizeof(msgbuf), 0, (struct sockaddr *)&cin, &size);

        // 对消息类型进行判断
        if(msgbuf.type == 'L')  // 登录
        {
            // 用户地址信息链表更新
            list_add(L, msgbuf.usrName, cin);
            printf("<%s[%s:%d]>已登录\n", msgbuf.usrName, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
            // 释放无名信号量
            sem_post(&sem_snd);
        }
        else if(msgbuf.type == 'C') // 聊天
        {
            printf("%s[%s:%d] : %s\n", msgbuf.usrName, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), msgbuf.msgText);
            // 释放无名信号量
            sem_post(&sem_snd);
        }
        else if(msgbuf.type == 'Q') // 退出
        {
            // 用户地址信息链表更新
            list_delete(L,cin);
            printf("<%s[%s:%d]>已离线\n", msgbuf.usrName, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
            // 释放无名信号量
            sem_post(&sem_snd);
        }
    }

    exit(EXIT_SUCCESS);
}

// 发送线程
void *task_snd(void *arg)
{
    struct pthread_buf * tidbuf;
    tidbuf = (struct pthread_buf *)arg;
    int sfd = tidbuf->sfd;
    NodePtr L = tidbuf->L;

    // 为套接字绑定ip地址和端口号  
    struct sockaddr_in sin; // 服务器地址信息容器    
    sin.sin_family = AF_INET;       //通信域
    sin.sin_port = htons(SER_PORT);    //端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);    //ip地址

    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
    {
        perror("bind error");
    }

    while(1)
    {
        // 申请无名信号量
        sem_wait(&sem_snd);

        // 向所有用户发送相同类型消息
        NodePtr p = L->next;
        while(p != NULL)
        {
            if(p->usrInfo.cin.sin_addr.s_addr != cin.sin_addr.s_addr\
                || p->usrInfo.cin.sin_port != cin.sin_port)
            {
                sendto(sfd, &msgbuf, sizeof(msgbuf), 0, (struct sockaddr *)&p->usrInfo.cin, sizeof(p->usrInfo.cin));
            }
            p = p->next;
        }

        // 释放无名信号量
        sem_post(&sem_rcv);
    }
    
    exit(EXIT_SUCCESS);
}
        主函数:
#include "udpChatSer.h"

int main(int argc, char const *argv[])
{
    // 创建用于通信的套接字文件描述符
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        perror("socket error");
        return -1;
    }

    // 创建用户地址信息链表
    NodePtr L = list_create();
    if(NULL == L)
    {
        return -1;
    }

    pthread_t tid_rcv = -1; // 接收线程
    pthread_t tid_snd = -1; // 发送线程
    struct pthread_buf tidbuf = {L, sfd};

    // 初始化无名信号量
    sem_init(&sem_rcv, 0, 1);
    sem_init(&sem_snd, 0, 0);

    // 创建线程完成 接收消息
    if(pthread_create(&tid_rcv, NULL, task_rcv, &tidbuf) != 0)
    {
        printf("tid_rcv error\n");
        return -1;
    }

    // 创建线程完成 发送消息
    if(pthread_create(&tid_snd, NULL, task_snd, &tidbuf) != 0)
    {
        printf("tid_snd error\n");
        return -1;
    }

    // 设置分支线程分离态
    pthread_detach(tid_rcv);
    pthread_detach(tid_snd);

    // 主线程,系统处理
    struct msgType sys_msgbuf;
    while(1)
    {
        sys_msgbuf.type = 'C';
        strcpy(sys_msgbuf.usrName, "system");

        fgets(sys_msgbuf.msgText, sizeof(sys_msgbuf.msgText), stdin);
        sys_msgbuf.msgText[strlen(sys_msgbuf.msgText)-1] = 0;
        if(strcmp(sys_msgbuf.msgText, "quit") == 0)
        {
            break;
        }

        // 向所有用户发送消息
        NodePtr p = L->next;
        while(p != NULL)
        {
            sendto(sfd, &sys_msgbuf, sizeof(sys_msgbuf), 0, (struct sockaddr *)&p->usrInfo.cin, sizeof(p->usrInfo.cin));
            p = p->next;
        }

        bzero(sys_msgbuf.msgText, sizeof(sys_msgbuf.msgText));
    }
    
    // 销毁无名信号量
    sem_destroy(&sem_rcv);
    sem_destroy(&sem_snd);

    return 0;
}

// 
        客户端:
        头文件:
#ifndef UDPCHATCLI_H
#define UDPCHATCLI_H
#include <myhead.h>

#define SER_PORT 6666   // 服务器端口
#define SER_IP "192.168.191.129"// 服务器ip地址

// 用户发送消息类型
struct msgType
{
    char type;  // 消息类型, L:登录、C:聊天、Q:下线
    char usrName[20];   // 用户名
    char msgText[1024]; // 消息正文
};

// 接收线程
void *task_rcv(void *arg);

#endif
        函数定义:
#include "udpChatCli.h"

// 接收线程
void *task_rcv(void *arg)
{
    int cfd = *(int *)arg;

    // 填充服务器地址信息结构体
    struct sockaddr_in sin; // 服务器地址信息容器    
    sin.sin_family = AF_INET;       //通信域
    sin.sin_port = htons(SER_PORT);    //端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);    //ip地址
    int size = sizeof(sin);

    struct msgType msgbuf;
    while(1)
    {
        // 接收服务器发送的消息
        recvfrom(cfd, &msgbuf, sizeof(msgbuf), 0, (struct sockaddr *)&sin, &size);
        // 对消息类型进行判断
        if(msgbuf.type == 'L')
        {
            printf("------<%s>已登录------\n", msgbuf.usrName);
        }
        else if(msgbuf.type == 'C')
        {
            printf("<%s> %s\n", msgbuf.usrName, msgbuf.msgText);
        }
        else if(msgbuf.type == 'Q')
        {
            printf("------<%s>已离线------\n", msgbuf.usrName);
        }
    }
}
        主函数:
#include "udpChatCli.h"

int main(int argc, char const *argv[])
{
    // 创建用于通信的套接字文件描述符
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(cfd == -1)
    {
        perror("socket error");
        return -1;
    }

    // 填充服务器地址信息结构体
    struct sockaddr_in sin; // 服务器地址信息容器    
    sin.sin_family = AF_INET;       //通信域
    sin.sin_port = htons(SER_PORT);    //端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);    //ip地址

    // 向服务器发送登录请求
    struct msgType msgbuf;
    msgbuf.type = 'L';
    printf("请输入usrName>>>");
    scanf("%s", msgbuf.usrName);
    getchar();
    sendto(cfd, &msgbuf, sizeof(msgbuf), 0, (struct sockaddr *)&sin, sizeof(sin));

    pthread_t tid_rcv = -1; // 接收线程
    // 创建线程完成 接收消息
    if(pthread_create(&tid_rcv, NULL, task_rcv, &cfd) != 0)
    {
        printf("tid_rcv error\n");
        return -1;
    }
    pthread_detach(tid_rcv);

    // 主线程
    msgbuf.type = 'C';
    while(1)
    {
        fgets(msgbuf.msgText, sizeof(msgbuf.msgText), stdin);
        msgbuf.msgText[strlen(msgbuf.msgText)-1] = 0;
        if(strcmp(msgbuf.msgText, "quit") == 0)
        {
            msgbuf.type = 'Q';
            sendto(cfd, &msgbuf, sizeof(msgbuf), 0, (struct sockaddr *)&sin, sizeof(sin));
            break;
        }
        sendto(cfd, &msgbuf, sizeof(msgbuf), 0, (struct sockaddr *)&sin, sizeof(sin));
    }

    printf("--------------------------\n");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值