目录
前言
本文采用 Visual Studio 2022 运用远程登陆,来对虚拟机内的 Linux 进行操作
背景
在TCP协议下,使用套接字(socket)多进程(fork)之间进行通信,并且客户端能够从服务器设定下的地址上获取文件。
分析
要分别从服务器和客户端两个方面入手,采取网络编程所固定的格式来进行编写
服务器:要不断的监听是否有客户端来进行通信,并且采用多进程的方式使得客户端之间互不影响
客户端:要可随时加入服务器,并且能够自主下载服务端下面的指定文件
编写步骤
服务器:
第一步:创建套接字
第二步:监听套接字
第三步:等待客户端连接
第四步:发送数据给客户端第五步:关闭套接字
客户端:
第一步:创建套接字
第二步:链接服务器
第三步:读写服务器
第四步:关闭套接字
服务器端代码
一、我们先做前期准备,先创建套接字(socket),并先确定其中的内容
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
struct sockaddr_in Ipv4 {}; //定义一个Ipv4的结构体信息
Ipv4.sin_family = AF_INET; //协议为IPV4
Ipv4.sin_addr.s_addr = 0; //Ip地址
Ipv4.sin_port = htons(PORT); //端口号 此处PORT为宏定义
二、创建完后,在对其进行绑定,并且开始监听
if (bind(tcp_sock, (struct sockaddr*)&Ipv4, sizeof(Ipv4)) < 0) //绑定
{
perror("bind");
return 1;
}
listen(tcp_sock, 10); //监听将主动套接字变为被动套接字 监听10路
三、创建 pollfd 类型的结构来确保我们使用 poll 函数
struct pollfd fds[SIZE]{}; // 将服务器加入到轮询表中
fds[index].fd = tcp_sock; // 有数据可读事件 */
四、准备工作已经做完了,就开始服务器的操作
while (true)
{
if (poll(fds, SIZE, 3000) == 0)
{
//printf("time out \n"); //检测
continue;
}
for (int i = 0; i < index + 1; i++)
{
if (tcp_sock == fds[i].fd) //主服务器
{
if (fds[i].revents & POLLIN)
{
int cid = accept(tcp_sock, NULL, NULL);
fds[index].fd = cid; //将服务器加入到轮询表中
fds[index].events = POLLIN; //有数据可读事件
index++;
printf("someone coming\n");
}
}
else //与客户端交互
{
if (fork() == 0) //多进程 子进程进行操作。避免出现孤儿进程
{
if (fds[i].revents & POLLIN)
{
memset(buf, 0, SIZE); //将buf的内容全部置为0
buf[SIZE - 1] = 1;
len = read(fds[i].fd, buf, SIZE); //读取数据
if (len > 0)
{
if (strcmp(buf, "file") == 0)
{
printf("sent file to %d\n", i);
dir(fds[i].fd); // 目录文件操作
read(fds[i].fd, buf, SIZE);
file(fds[i].fd, buf);
memset(buf, 0, SIZE);
}
if (buf[SIZE - 1] != 0)
{
printf("%d --- :%s\n", i, buf);
}
write(fds[i].fd, buf, len);
}
}
exit(-1);
}
}
waitpid(-1, NULL, WNOHANG);
}
}
附:文件操作部分
void file(int tcp_socket,char name[20])
{
data_t cmd{};
/**** 获取文件名 + 文件大小 *****/
struct stat stat_buf {};
if (stat(name, &stat_buf) != 0) //获取文件信息
{
perror("stat");
return;
}
cmd.file_len = stat_buf.st_size;
char* str = strrchr(name, '/');
if (str != NULL) //找到了
{
str += 1; //往后移动一位
}
else
{
str = name;
}
strcpy(cmd.file_name, str); //获取文件名
printf("file name:%s\n", cmd.file_name);
printf("file size:%d\n", cmd.file_len);
int fd = open(name, O_RDONLY);
if (fd < 0)
{
perror("open");
return;
}
write(tcp_socket, &cmd, sizeof(cmd)); //发送包头:文件名 + 文件大小
/***** 循环读取文件并发送 ****/
char buf[1024]{};
int len = 0;
while (cmd.file_len > 0)
{
len = read(fd, buf, 1024); //读文件
write(tcp_socket, buf, len); //写入
cmd.file_len -= len;
}
/****4.关闭套接字 *****/
close(fd);
}
附:目录操作部分
void dir(int cid)
{
DIR* dir = opendir("/home/student/projects/ProjectDemo/bin/x64/Debug"); // 打开一个目录
char buf[SIZE]{};
if (dir == NULL)
{
perror("opendir");
return ;
}
struct dirent* dnt;
while ((dnt = readdir(dir)) != NULL) // 只要返回结果不为NULL,就一直遍历
{
printf("%s\n", dnt->d_name);
strcpy(buf, dnt->d_name);
write(cid, buf, sizeof(buf));
}
strcpy(buf, "quitquit");
write(cid, buf, sizeof(buf));
closedir(dir);
}
客户端代码
客户端代码与服务器端代码大同小异,可直接参考服务器端来编写
int cid = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in IPV4 {};
IPV4.sin_family = AF_INET;
IPV4.sin_addr.s_addr = 0;
IPV4.sin_port = htons(PORT);
char name[20]{};
if (connect(cid, (struct sockaddr*)&IPV4, sizeof(IPV4)) < 0)
{
perror("conncet");
return 1;
}
char buf[128]{};
while (true)
{
printf("sent message to service:\n");
gets(buf);
write(cid, buf, sizeof(buf));
if (strncmp(buf, "file",4) == 0)
{
dir(cid);
printf("\n");
printf("receive a file is \n");
scanf("%s", name);
write(cid, name, sizeof(buf));
file(cid,name); //文件操作
}
if (strncmp(buf, "quit", 4) == 0)
{
break;
}
}
全部代码
在此,给出全部的代码以供大家学习
头文件部分
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <string.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <poll.h>
#include <sys/wait.h>
#include <dirent.h>
服务器全部代码
typedef struct
{
char file_name[20]; //文件名
int file_len; //文件大小
} data_t;
constexpr auto PORT = 10000;
constexpr auto IP = "192.168.248.128";
constexpr auto SIZE = 1024;
void dir(int cid)
{
DIR* dir = opendir("/home/student/projects/ProjectDemo/bin/x64/Debug"); // 打开一个目录
char buf[SIZE]{};
if (dir == NULL)
{
perror("opendir");
return ;
}
struct dirent* dnt;
while ((dnt = readdir(dir)) != NULL) // 只要返回结果不为NULL,就一直遍历
{
printf("%s\n", dnt->d_name);
strcpy(buf, dnt->d_name);
write(cid, buf, sizeof(buf));
}
strcpy(buf, "quitquit");
write(cid, buf, sizeof(buf));
closedir(dir);
}
void file(int tcp_socket,char name[20])
{
data_t cmd{};
/**** 获取文件名 + 文件大小 *****/
struct stat stat_buf {};
if (stat(name, &stat_buf) != 0) //获取文件信息
{
perror("stat");
return;
}
cmd.file_len = stat_buf.st_size;
char* str = strrchr(name, '/');
if (str != NULL) //找到了
{
str += 1; //往后移动一位
}
else
{
str = name;
}
strcpy(cmd.file_name, str); //获取文件名
printf("file name:%s\n", cmd.file_name);
printf("file size:%d\n", cmd.file_len);
int fd = open(name, O_RDONLY);
if (fd < 0)
{
perror("open");
return;
}
write(tcp_socket, &cmd, sizeof(cmd)); //发送包头:文件名 + 文件大小
/***** 循环读取文件并发送 ****/
char buf[1024]{};
int len = 0;
while (cmd.file_len > 0)
{
len = read(fd, buf, 1024); //读文件
write(tcp_socket, buf, len); //写入
cmd.file_len -= len;
}
/****4.关闭套接字 *****/
close(fd);
}
int main() //多路复用高并发
{
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in Ipv4 {};
Ipv4.sin_family = AF_INET;
Ipv4.sin_addr.s_addr = 0;
Ipv4.sin_port = htons(PORT);
if (bind(tcp_sock, (struct sockaddr*)&Ipv4, sizeof(Ipv4)) < 0)
{
perror("bind");
return 1;
}
listen(tcp_sock, 10);
int index = 0;
struct pollfd fds[SIZE]{};
/* 将服务器加入到轮询表中 */
fds[index].fd = tcp_sock;
/* 有数据可读事件 */
fds[index].events = POLLIN;
index++;
char buf[SIZE]{};
int len = 0;
while (true)
{
if (poll(fds, SIZE, 3000) == 0)
{
//printf("time out \n");
continue;
}
for (int i = 0; i < index + 1; i++)
{
if (tcp_sock == fds[i].fd)// 主服务器
{
if (fds[i].revents & POLLIN)
{
int cid = accept(tcp_sock, NULL, NULL);
fds[index].fd = cid; //将服务器加入到轮询表中
fds[index].events = POLLIN; //有数据可读事件
index++;
printf("someone coming\n");
}
}
else //与客户端交互
{
if (fork() == 0)
{
if (fds[i].revents & POLLIN)
{
memset(buf, 0, SIZE);
buf[SIZE - 1] = 1;
len = read(fds[i].fd, buf, SIZE);
if (len > 0)
{
if (strcmp(buf, "file") == 0)
{
printf("sent file to %d\n", i);
dir(fds[i].fd);
read(fds[i].fd, buf, SIZE);
file(fds[i].fd, buf);
memset(buf, 0, SIZE);
}
if (buf[SIZE - 1] != 0)
{
printf("%d --- :%s\n", i, buf);
}
write(fds[i].fd, buf, len);
}
}
exit(-1);
}
}
waitpid(-1, NULL, WNOHANG);
}
}
close(tcp_sock);
return 1;
}
客户端所有代码
constexpr auto PORT = 10000;
constexpr auto IP = "192.168.248.128";
constexpr auto SIZE = 1024;
typedef struct
{
char file_name[20]; //文件名
int file_len; //文件大小
} data_t;
void dir(int cid)
{
char buf[SIZE]{};
printf("you can get:\n");
read(cid, buf, sizeof(buf));
int i = 0;
while (strcmp(buf,"quitquit") != 0)
{
printf("%s ", buf);
read(cid, buf, sizeof(buf));
i++;
if (i == 5)
{
printf("\n");
i = 0;
}
}
}
void file(int cid,char name[20])
{
data_t cmd;
read(cid, &cmd, sizeof(cmd)); //接收文件名 + 大小
remove(name);
int fd = open(name, O_CREAT, 0666);
if (fd < 0)
{
perror("open");
return;
}
char* buf = (char*)malloc(1024);
int len = 0;
while (cmd.file_len > 0)
{
len = read(cid, buf, 1024);
if (len <= 0)
{
perror("read");
break;
}
cmd.file_len -= len;
write(fd, buf, len);
}
printf("over\n");
}
int main()//客户端
{
int cid = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in IPV4 {};
IPV4.sin_family = AF_INET;
IPV4.sin_addr.s_addr = 0; //ip地址
IPV4.sin_port = htons(PORT); //端口号
char name[20]{};
if (connect(cid, (struct sockaddr*)&IPV4, sizeof(IPV4)) < 0)
{
perror("conncet");
return 1;
}
char buf[128]{};
while (true)
{
printf("sent message to service:\n");
gets(buf);
write(cid, buf, sizeof(buf));
if (strncmp(buf, "file",4) == 0)
{
dir(cid);
printf("\n");
printf("receive a file is \n");
scanf("%s", name);
write(cid, name, sizeof(buf));
file(cid,name);
}
if (strncmp(buf, "quit", 4) == 0)
{
break;
}
}
close(cid);
return 1;
}
总结:
网络编程以固定的套路走,只要熟练了,按照一定的步骤走,网络编程就有迹可循。搞懂并不是一个特别困难的过程。加油!