小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1 实现客户端下载服务器所在目录文件
客户端发送要下载的文件名给服务器,服务器判断文件是否存在,将结果告知客户端如果文件存在,服务器读取文件内容并发送给客户端,客户端接收文件内容并写入指定的文件。
1.1 服务器代码
//TCP网络编程之服务器
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
//使用不需要定义,__FILE__指示当前文件名__func__指示当前函数名__LINE__指示运行当前文件的行数
//exit(1)异常退出
#define N 128
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
typedef struct{
int n; //文件内容的大小
char text[N]; //文件内容
}MSG;
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd, acceptfd;
struct sockaddr_in serveraddr, clientaddr;
socklen_t addrlen = sizeof(serveraddr);
char buf[N] = {0};
ssize_t bytes;
int fd;
ssize_t num;
MSG msg;
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
//inet_addr:将点分十进制ip地址转换为网络字节序的无符号4字节整数
//atoi:将数字型字符串转换为整形数据
//htons:将主机字节序转化为网络字节序
serveraddr.sin_family = AF_INET;
//注意:ip地址不能随便写,服务器在那个主机中运行,ip地址就是这个主机的
//如果是自己主机的客户端服务器测试,可以使用127网段的
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:将套接字与服务器网络信息结构体绑定
if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("bind error");
}
//第四步:将套接字设置为被动监听状态
if(listen(sockfd, 5) == -1)
{
ERRLOG("listen error");
}
NEXT:
//第五步:阻塞等待客户端的连接
if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) == -1)
{
ERRLOG("accept error");
}
//打印客户端的信息
printf("客户端%s:%d连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
while(1)
{
RECV:
//服务器接收客户端发送的内容
if((bytes = recv(acceptfd, buf, N, 0)) == -1)
{
ERRLOG("recv error");
}
else if(bytes == 0)
{
printf("客户端%s-%d退出了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
goto NEXT;
}
//printf("filename: %s\n", buf);
//判断文件是否存在
if((fd = open(buf, O_RDONLY)) == -1)
{
//通过errno来获取文件不存在的错误码,将其单独操作
//printf("errno = %d\n", errno);
if(errno == ENOENT)
{
//如果文件不存在,服务器告知客户端
if(send(acceptfd, "**NOEXIST**", N, 0) == -1)
{
ERRLOG("send error");
}
goto RECV;
}
else
{
ERRLOG("open error");
}
}
//如果文件存在也要告知客户端
if(send(acceptfd, "**EXIST**", N, 0) == -1)
{
ERRLOG("send error");
}
//TCP数据粘包问题
//TCP底层有一个Nagel算法,会将一段时间内连续发送的内容组成
//一个整体,然后再将其发送给接收方,但是接收方接收数据时没有
//办法区分数据的类型,所以可能会将不同类型的数据一次性接收到,
//代码就会出现冲突
//处理方法1:将不能类型的数据通过延时处理,不将其放在一个数据包中发送
#if 0
//读取文件内容并发送给客户端
while((num = read(fd, buf, N)) != 0)
{
if(send(acceptfd, buf, num, 0) == -1)
{
ERRLOG("send error");
}
}
sleep(1);
//发送结束标志,告知客户端文件内容发送完毕
if(send(acceptfd, "**OVER**", N, 0) == -1)
{
ERRLOG("send error");
}
#endif
//处理方法2:只要保证每次发送的数据包都一样大,就不会出现这个问题,
//定义一个结构体,发送和接收结构体,这样保证每一个单独的数据包都一样大
while((num = read(fd, buf, N)) != 0)
{
msg.n = num;
strcpy(msg.text, buf);
if(send(acceptfd, &msg, sizeof(msg), 0) == -1)
{
ERRLOG("send error");
}
}
//发送结束标志,告知客户端文件内容发送完毕
msg.n = 0;
strcpy(msg.text, "**OVER**");
if(send(acceptfd, &msg, sizeof(msg), 0) == -1)
{
ERRLOG("send error");
}
printf("文件发送完毕\n");
}
return 0;
}
1.2 客户端
//TCP网络编程之客户端
#include <stdio.h> //atoi
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>//字节序转换
#include <netinet/in.h>//ip
#include <sys/socket.h>//socket
#include <sys/types.h> //API接口//socket
#include <sys/stat.h>
#include <fcntl.h>
#define N 128
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
//文件结构体
typedef struct{
int n; //文件内容的大小
char text[N]; //文件内容
}MSG;
int main(int argc, char const *argv[])
{
if(argc < 3)//判断终端输入的数量
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
/* struct sockaddr_in {
sa_family_t sin_family; //address family: AF_INET (协议族:哪种协议方式)
in_port_t sin_port; //port in network byte order (端口网络字节序)
struct in_addr sin_addr; //internet address
};
//Internet address
struct in_addr {
uint32_t s_addr; //address in network byte order (地址网络字节序)
}; */
int sockfd;//定义文件描述符
struct sockaddr_in serveraddr;//网络信息结构体
socklen_t addrlen = sizeof(serveraddr);//网络信息结构体的大小
char buf[N] = {0};//存放数据
char filename[N] = {0};//文件名
int fd;//文件描述符
MSG msg;//文件结构体
//第一步:创建套接字,协议族,套接字类型,附加协议
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;//IPV4
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//inet_addr:将点分十进制ip地址转换为网络字节序的无符号4字节整数
serveraddr.sin_port = htons(atoi(argv[2]));//atoi:将数字型字符串转换为整形数据
//htons:将主机字节序转化为网络字节序
//第三步:给服务器发送客户端的连接请求
if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("connect error");
}
while(1)
{
PRINTFILE:
//客户端输入文件名并发送给服务器
printf("请输入要下载的文件名: ");
fgets(filename, N, stdin);
filename[strlen(filename) - 1] = '\0';
if(send(sockfd, filename, N, 0) == -1)
{
ERRLOG("send error");
}
//接收服务器发送的数据并做出相应的处理
if(recv(sockfd, buf, N, 0) == -1)
{
ERRLOG("recv error");
}
//如果文件不存在,则重新输入
if(strcmp(buf, "**NOEXIST**") == 0)
{
printf("文件%s不存在,请重新输入!!!\n", filename);
goto PRINTFILE;
}
//如果文件存在,创建或者打开文件
if((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1)
{
ERRLOG("open error");
}
//接收服务器发送的数据并写入到文件中
#if 0
while((num = recv(sockfd, buf, N, 0)) != -1)
{
if(strcmp(buf, "**OVER**") == 0)
{
break;
}
write(fd, buf, num);
}
#endif
while(recv(sockfd, &msg, sizeof(msg), 0) != -1)
{
if(msg.n > 0)
{
write(fd, msg.text, msg.n);
}
else if(msg.n == 0)
{
break;
}
}
printf("文件下载完毕\n");
}
return 0;
}
2 练习:机械臂控制
2.1 机械臂操作介绍
机械臂是由QT编写的TCP服务器代码,在哪运行,服务器ip地址就当前系统的地址,端口号任意设置机械臂协议构成:0xff 0x02 (1) (2) 0xff (1) 机械臂的摆臂 0x00 红色摆臂 0x01 蓝色摆臂 (2) 偏移值
注意:Qt机械臂程序的ip地址是你windows的ip地址,端口号可以任意设置
2.2 代码控制机械臂(demo)
//TCP网络编程之客户端
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#define N 5
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:给服务器发送客户端的连接请求
if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("connect error");
}
//进行通信
char buf[N] = {0xff, 2, 1, 90, 0xff};
if(send(sockfd, buf, N, 0) == -1)
{
ERRLOG("send error");
}
return 0;
}
2.3 使用按键wasd控制机械臂(demo)
//TCP网络编程之客户端
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#define N 5
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:给服务器发送客户端的连接请求
if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("connect error");
}
char buf_red[N] = {0xff, 2, 0, 0, 0xff};
unsigned char buf_blue[N] = {0xff, 2, 1, 90, 0xff};
if(send(sockfd, buf_red, N, 0) == -1)
{
ERRLOG("send error");
}
if(send(sockfd, buf_blue, N, 0) == -1)
{
ERRLOG("send error");
}
char ch;
while(1)
{
ch = getchar();
getchar();
switch(ch)
{
case 'a':
if(buf_red[3] != -90)
{
buf_red[3] -= 5;
}
if(send(sockfd, buf_red, N, 0) == -1)
{
ERRLOG("send error");
}
break;
case 'd':
if(buf_red[3] != 90)
{
buf_red[3] += 5;
}
if(send(sockfd, buf_red, N, 0) == -1)
{
ERRLOG("send error");
}
break;
case 'w':
if(buf_blue[3] != 0)
{
buf_blue[3] -= 5;
}
if(send(sockfd, buf_blue, N, 0) == -1)
{
ERRLOG("send error");
}
break;
case 's':
if(buf_blue[3] != 180)
{
buf_blue[3] += 5;
}
if(send(sockfd, buf_blue, N, 0) == -1)
{
ERRLOG("send error");
}
break;
}
}
return 0;
}
2.4 linux键盘编程(demo)
驱动工程师编写键盘驱动,会在/dev/input目录下生成一个event0-event4字符设备文件,当按下键盘上的某一个按键时,会往设备文件中写内容,所以我们需要从设备文件中读取内容并解析,然后找到想要使用的按键,首先测试自己的键盘驱动是哪一个文件:sudo cat event0-4 我们发现读取到的内容时二进制的,所以需要查询资料找到读取的内容到底是什么,通过查询资料我们得知,读取到的内容是linux/input.h文件中的一个结构体
struct input_event {
struct timeval time;
__u16 type; 类型
EV_KEY 键盘
__u16 code;
__s32 value; 按键事件的类型值
1 按键按下
0 按键松开
2 按键一直按下
};
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <linux/input.h>
#define DEVFILE "/dev/input/event1"
int main(int argc, char const *argv[])
{
//打开键盘对应的设备文件
int fd;
if((fd = open(DEVFILE, O_RDONLY)) == -1)
{
perror("open error");
exit(1);
}
//读取设备文件的内容并解析
struct input_event mykey;
while(1)
{
read(fd, &mykey, sizeof(mykey));
if(mykey.type == EV_KEY)
{
if(mykey.value == 1)
{
printf("%d按键按下\n", mykey.code);
}
else if(mykey.value == 0)
{
printf("%d按键松开\n", mykey.code);
}
else if(mykey.value == 2)
{
printf("%d按键一直按下\n", mykey.code);
}
//printf("type:%d, value:%d, code:%d\n", mykey.type, mykey.value, mykey.code);
}
}
return 0;
}
2.5 使用按键wasd控制机械臂(高级版)(demo)
//TCP网络编程之客户端
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <linux/input.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define DEVFILE "/dev/input/event1"
#define N 5
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:给服务器发送客户端的连接请求
if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("connect error");
}
char buf_red[N] = {0xff, 2, 0, 0, 0xff};
unsigned char buf_blue[N] = {0xff, 2, 1, 90, 0xff};
if(send(sockfd, buf_red, N, 0) == -1)
{
ERRLOG("send error");
}
if(send(sockfd, buf_blue, N, 0) == -1)
{
ERRLOG("send error");
}
char ch;
int fd;
if((fd = open(DEVFILE, O_RDONLY)) == -1)
{
perror("open error");
exit(1);
}
struct input_event mykey;
while(1)
{
read(fd, &mykey, sizeof(mykey));
if(mykey.type == EV_KEY && mykey.value == 1)
{
switch(mykey.code)
{
case 30: //a
if(buf_red[3] != -90)
{
buf_red[3] -= 5;
}
if(send(sockfd, buf_red, N, 0) == -1)
{
ERRLOG("send error");
}
break;
case 32: //d
if(buf_red[3] != 90)
{
buf_red[3] += 5;
}
if(send(sockfd, buf_red, N, 0) == -1)
{
ERRLOG("send error");
}
break;
case 17: //w
if(buf_blue[3] != 0)
{
buf_blue[3] -= 5;
}
if(send(sockfd, buf_blue, N, 0) == -1)
{
ERRLOG("send error");
}
break;
case 31: //s
if(buf_blue[3] != 180)
{
buf_blue[3] += 5;
}
if(send(sockfd, buf_blue, N, 0) == -1)
{
ERRLOG("send error");
}
break;
}
}
}
return 0;
}
3 实现TFTP客户端的下载功能
3.1客户端实现上传和下载
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
#define N 128
void do_help()
{
system("clear");
printf("---------------------\n");
printf("------ 1. 下载 ------\n");
printf("------ 2. 上传 ------\n");
printf("------ 3. 退出 ------\n");
printf("---------------------\n");
}
void do_download(int sockfd, struct sockaddr_in serveraddr)
{
char filename[N] = {};
printf("请输入要下载的文件名:");
scanf("%s", filename);
char data[1024] = "";
int data_len;
int fd;
int flags = 0;
int num = 0;
int recv_len;
//组数据并发送
data_len = sprintf(data, "%c%c%s%c%s%c", 0, 1, filename, 0, "octet", 0);
if(sendto(sockfd, data, data_len, 0, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
ERRLOG("fail to sendto");
}
//接收数据并分析处理
socklen_t addrlen = sizeof(serveraddr);
while(1)
{
if((recv_len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr *)&serveraddr, &addrlen)) < 0)
{
ERRLOG("fail to recvfrom");
}
//printf("%d - %u\n", data[1], ntohs(*(unsigned short *)(data + 2)));
//printf("%s\n", data + 4);
if(data[1] == 5)
{
printf("error: %s\n", data + 4);
return ;
}
else if(data[1] == 3)
{
//防止文件内容清空
if(flags == 0)
{
if((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664)) < 0)
{
ERRLOG("fail to open");
}
flags = 1;
}
//判断数据包的编号是否是上一次的编号加1
if(num + 1 == ntohs(*(unsigned short *)(data + 2)) && recv_len == 516)
{
//向文件写入数据
write(fd, data + 4, recv_len - 4);
//组数据发送给服务器
data[1] = 4;
if(sendto(sockfd, data, 4, 0, (struct sockaddr *)&serveraddr, addrlen) < 0)
{
ERRLOG("fail to sendto");
}
num = ntohs(*(unsigned short *)(data + 2));
}
//接收到的最后一次的数据
else if(num + 1 == ntohs(*(unsigned short *)(data + 2)) && recv_len < 516)
{
write(fd, data + 4, recv_len - 4);
break;
}
}
}
printf("文件下载成功\n");
}
void do_upload(int sockfd, struct sockaddr_in serveraddr)
{
char filename[N] = {};
printf("请输入要上传的文件名:");
scanf("%s", filename);
//打开文件并判断文件是否存在
int fd;
if((fd = open(filename, O_RDONLY)) < 0)
{
if(errno == ENOENT)
{
printf("文件%s不存在,请重新输入\n", filename);
return ;
}
else
{
ERRLOG("fail to open");
}
}
//组数据并发送给服务器执行上传功能
char data[1024] = {};
int data_len;
socklen_t addrlen = sizeof(serveraddr);
data_len = sprintf(data, "%c%c%s%c%s%c", 0, 2, filename, 0, "octet", 0);
if(sendto(sockfd, data, data_len, 0, (struct sockaddr *)&serveraddr, addrlen) < 0)
{
ERRLOG("fail to sendto");
}
//接收服务器发送的数据并分析处理
int recv_len;
int num = 0;
ssize_t bytes;
while(1)
{
if((recv_len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr *)&serveraddr, &addrlen)) < 0)
{
ERRLOG("fail to recvfrom");
}
//printf("%d - %d\n", data[1], ntohs(*(unsigned short *)(data + 2)));
//printf("%s\n", data + 4);
if(data[1] == 4 && num == ntohs(*(unsigned short *)(data + 2)))
{
num++;
bytes = read(fd, data + 4, 512);
data[1] = 3;
*(unsigned short *)(data + 2) = htons(num);
if(bytes == 512)
{
if(sendto(sockfd, data, bytes + 4, 0, (struct sockaddr *)&serveraddr, addrlen) < 0)
{
ERRLOG("fail to sendto");
}
}
else
{
if(sendto(sockfd, data, bytes + 4, 0, (struct sockaddr *)&serveraddr, addrlen) < 0)
{
ERRLOG("fail to sendto");
}
break;
}
}
}
printf("文件上传完毕\n");
}
int main(int argc, char const *argv[])
{
int sockfd;
struct sockaddr_in serveraddr;
//创建套接字
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
ERRLOG("fail to socket");
}
//填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(69);
system("clear");
printf("------------------------------\n");
printf("----请输入help查看帮助信息----\n");
printf("------------------------------\n");
printf(">>> ");
char buf[N] = {};
NEXT:
fgets(buf, N, stdin);
buf[strlen(buf) - 1] = '\0';
if(strncmp(buf, "help", 4) == 0)
{
do_help();
}
else
{
printf("您输入的有误,请重新输入\n");
goto NEXT;
}
int num;
while(1)
{
printf("input>>> ");
scanf("%d", &num);
switch (num)
{
case 1:
do_download(sockfd, serveraddr);
break;
case 2:
do_upload(sockfd, serveraddr);
break;
case 3:
close(sockfd);
exit(0);
break;
default:
printf("您输入的有误,请重新输入\n");
break;
}
}
return 0;
}