实验内容
- 任务一
学习使用Linux命令wc(1)基于Linux Socket程序设计实现wc(1)服务器(端口号是你学号的后6位155203)和客户端客户端传一个文本文件给服务器服务器返加文本文件中的单词数
- 实现mywc
wc功能:
1.命令格式:wc [选项]文件...2.命令功能:统计指定文件中的字节数、字数、行数,并将统计结果显示输出。该命令统计指定文件中的字节数、字数、行数。如果没有给出文件名,则从标准输入读取。wc同时也给出所指定文件的总统计数。3.命令参数:-c 统计字节数。-l 统计行数。-m 统计字符数。这个标志不能与 -c 标志一起使用。-w 统计字数。一个字被定义为由空白、跳格或换行字符分隔的字符串。-L 打印最长行的长度。-help 显示帮助信息--version 显示版本信息
wc实现伪代码:
int main(){ fd = fopen()//打开文件; fscanf()//对文件的内容以字符串的形式进行读取 if..count++//设置条件,当满足字符串条件时计数;}
wc实现代码:
#include#include int main(int argc ,char *argv[]){ char ch; FILE *fp; long count=0; char s[21]; if ((fp=fopen(argv[1],"r+"))==NULL) { fprintf(stderr,"不能打开文件\"%s\"\n",argv[1]); exit(EXIT_FAILURE); }while(fscanf(fp,"%s",s)!=EOF){ if((s[0]>='a'&&s[0]<='z')||(s[0]<='Z'&&s[0]>='A')) count++;} fclose(fp); printf("File %s has %ld characters\n",argv[1],count); return 0;}
服务器端与客户端实现代码(使用迭代服务器):
/*server*/#include#include #include #include #include #include #include #include #include #define PORT 155203#define BACKLOG 1#define MAXRECVLEN 1024int main(int argc, char *argv[]){ char buf[MAXRECVLEN]; int listenfd, connectfd; /* socket descriptors */ struct sockaddr_in server; /* server's address information */ struct sockaddr_in client; /* client's address information */ socklen_t addrlen; /* Create TCP socket */ if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { /* handle exception */ perror("socket() error. Failed to initiate a socket"); exit(1); } /* set socket option */ int opt = SO_REUSEADDR; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&server, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) { /* handle exception */ perror("Bind() error."); exit(1); } if(listen(listenfd, BACKLOG) == -1) { perror("listen() error. \n"); exit(1); } addrlen = sizeof(client); while(1){ if((connectfd=accept(listenfd,(struct sockaddr *)&client, &addrlen))==-1) { perror("accept() error. \n"); exit(1); } FILE *stream; struct timeval tv; gettimeofday(&tv, NULL); printf("You got a connection from client's ip %s, port %d at time %ld.%ld\n",inet_ntoa(client.sin_addr),htons(client.sin_port), tv.tv_sec,tv.tv_usec); int iret=-1; char d[1024]; iret = recv(connectfd, buf, MAXRECVLEN, 0); if(iret>0) { strcpy(d,buf); stream = fopen(buf,"r"); bzero(buf, sizeof(buf)); strcat(buf,"单词数:"); char s[21];long int count = 0; while(fscanf(stream,"%s",s)!=EOF) { if((s[0]>='a'&&s[0]<='z')||(s[0]<='Z'&&s[0]>='A')) count++; } char str[10];sprintf(str, "%ld", count); int n = sizeof(str);str[n] = '\0'; strcat(buf,"\n");strcat(buf,str);strcat(buf,"(");strcat(buf,d);strcat(buf,"单词数)");strcat(buf,"\n"); }else { close(connectfd); break; } /* print client's ip and port */ send(connectfd, buf, iret, 0); /* send to the client welcome message */ } close(listenfd); /* close listenfd */ return 0;}/*server end*//*client*/clude #include #include #include #include #include #include #include /* netdb is necessary for struct hostent */#define PORT 155203/* server port */#define MAXDATASIZE 100int main(int argc, char *argv[]){ int sockfd, num; /* files descriptors */ char buf[MAXDATASIZE]; /* buf will store received text */ struct hostent *he; /* structure that will get information about remote host */ struct sockaddr_in server; if (argc != 3) { printf("Usage: %s \n",argv[0]); exit(1); } if((he=gethostbyname(argv[1]))==NULL) { printf("gethostbyname() error\n"); exit(1); } if((sockfd=socket(AF_INET,SOCK_STREAM, 0))==-1) { printf("socket() error\n"); exit(1); } bzero(&server,sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr = *((struct in_addr *)he->h_addr); if(connect(sockfd, (struct sockaddr *)&server, sizeof(server))==-1) { printf("connect() error\n"); exit(1); }char str[MAXDATASIZE] ;strcpy(str,argv[2]);if((num=send(sockfd,str,sizeof(str),0))==-1){ printf("send() error\n"); exit(1); } if((num=recv(sockfd,buf,MAXDATASIZE,0))==-1) { printf("recv() error\n"); exit(1); } buf[num-1]='\0'; printf("server message: %s\n",buf); close(sockfd); return 0;}
- 任务二
使用多线程实现wc服务器并使用同步互斥机制保证计数正确
- 线程同步和互斥机制的区别
1. 互斥锁主要用来保护临界资源,什么是临界资源,就是有可能多个线程都需要访问的数据地址,也有可能是某一段代码,执行这段代码有可能会改变多个线程都需要访问的数据。2.加入同步机制主要是为了在多线程程序中,如果需要对某个共享资源C进行同步访问,什么是同步访问,就是A线程访问过程中,B线程不能访问,必须等A线程访问结束后,B线程才能访问,而互斥锁,如果用来对C进行保护,A访问C资源的过程中,B不能访问,A访问结束后,B可以访问,但不一定访问的到,这取决于系统的调度是否给到B,如果没有,A反而被调度到了,那么A就有可能方法到C。反观同步机制,在这种情况下,如果系统没有调度到B,A也是没有可能访问C的,必须等B调度到之后,A才可能重新访问。
- 服务器客户端实现代码: (参考教材中有关互斥机制的代码)
/* /* * echoservert_pre.c - A prethreaded concurrent echo server *//* $begin echoservertpremain */#include "csapp.h"#include#include static int byte_cnt; /* byte counter */static sem_t mutex;#define NTHREADS 4#define SBUFSIZE 16typedef struct { int *buf; /* Buffer array */ int n; /* Maximum number of slots */ int front; /* buf[(front+1)%n] is first item */ int rear; /* buf[rear%n] is last item */ sem_t mutex; /* Protects accesses to buf */ sem_t slots; /* Counts available slots */ sem_t items; /* Counts available items */} sbuf_t;void echo_cnt(int connfd);void *thread(void *vargp);int wc(char *name){ char ch; FILE *fp; long count=0; char s[21]; if ((fp=fopen(name,"r+"))==NULL) { fprintf(stderr,"不能打开文件\n"); exit(EXIT_FAILURE); }while(fscanf(fp,"%s",s)!=EOF) count++; fclose(fp); printf("File %s has %ld characters\n",name,count); return 0;}sbuf_t sbuf; /* shared buffer of connected descriptors */int main(int argc, char **argv) { int i, listenfd, connfd, port, clientlen=sizeof(struct sockaddr_in); struct sockaddr_in clientaddr; pthread_t tid; if (argc != 2) { fprintf(stderr, "usage: %s \n", argv[0]); exit(0); } port = atoi(argv[1]); sbuf_init(&sbuf, SBUFSIZE); listenfd = Open_listenfd(port); for (i = 0; i < NTHREADS; i++) /* Create worker threads */ Pthread_create(&tid, NULL, thread, NULL); while (1) { connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen); sbuf_insert(&sbuf, connfd); /* Insert connfd in buffer */ }}static void init_echo_cnt(void){ Sem_init(&mutex, 0, 1); byte_cnt = 0;}void echo_cnt(int connfd) { int n,x; long int count; char buf[MAXLINE]; char name[MAXLINE] rio_t rio; static pthread_once_t once = PTHREAD_ONCE_INIT; Pthread_once(&once, init_echo_cnt); Rio_readinitb(&rio, connfd); while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) { P(&mutex); byte_cnt += n; x = sizeof(buf); buf[x] = 0; count = wc(buf); printf("thread %d received %d (%d total) bytes on fd %d\n", (int) pthread_self(), n, byte_cnt, connfd); name = buf; V(&mutex); sprint(buf,"%s:%ld characters".count); Rio_writen(connfd, buf, n); }}void sbuf_init(sbuf_t *sp, int n){ sp->buf = Calloc(n, sizeof(int)); sp->n = n; /* Buffer holds max of n items */ sp->front = sp->rear = 0; /* Empty buffer iff front == rear */ Sem_init(&sp->mutex, 0, 1); /* Binary semaphore for locking */ Sem_init(&sp->slots, 0, n); /* Initially, buf has n empty slots */ Sem_init(&sp->items, 0, 0); /* Initially, buf has zero data items */}/* $end sbuf_init *//* Clean up buffer sp *//* $begin sbuf_deinit */void sbuf_deinit(sbuf_t *sp){ Free(sp->buf);}/* $end sbuf_deinit *//* Insert item onto the rear of shared buffer sp *//* $begin sbuf_insert */void sbuf_insert(sbuf_t *sp, int item){ P(&sp->slots); /* Wait for available slot */ P(&sp->mutex); /* Lock the buffer */ sp->buf[(++sp->rear)%(sp->n)] = item; /* Insert the item */ V(&sp->mutex); /* Unlock the buffer */ V(&sp->items); /* Announce available item */}/* $end sbuf_insert *//* Remove and return the first item from buffer sp *//* $begin sbuf_remove */int sbuf_remove(sbuf_t *sp){ int item; P(&sp->items); /* Wait for available item */ P(&sp->mutex); /* Lock the buffer */ item = sp->buf[(++sp->front)%(sp->n)]; /* Remove the item */ V(&sp->mutex); /* Unlock the buffer */ V(&sp->slots); /* Announce available slot */ return item;}void *thread(void *vargp) { Pthread_detach(pthread_self()); while (1) { int connfd = sbuf_remove(&sbuf); /* Remove connfd from buffer */ echo_cnt(connfd); /* Service client */ Close(connfd); }}/* $end echoservertpremain *//* * echoclient.c - An echo client *//* * echoclient.c - An echo client *//* $begin echoclientmain */#include "csapp.h"#include #include int wc(char *name){ char ch; FILE *fp; long count=0; char s[21]; if ((fp=fopen("test1.txt","r+"))==NULL) { fprintf(stderr,"不能打开文件%s\n",name); exit(EXIT_FAILURE); }while(fscanf(fp,"%s",s)!=EOF) count++; fclose(fp); printf("File %s has %ld characters\n",name,count); return 0;}int main(int argc, char **argv) { int clientfd, port,n,count; char *host, buf[MAXLINE]; rio_t rio; if (argc != 3) { fprintf(stderr, "usage: %s \n", argv[0]); exit(0); } host = argv[1]; port = atoi(argv[2]); clientfd = Open_clientfd(host, port); Rio_readinitb(&rio, clientfd); while (Fgets(buf, MAXLINE, stdin) != NULL) { if((num=recv(sockfd,buf,MAXDATASIZE,0))==-1) { printf("recv() error\n"); exit(1); } buf[num-1]='\0'; Rio_writen(clientfd, buf, strlen(buf)); Rio_readlineb(&rio, buf, MAXLINE); Fputs(buf, stdout); } Close(clientfd); exit(0);}/* $end echoclientmain *//* $begin echoclientmain *///#include "csapp.h"/*int main(int argc, char **argv) { int clientfd, port; char *host, buf[MAXLINE]; char *name; rio_t rio; FILE *fp; if (argc != 4) { fprintf(stderr, "usage: %s \n", argv[0]); exit(0); } host = argv[1]; port = atoi(argv[2]); name = argv[3]; clientfd = Open_clientfd(host, port); Rio_readinitb(&rio, clientfd); fp=fopen(name,"r+"); while (Fgets(buf, MAXLINE,fp) != NULL) { Rio_writen(clientfd, buf, strlen(buf)); Rio_readlineb(&rio, buf, MAXLINE); Fputs(buf, stdout); } Close(clientfd); exit(0);}/* $end echoclientmain */
思考题:
对比单线程的版本的性能,并分析原因。回答:原因:单线程比较稳定易于实现吧,这个是通过修改书上的代码得到的修改的过程中每次编译运行会出乱码或者运行不正确。线程本身由于创建和切换的开销,采用多线程不会提高程序的执行速度,反而会降低速度,但是对于频繁IO操作的程序,多线程可以有效的并发。
实验体会:
这次实验中更进一步体会了多进程和多线程的区别以及二者的优劣。进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。实际上,线程是属于进程内部的。线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。
进程和线程的关系:(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。(3)CPU分给线程,即真正在CPU上运行的是线程。
同时对于操作系统课上所学到的互斥访问概念有了实践体会。