一、基于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;
}