一、 引言(简要说明设计题目的目的、意义、内容、主要任务等)
1. 目的
1) 巩固和复习c语言的基础知识,进一步加深对c语言的理解和掌握
2) 课程设计提供了一个既动脑又动手,独立实践的机会,将课本上得理论知识和实际
3) 有机的结合起来,锻炼学生的分析解决实际问题的能力,提高学生适应实际,实践编程的
4) 能力;
5) 熟悉linux的基本操作,并且能够在linux环境下编写c语言程序、
6) 培养在项目开发中团队合作精神,创新意识及能力
2. 意义
1) 综合应用c程序设计课程的理论基础和知识,掌握程序设计的一般方法,树立正 确的设计思想,培养了分析问题和解决问题的能力
2) 学会了从实际的要求出发,合理的选择算法,正确的使用测试方法,培养了程序
设计能力
3) 学会运用帮助和查阅有关技术资料的能力。
3. 内容
程序一:
制作一个可供小学数学运算的程序:10以内加减法,能根据输入题数出题,判断题是否正确,最后计算分数。例如:请输入题数:2
1+5=
2+4=
由用户输入答案,程序根据答案判断正误,输出正确率(%)。并用make工程管理器编译。(注意分割文件,可参考第一题的提示。)编写makefile文件。
程序二:
设计一个程序,要求新建一个文件“hello”,利用write函数将“Linux下c软件设计”字符串写入该文件。
程序三:
利用命名管道实现两个进程间的通信。
程序四:
服务器通过socket连接后,向客户端发送字符串“连接上了”。在服务器上显示客
户端的IP地址或域名。
4. 主要任务
1) 分析题目的要求,
2) 总结各个程序所涉及的相关知识点
3) 各种算法的涉及
4) 画出部分模块的流程图
5)程序代码的设计与实现
二、 正文(课程设计的主要内容,包括实验与观测方法和结果、仪器设备、计算方法、编程原理、数据处理、设计说明与依据、加工整理和图表、形成的论点和导出的结论等。正文内容必须实事求是、客观真切、准确完备、合乎逻辑、层次分明、语言流畅、结构严谨,符合各学科、专业的有关要求。)
程序一:
源代码:
start.c文件:
该文件主要处理算法和逻辑,学生开始答题,运算逻辑的和结果 以及答题结果。
#include <stdio.h>
#include <stdlib.h>
void print_random(int count, int *a)
{
int ii = 1; a++; a++; int i=*a; int j=*a;
int c;
int total=0;
int d=0;
int ok = 0;
printf("本题库中只有%d题,请认真答题,每题5分\n",count); while(1){
printf("第%d题:", ii);
printf("%d ", i);
printf("+");
printf("%d ", j);
printf("=?");
printf("\n");
printf("请您输入结果:");
scanf("%d",&c);
d=i+j;
if(c==d){ printf("正确\n"); ok++; total+=5; }else{ printf("错误%d\n",d); total+=0;
}
if(ii==count){
} break;
ii++; a++; a++; i=*a; j=*a;
d=0;
}
printf("正确率为:%d%\n",ok*100/count);
printf("您最后的总分是:%d",total);
printf("\n");
}
main.c 文件:
该文件主要包含主函数,逻辑的控制,以及学生确定题的数量 还包括生成题库的函数。
#include <stdio.h>
#include <stdlib.h>
void gen_random(int count,int *a)
{
int i;
for (i = 0; i < count*2; i++){
}
int main(void)
{
printf("请您输入数量:"); int c; int a[200]; } a++; *a = rand() % 10; scanf("%d",&c); gen_random(c,a); printf("%d",a[2]);
print_random(c,a);
return 0;
}
Makefile文件:
main:start.o main.o
gcc start.o main.o -o xxlx
isDelta.o:start.c
gcc start.c -c
main.o:main.c
gcc main.c -c
运行结果图:
程序二:
程序描述:此程序主要实现了文件的打开,关闭,读取和写入。
1. creat
函数 creat 的作用是在目录中建立一个空文件,该函数的使用方法如下所示。 int creat(char * pathname, mode_t mode);
函数的参数 pathname 表示需要建立文件的文件名和目录名,mode 表示这个文件的权限。
文件权限的设置见本章第一节所述。文件创建成功时返回创建文件的编号,否则返回-1。
使用这个函数时,需要在程序的前面包含下面三个头文件。
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
如果创建文件不成功,可用 errno 捕获错误编号然后输出。creat 函数可能发生的错误如
下所示。
EEXIST:参数 pathname 所指的文件已存在。
EACCESS:参数 pathname 所指定的文件不符合所要求测试的权限。
EROFS:欲打开写入权限的文件存在于只读文件系统内。
EFAULT:参数 pathname 指针超出可存取的内存空间。
EINVAL:参数 mode 不正确。
ENAMETOOLONG:参数 pathname 太长。
ENOTDIR:参数 pathname 为一目录。
ENOMEM:核心内存不足。
ELOOP:参数 pathname 有过多符号链接问题。
EMFILE:已达到进程可同时打开的文件数上限。
2. open
函数 open 的作用是打开一个文件,使文件处于可读写的状态。这个函数的使用方法如下
所示。
int open(char *pathname, int flags);
int open(char *pathname,int flags, mode_t mode);
在上一节所示函数的参数列表中,pathname 表示要打开文件的路径字符串。参数flags
是系统定义的一些整型常数,表示文件的打开方式。Flags 的可选值如下所示。
O_RDONLY:以只读方式打开文件。
O_WRONLY:以只写方式打开文件。
O_RDWR:以可读写方式打开文件。
上述三种旗标是互斥的,也就是不可同时使用,但可与下列的旗标利用“|”运算符组合。
O_CREAT:若要打开的文件不存在则自动建立该文件。
O_EXCL:如果 O_CREAT 已被设置,此指令会去检查文件是否存在。文件若不存 在 则建立该文件,否则将导致打开文件错误。此外,若 O_CREAT 与 O_EXCL 同时设置 时,如果要打开的文件为一个链接,则会打开失败。
O_NOCTTY:如果要打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。
O_TRUNC:若文件存在并且以可写的方式打开时,此标志会清空文件。这样原来的文件内容会丢失。
O_APPEND:以附加的文件打开文件。当读写文件时会从文件尾开始向后移动,写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK:以不可阻断的方式打开文件,也就是无论文件有无数据读取或等待操作,都会立即打开文件。
O_NDELAY:O_NONBLOCK。
#include <fcntl.h> O_SYNC:以同步的方式打开文件,所有的文件操作不写入到缓存。
O_NOFOLLOW:如果参数 pathname 所指的文件为一符号链接,则会打开失败。
O_DIRECTORY:如果参数 pathname 所指的文件的目录不存在,则打开文件失败。
3. wirte
函数 write 可以把一个字符串写入到一个已经打开的文件中。这个函数的使用方法如下所示。
ssize_t write (int fd,void * buf,size_t count);
在参数列表中,fd 是已经打开文件的文件号,buf 是需要写入的字符串,count 是一个整型数,表示需要写入的字符个数。size_t 是一个相当于整型的数据类型,表示需要写入的字节的数目。将字符串写入文件以后,文件的写位置也会随之移动。
如果写入成功,write 函数会返回实际写入的字节数。发生错误发生时则返回-1,可以用
errno 来捕获发生的错误。可能发生的错误如下所示。
EINTR:此操作被其他操作中断。
EAGAIN:当前打开文件是不可写的方式打开的,不能写入文件。
EADF:参数 fd 不是有效的文件编号,或该文件已关闭。
代码解析:
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
/*包含头文件。*/
#include <errno.h>
main()
{
/*定义打开的文件号。*/
int fd;
/*定义要写入文件的路径*/
char path[]="hello";
char s[]="Linux下c软件设计";
/*定义要写入的字符串。*/
/*使用错误号。*/
extern int errno;
1) /*用 creat 函数创建一个文件*/
if(creat(path,0766)==-1)
{
/*不能创建文件。*/
printf("cant't create the file %s.\n",path);
printf("errno:%d\n",errno); /*显示错误号。*/ printf("ERR :%s\n",strerror(errno)); /*显示错误信息。*/ }
/*另一种情况。*/
else
{
/*显示创建成功。*/ printf("created file %s.\n",path);
}
/*打开文件。*/
fd=open(path,O_WRONLY|O_CREAT);
/*打开文件是否成功。*/ if(fd!=-1)
{
/*文件打开成功。*/ printf("opened file %s .\n",path);
}
else
{
/*文件打开不成功。*/ printf("cant't open file %s.\n",path);
printf("errno:%d\n",errno); /*显示错误号。*/ printf("ERR :%s\n",strerror(errno)); /*显示错误信息。*/ }
/*将内容写入到文件。*/ write(fd,s,sizeof(s));
/*关闭文件。*/ close(fd);
/*显示信息。*/ pintf("Done");
}
程序运行结果图:
程序三:
程序描述:主要完成进程的应用,实现进程间的通信问题
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[4];
char* w_buf[256];
int childexit=0;
int i;
int cmd;
memset(r_buf,0,sizeof(r_buf));
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
if((pid=fork())==0)
//子进程:解析从管道中获取的命令,并作相应的处理
{
printf("\n");
close(pipe_fd[1]);
sleep(2);
while(!childexit)
{
read(pipe_fd[0],r_buf,4);
cmd=atoi(r_buf);
if(cmd==0)
{
printf("child: receive command from parent over\n now child process exit\n"); childexit=1;
}
else if(handle_cmd(cmd)!=0)
return;
sleep(1);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid>0)
//parent: send commands to child
{
close(pipe_fd[0]);
w_buf[0]="003";
w_buf[1]="005";
w_buf[2]="777";
w_buf[3]="000";
for(i=0;i<4;i++)
write(pipe_fd[1],w_buf[i],4);
close(pipe_fd[1]);
}
}
//下面是子进程的命令处理函数(特定于应用):
int handle_cmd(int cmd)
{
if((cmd<0)||(cmd>256))
//suppose child only support 256 commands return 0;
{
printf("child: invalid command \n");
return -1;
}
printf("child: the cmd from parent is %d\n", cmd);
return 0;
}
程序运行结果图:
程序四:
方法描述:
请求连接函数 connet
所谓请求连接,指的是客户机向服务器发送信息时,需要发送一个连接请求。connect 函数可以完成这项功能,这个函数的使用方法如下所示。
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
在参数列表中,sockfd 表示已经建立的 socket,serv_addr 是一个结构体指针,指向一个sockaddr 结构体,这个结构体存储着远程服务器的 IP 与端口信息,Addrlen 表示 sockaddr 结构体的内存长度,可以用 sizeof 函数来获取。sockaddr 结构体的定义见前面节中的 bind 函数所述。
函数会将本地的 socket 连接到 serv_addr 所指定的服务器 IP 与端口。如果连接成功,返
回值为 0,连接失败则返回-1。函数可能发生下面的错误,可以用 error 来捕获发生的错误。
EBADF:参数 sockfd 不是一个合法的 socket。
EFAULT:参数 serv_addr 指针指向了一个无法读取的内存空间。
ENOTSOCK:参数 sockfd 是文件描述词,而不是一个正常的 socket。
EISCONN:参数 sockfd 的 socket 已经处于连接状态。
ECONNREFUSED:连接要求被服务器拒绝。
ETIMEDOUT:需要的连接操作超过限定时间仍未得到响应。
ENETUNREACH:无法传送数据包至指定的主机。
EAFNOSUPPORT:sockaddr 结构的 sa_family 不正确。
EALREADY:socket 不能阻断,但是以前的连接操作还未完成。
如果需要使用这个函数,需要在程序的最前面包含下面的头文件。
#include<sys/types.h>
#include<sys/socket.h>
数据接收函数 recv
函数 recv 可以接收远程主机发送来的数据,并把这些数据保存到一个数组中。该函数的使用方法如下所示。
int recv(int s,void *buf,int len,unsigned int flags);
在参数列表中,s 表示已经建立的 socket,buf 是一个指针,指向一个数组,接收到的数据会保存到这个数组上,len 表示数组的长度,可以用 sizeof 函数来取得,flags 一般设置为 0,其他可能的赋值与含义如下所示。
MSG_OOB:接收以 out-of-band 送出的数据。
MSG_PEEK:返回来的数据并不会在系统内删除,如果再调用 recv 时会返回相同的数 据内容。
MSG_WAITALL:强迫接收到 len 大小的数据后才能返回,除非有错误或信号产生。 MSG_NOSIGNAL:此操作被 SIGPIPE 信号中断。
recv 函数如果接收到数据,会把这些数据保存在 buf 指针指向的内存中,然后返回接收到字符的个数。如果发生错误则会返回-1。函数可能发生下面这些错误,可以用 errno 来捕获错误。
EBADF:参数 s 不是一个合法的 socket。
EFAULT:参数中的指针指向了无法读取的内存空间。
ENOTSOCK:参数 s 是文件描述词,而不是一个 socket。
EINTR:被信号中断。
EAGAIN:此动作会阻断进程,但参数 s 的 socket 不可阻断。
ENOBUFS:系统的缓冲内存不足。
ENOMEM:核心内存不足
EINVAL:参数不正确。
使用这个函数前,需要在程序的最前面包含下面的头文件。
#include <sys/types.h>
#include <sys/socket.h>
信息发送函数 send
用 connect 函数连接到远程计算机以后,可以用 send 函数将信息发送到对方的计算机。
这个函数的使用方法如下所示。
int send(int s,const void * msg,int len,unsigned int flags);
在参数列表中,s 表示已经建立的 socket,msg 是需要发送数据的指针,len 表示需要发
送数据的长度。这个长度可以用 sizeof 函数来取得,参数 flags 一般设置为 0,可能的赋值与
含义如下所示。
MSG_OOB:传送的数据以 out-of-band 的方式送出。
MSG_DONTROUTE:取消路由表查询。
MSG_DONTWAIT:设置为不可阻断传输。
MSG_NOSIGNAL:此传输不可被 SIGPIPE 信号中断。
如果发送数据成功,函数会返回已经传送的字符个数,否则会返回-1。函数可能发生下面这些错误,可以用 errno 来捕获函数的错误。
EBADF:参数 s 不是一个正确的 socket。
EFAULT:参数中的指针指向了不可读取的内存空间。
ENOTSOCK:参数 s 是一个文件,而不是一个 socket。
EINTR:被信号中断。
EAGAIN:此操作会中断进程,但 socket 不允许中断。
ENOBUFS:系统的缓冲内存不足。
ENOMEM:核心内存不足。
EINVAL:传给系统调用的参数不正确。
在使用这个函数前,需要在程序的最前面包含下面的头文件。
socket
在进行网络连接前,需要用 socket 函数向系统申请一个通信端口。这个函数的使用方法
如下所示。
int socket(int domain, int type, int protocol);
在参数表表中,domain 指定使用何种的地址类型,可能的值是下面这些系统常量。
PF_UNIX,PF_LOCAL,AF_UNIX,AF_LOCAL:这些是 UNIX 进程通信协议。 PF_INET,AF_INET:Ipv4 网络协议。
PF_INET6,AF_INET6:Ipv6 网络协议。
PF_IPX,AF_IPX:IPX-Novell 协议。
PF_NETLINK,AF_NETLINK:核心用户接口装置。
PF_X25,AF_X25、ITU-T X.25:ISO-8208 协议。
PF_AX25,AF_AX25:业余无线 AX.25 协议。
PF_ATMPVC,AF_ATMPVC:存取原始 ATM PVCs。
PF_APPLETALK,AF_APPLETALK:DDP 网络协议。
PF_PACKET,AF_PACKET:初级封包接口。
type 参数的作用是设置通信的协议类型,可能的取值如下所示。
SOCK_STREAM:提供面向连接的稳定数据传输,即 TCP 协议。
OOB:在所有数据传送前必须使用 connect()来建立连线状态。
SOCK_DGRAM:使用不连续不可靠的数据包连接。
SOCK_SEQPACKET:提供连续可靠的数据包连接。
SOCK_RAW:提供原始网络协议存取。
SOCK_RDM:提供可靠的数据包连接。
SOCK_PACKET:与网络驱动程序直接通信。
参数 protocol 用来指定 socket 所使用的传输协议编号。这一参数通常不具体设置,一般Linux 系统下 C 程序开发详解
设置为 0 即可。
在使用这个函数建立套接字前,需要在程序的最前面包含下面的头文件。
#include <sys/types.h>
#include <sys/socket.h>
如果建立套接字成功,则返回这个套接字的编号。如果不成功,则返回-1。这个函数可能发生的错误如下所示。
EPROTONOSUPPORT:参数 domain 指定的类型不支持参数 type 或 protocol 指定的 协议。
ENFILE:核心内存不足,无法建立新的 socket 结构。
EMFILE:进程文件表溢出,无法再建立新的套接字。
EACCESS :权限不足,无法建立 type 或 protocol 指定的协议。
ENOBUFS、ENOMEM:内存不足。
EINVAL:参数不合法。
程序描述:
实现网络通信,通过套接字实现服务器端和客户端的链接,
以及实现传送数据。
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVPORT 3333 /*服务器监听端口号 */
#define BACKLOG 10 /* 最大同时连接请求数 */
int main()
{
int sock_fd,client_fd; /*sock_fd:监听socket;client_fd:数据传输socklen_t sin_size; struct sockaddr_in my_addr; /* 本机地址信息 */ struct sockaddr_in remote_addr; /* 客户端地址信息 */ if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket创建出错!"); exit(1); } my_addr.sin_family=AF_INET; my_addr.sin_port=htons(SERVPORT); socket */
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
if(bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
perror("bind出错!");
exit(1);
}
if(listen(sock_fd, BACKLOG) == -1) {
perror("listen出错!");
exit(1);
}
while(1)
{ sin_size = sizeof(struct sockaddr_in); if((client_fd = accept(sock_fd, (struct sockaddr *)&remote_addr, { perror("accept出错"); continue;
a connection &sin_size)) == -1) } printf("received
from %s\n",inet_ntoa(remote_addr.sin_addr));
if(!fork()) { /* 子进程代码段 */
if(send(client_fd, "Hello, you are connected!\n", 26, 0) == -1) {
perror("send出错!");
}
close(client_fd);
exit(0);
}
close(client_fd);
}
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#define SERVPORT 3333
#define MAXDATASIZE 100 /*每次最大数据传输量 */
int main(int argc, char *argv[])
{
int sock_fd, recvbytes;
char buf[MAXDATASIZE]; return 0;
struct hostent *host;
struct sockaddr_in serv_addr;
if(argc< 2) {
fprintf(stderr,"Please enter the server's hostname!\n"); exit(1);
}
if((host=gethostbyname(argv[1])) == NULL) {
herror("gethostbyname出错!");
exit(1);
}
if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket创建出错!");
exit(1);
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERVPORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
if(connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) ==-1) {
perror("connect出错!");
exit(1);
}
if((recvbytes=recv(sock_fd, buf, MAXDATASIZE, 0)) == -1) {
perror("recv出错!");
exit(1);
}
buf[recvbytes] = '\0';
printf("Received: %s",buf);
close(sock_fd);
return 0;
}
运行图:
服务器端图:
客户端图:
三、 结论(应当准确、完整、明确精练;也可以在结论或讨论中提出建议、设想、尚待解决问题等。)
1.自我总结
通过这次的课程设计,我收获了很多。在设计的过程中,我对课本的内容有了进一步的了解,编程及调试程序的能力都得到了较大的提高。在写第一二题是还是比较轻松的,但我觉得第三四题对我的能力的提高更有帮助,每个函数都调试了n多次。
在这次设计过程中,不仅复习课本上所学知识,还通过查资料、问同学学到了课本上没有的知识。从而启发我,要想写好程序,在写好课本知识的同时还需要多读和专业有关的一些书籍,同时还需要多动脑子,尽量把所学的知识综合起来应用,力争写出完美的程序。除此之外,我还得到了一些有用的教训:写程序时必须要细心,不能输错一个字符标点,就连全角半角也得注意。在修改时要有耐心,编译出错后必须逐个错误去改正,绝不能心急浮躁,否则修改之后还会有新的错误。
2. 建议
首先,要准备足够的资料,在编写代码是以备查阅。多调试,调试时要不断的设置断点,以确定程序的走向和过程。
3.设想
全部的程序增加图形化界面
4.尚待解决的问题
简化程序,梳理代码,提高程序运行的效率,节省内存的占用和处理器的占用。