Linux操作系统实验三:网络编程及文件操作
题目:
【实验目的】
了解和熟悉 Linux 环境下基于套接口的通信方式。本地进程间通信方式局限在单一计算机内,基于套接口的通讯方式不仅可以实现单机内的进程间通信,还可以实现不同计算机进程之间的通信。文件操作是 Linux 系统中最常见的操作之一,熟悉 Linux 的文件系统及其操作的相关系统调用,了解和掌握基于文件描述符和基于数据流的文件I/O 操作方式。
【实验预备内容】
(1) 阅读 Linux 下网络编程和文件 I/O 操作等相关章节的参考资料。
(2) 重点学习网络编程模式和基于数据流的文件 I/O 操作
【实验内容】
阅读参考资料,分析示例程序代码,用 C 语言编程实现以下要求。
- 使用套接口通信机制实现客户端进程与服务器端进程之间信息的发送接收;
- 并利用文件 I/O 操作读取及保存信息的发送接收。
学号:021900208 姓名:高旭 专业:计算机科学与技术 班级:02
一、实验环境:
Oracle VM VirtualBox、Ubuntu(64-bit)
二、实验内容:
代码部分
ex3.h
// ex3.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <signal.h>
#include <netdb.h>
#define ARFILE "./usr.ar" // 指定档案文件的路径名称
struct arstruct // 员工资料结构
{
char name[10];
int age;
char tele[21];
};
// 删除员工函数
int removeuser(char *name)
{
struct arstruct ar;
FILE *fp;
FILE *fpn;
if((fpn = fopen("./tmpfile","w")) == NULL)
{
return 0;
}
if((fp = fopen(ARFILE,"r")) == NULL)
{
return 0;
}
while(fread(&ar,sizeof(ar),1,fp) == 1) // 循环复制,与输入姓名相匹配的不复制
{
if(strcmp(name,ar.name) != 0)
{
fwrite(&ar,sizeof(ar),1,fpn); // 不相同,则复制
}
memset(&ar,0x00,sizeof(ar));
}
fclose(fp);
fclose(fpn);
remove(ARFILE); // 删除原档案文件
rename("./tmpfile",ARFILE); // 复制好的新文件重命名为档案文件
return 1;
}
// 查询员工函数
int queryuser(char *name)
{
int found = 0;
struct arstruct ar;
FILE *fp;
if((fp = fopen(ARFILE,"r")) == NULL)
{
return 0;
}
while(fread(&ar,sizeof(ar),1,fp) == 1)
{
if(strcmp(name,ar.name) == 0)
{
found = 1;
break;
}
memset(&ar,0x00,sizeof(ar));
}
fclose(fp);
return found;
}
// 增加员工函数
int insertuser(struct arstruct tmpar)
{
struct arstruct ar;
FILE *fp;
if((fp = fopen(ARFILE,"a")) == NULL)
{
return 0;
}
memset(&ar,0x00,sizeof(ar));
strcpy(ar.name,tmpar.name);
strcpy(ar.tele,tmpar.tele);
ar.age=tmpar.age;
if(fwrite(&ar,sizeof(ar),1,fp) < 0)
{
perror("fwrite");
fclose(fp);
return 0;
}
fclose(fp);
return 1;
}
// 修改员工函数
int updateuser(struct arstruct new_ar)
{
struct arstruct old_ar;
FILE *fp;
fpos_t pos;
int found=0;
int cnt=0;
if((fp = fopen(ARFILE,"r+")) == NULL)
{
return 0;
}
while(fread(&old_ar, sizeof(old_ar), 1, fp) == 1) // 循环复制,与输入姓名相匹配的不复制
{
if(strcmp(old_ar.name,new_ar.name) != 0)
{
cnt++;
}
else if(strcmp(old_ar.name, new_ar.name) == 0)
{
found = 1;
pos.__pos = (long)(cnt * sizeof(struct arstruct)); // 设置移动量为一个结构体
fsetpos(fp, &pos); // 另外一种移动文件指针位置的方法
fwrite(&new_ar, sizeof(new_ar), 1, fp);
}
memset(&old_ar,0x00,sizeof(old_ar));
}
if(found)
{
return 1;
}
else
{
return -1;
}
}
服务器代码:new1.c
// new1.c
#include "ex3.h"
#define MAXSIZE 1024 // 缓冲区大小
int main(int argc, char *argv[])
{
int sockfd,new_fd; // 定义存放套接口描述符的变量
struct sockaddr_in server_addr; // 定义服务器端套接口数据结构server_addr
struct sockaddr_in client_addr; // 定义客户端套接口数据结构client_addr
struct arstruct ar;
int server_len, client_len; // 服务器和客户消息长度
int nbytes,portnumber;
int age;
char name[10];
char buf[MAXSIZE];
char rebuf[MAXSIZE];
if(argc!=2)
{
fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
exit(1);
}
if((portnumber=atoi(argv[1]))<0) // 获得命令行的第二个参数--端口号,atoi()把字符串转换成整型数
{
fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
exit(1);
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // 服务器端开始建立socket描述符
{
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
bzero(&server_addr,sizeof(struct sockaddr_in)); // 先将套接口地址数据结构清零
server_addr.sin_family=AF_INET; // 设为TCP/IP地址族
server_addr.sin_addr.s_addr=htonl(INADDR_ANY); // 设置本机地址并从主机字节序转换为网络字节序*/
server_addr.sin_port=htons(portnumber); // 设置端口号并从主机字节序转换为网络字节序*/
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1) // 调用bind函数绑定指定的端口号和ip地址到服务器创建的套接口
{
fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
exit(1);
}
if(listen(sockfd,5)==-1) // 端口绑定成功,监听sockfd描述符,设置同时处理的最大连接请求数为5 */
{
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
client_len = sizeof(client_addr); // 接收客户端连接请求
new_fd = accept(sockfd,(struct sockaddr *) &client_addr,(socklen_t *__restrict) &client_len);
while(1)
{
memset(rebuf,0x00,sizeof(rebuf));
read(new_fd,rebuf,sizeof(rebuf));
printf("收到客户端消息: %s\n",rebuf); // 输出到终端
if(rebuf[0] == '1')
{
write(new_fd,"请输入姓名",MAXSIZE); // 回复消息
read(new_fd,rebuf,sizeof(rebuf));
strcpy(ar.name,rebuf);
printf("收到客户端姓名消息: %s\n",ar.name); // 输出到终端
write(new_fd,"请输入年龄",MAXSIZE); // 回复消息
read(new_fd,rebuf,sizeof(rebuf));
age=atoi(rebuf);
ar.age=age;
printf("收到客户端年龄消息: %d\n",age); // 输出到终端
write(new_fd,"请输入员工手机号",MAXSIZE); // 回复消息
read(new_fd,rebuf,sizeof(rebuf));
strcpy(ar.tele,rebuf);
printf("收到客户端手机号消息: %s\n",ar.tele); // 输出到终端
if(insertuser(ar))
{
write(new_fd,"增加新员工成功...\n",MAXSIZE);
}
else
{
write(new_fd,"添加成员的时候发生了错误...\n",MAXSIZE);
}
}
else if(rebuf[0] == '2')
{
write(new_fd,"请输入姓名",MAXSIZE); // 回复消息
read(new_fd,rebuf,sizeof(rebuf));
printf("收到客户端查询员工姓名消息: %s\n",rebuf); // 输出到终端
if(queryuser(rebuf))
{
write(new_fd,"查询到该员工数据\n",MAXSIZE);
}
else
{
write(new_fd,"没有找到该员工的数据...\n",MAXSIZE);
}
}
else if(rebuf[0]== '3')
{
write(new_fd,"请输入员工姓名:",MAXSIZE); // 回复消息
read(new_fd,rebuf,10);
strcpy(name,rebuf);
printf("收到客户端删除员工姓名消息: %s\n",name); // 输出到终端
if(removeuser(name))
{
write(new_fd,"删除员工资料成功...\n",MAXSIZE);
}
else
{
write(new_fd,"删除员工数据发生了错误...\n",MAXSIZE);
}
}
else if(rebuf[0] == '4')
{
write(new_fd,"请输入姓名",MAXSIZE); // 回复消息
read(new_fd,rebuf,sizeof(rebuf));
strcpy(ar.name,rebuf);
printf("收到客户端修改员工姓名消息: %s\n",rebuf); // 输出到终端
write(new_fd,"请输入年龄",MAXSIZE); // 回复消息
read(new_fd,rebuf,sizeof(rebuf));
age=atoi(rebuf);
ar.age=age;
printf("收到客户端修改员工年龄消息: %d\n",age); // 输出到终端
write(new_fd,"请输入员工手机号",MAXSIZE); // 回复消息
read(new_fd,rebuf,sizeof(rebuf));
strcpy(ar.tele,rebuf);
printf("收到客户端修改员工手机号消息: %s\n",rebuf); // 输出到终端
int ret = updateuser(ar);
if(ret == 1)
{
write(new_fd,"修改员工资料成功...\n",MAXSIZE);
}
else if(ret == -1)
{
write(new_fd,"没有该员工的数据...\n",MAXSIZE);
}
else
{
write(new_fd,"修改员工数据发生了错误...\n",MAXSIZE);
}
}
else if(rebuf[0] == '0')
{
break;
}
}
return 0;
}
客户端代码:new2.c
// new2.c
#include "ex3.h"
#define MAXSIZE 1024 // 缓冲区大小
int main(int argc, char *argv[])
{
int new_fd;
char buf[MAXSIZE];
char rebuf[MAXSIZE];
char c;
struct sockaddr_in server_addr; // 定义服务器端套接口数据结构server_addr
struct hostent *host; // 定义一个hostent结构的指针
struct arstruct ar;
int portnumber,nbytes;
if(argc!=3)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL) // 通过域名或主机名得到包含地址的hostent指针
{
fprintf(stderr,"Gethostname error\n");
exit(1);
}
if((portnumber=atoi(argv[2]))<0)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
if((new_fd = socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr=*((struct in_addr *)host->h_addr);
if(connect(new_fd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
else
{
fprintf(stdout,"连接成功\n");
}
printf(" *员工档案管理系统*\n");
printf("---------------------------\n");
printf(" 1.录入新员工档案 \n");
printf(" 2.查看员工档案 \n");
printf(" 3.删除员工档案 \n");
printf(" 4.修改员工档案 \n");
printf(" 0.退出系统 \n");
printf("---------------------------\n");
while(1)
{
printf("请输入:");
scanf("%s",buf);
write(new_fd,buf,sizeof(buf)); // 发送消息
if(buf[0] == '0')
{
break;
}
read(new_fd,rebuf,sizeof(rebuf)); // 接收新消息
printf("收到服务器消息:%s\n",rebuf); // 输出到终端
}
printf("结束通信\n");
close(new_fd);
return 0;
}
实验结果截图:
三、实验总结:
1.所编写程序的思想:
- 对示例的代码 3-6进行改写成ex3.h,使得服务器程序可以调用函数维护员工系统
- 在员工系统中增加了使用流定位实现了对员工系统进行修改的函数
- 运行的流程是:客户端访问服务器程序,服务器接收客户端请求对员工系统进行查询、插入、删除、修改
2.本次实验自学了哪些知识:
①文件描述符
②流的打开关闭与读写
③流定位
④网络编程原理
⑤套接口描述符
⑥套接口读写
⑦TCP套接口通信流程
3.本次实验遇到的困难与解决:
①read/write函数与recv/send函数参数问题
②将套接口描述符作为参数调用
③缓冲区发送
④删除员工函数存在问题(未解决)