一、實驗目的
理解進程的創建和終止方法;
熟悉父進程與子進程對描述符的操作過程;
學會編寫基本的多進程并發服務器程序和客戶程序。
二、實驗平臺
ubuntu-8.04操作系統
三、實驗內容
編寫多進程并發服務器程序和客戶程序,具體功能如下:
1、服務器等待接收客戶的連接請求,一旦連接成功則顯示客戶地址,接著接收客戶端的名稱并顯示;然后接收來自該客戶的字符串,每當收到一個字符串時,顯示該字符串,并將字符串按照愷撒密碼的加密方式(K=3)進行加密,再將加密后的字符發回客戶端;之后,繼續等待接收該客戶的信息,直到客戶關閉連接。要求服務器具有同時處理多個客戶請求的能力。
2、客戶首先與相應的服務器建立連接;接著接收用戶輸入的客戶端名稱,并將其發送給服務器;然后繼續接收用戶輸入的字符串,再將字符串發送給服務器,同時接收服務器發回的加密后的字符串并顯示。之后,繼續等待用戶輸入字符串,直到用戶輸入Ctrl+D,客戶關閉連接并退出。
四、實驗原理
前面所實現的服務器/客戶程序中,服務器每次只能處理一個客戶的請求,他雖然很簡單但效率低下。在實際應用中,這樣的服務器不能滿足實際需求,并發技術可以極大地提高服務器的處理能力和響應速度。
TCP并發服務器的工作流程見圖6.1所示:
圖6.1TCP并發服務器
1、創建進程
可以通過調用fork或vfork函數來創建新進程。
(1)fork函數
------------------------------------------------------------------- #include<sys/types.h> #include <unistd.h> pid_t fork(void) 返回:父進程中返回子進程的進程ID,子進程返回0;-1—出錯 ------------------------------------------------------------------- |
fork后,子進程和父進程繼續執行fork()函數后的指令。子進程是父進程的副本。子進程擁有父進程的數據空間、堆棧的副本。但父、子進程并不共享這些存儲空間部分。如果代碼段是只讀的,則父子進程共享代碼段。如果父子進程同時對同一文件描述字操作,而又沒有任何形式的同步,則會出現混亂的狀況;
父進程中調用fork之前打開的所有描述字在函數fork返回之后子進程會得到一個副本。fork后,父子進程均需要將自己不使用的描述字關閉。
(2)vfork函數
------------------------------------------------------------------- #include<sys/types.h> #include <unistd.h> pid_tvfork(void) 返回:父進程中返回子進程的進程ID,子進程返回0;-1—出錯 ------------------------------------------------------------------- |
2、終止進程
進程的終止存在兩個可能:
(1)父進程先于子進程終止;
(2)子進程先于主進程終止。
exit()函數:
------------------------------------------------------------------- #include<stdlib.h> void exit(int status); ------------------------------------------------------------------- |
exit()函數用于終止調用進程。關閉所有子進程打開的描述符,向父進程發送SIGCHLD信號,并返回狀態。
父進程可通過調用wait()或waitpid()函數獲得子進程的終止信息。
wait()函數:
------------------------------------------------------------------- #include<sys/types.h> #include <sys/wait.h> pid_t wait(int*stat_loc); 返回:終止子進程的ID-成功;-1-出錯;stat_loc存儲子進程的終止狀態(一個整數); ------------------------------------------------------------------- |
如果沒有終止的子進程,但是有一個或多個正在執行的子進程,則該函數將堵塞,直到有一個子進程終止或者wait被信號中斷時,wait返回。
當調用該系統調用時,如果有一個子進程已經終止,則該系統調用立即返回,并釋放子進程所有資源。
waitpid()函數:
------------------------------------------------------------------- #include<sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int*stat_loc, int option); 返回:終止子進程的ID-成功;-1-出錯;stat_loc存儲子進程的終止狀態;------------------------------------------------------------------- |
當pid=-1,option=0時,該函數等同于wait,否則由參數pid和option共同決定函數行為,其中pid參數意義如下:
Option最常用的選項是WNOHANG,它通知內核在沒有已終止進程時不要堵塞。
調用wait或waitpid函數時,正常情況下,可能會有以下幾種情況:
五、實驗步驟
1、登陸進入ubuntu操作系統,新建一個文件,命名為mproc_server.c,新建另一個文件,命名為mproc_client.c。
2、在mproc_server.c和mproc_client.c中編寫相應代碼并保存。
3、打開一個“終端”,執行命令進入mproc_server.c和mproc_client.c所在目錄。
4、執行命令gcc–omproc_servermproc_server.c生成可執行文件mproc_server。
5、執行命令gcc–omproc_clientmproc_client.c生成可執行文件mproc_client。
6、執行命令./mproc_server,運行服務器端。
7、打開第2個“終端”,執行命令進入mproc_server.c和mproc_client.c所在目錄。
8、執行命令./mproc_client127.0.0.1,模擬客戶1。
9、打開第3個“終端”,執行命令進入mproc_server.c和mproc_client.c所在目錄。
10、執行命令./mproc_client127.0.0.1,模擬客戶2。
11、程序運行結果如下:
服務器端:

客戶1:

客戶2:

12、在客戶端按下Ctrl+D,關閉客戶連接。
13、認真分析源代碼,體會多進程并發服務器程序的編寫。
六、參考程序
1、mproc_server.c內容如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
-
- #define PORT 1234
- #define BACKLOG 5
- #define MAXDATASIZE 1000
- void process_cli(int connfd, struct sockaddr_in client);
-
- main()
- {
- int listenfd, connfd;
- pid_t pid;
- struct sockaddr_in server;
- struct sockaddr_in client;
- int len;
-
- if ((listenfd =socket(AF_INET, SOCK_STREAM, 0)) == -1) {
- perror("Creatingsocket failed.");
- exit(1);
- }
-
- 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) {
- perror("Bind()error.");
- exit(1);
- }
-
- if(listen(listenfd,BACKLOG)== -1){
- perror("listen() error\n");
- exit(1);
- }
- len=sizeof(client);
-
- while(1)
- {
- if ((connfd =accept(listenfd,(struct sockaddr *)&client,&len))==-1) {
- perror("accept() error\n");
- exit(1);
- }
- if ((pid=fork())>0){
- close(connfd);
- continue;
- }
- else if (pid==0) {
- close(listenfd);
- process_cli(connfd, client);
- exit(0);
- }
- else {
- printf("fork()error\n");
- exit(0);
- }
- }
- close(listenfd);
- }
-
- void process_cli(int connfd, struct sockaddr_in client)
- {
- int num;
- char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];
- printf("Yougot a connection from %s. ",inet_ntoa(client.sin_addr) );
- num = recv(connfd,cli_name, MAXDATASIZE,0);
- if (num == 0)
- {
- close(connfd);
- printf("Client disconnected.\n");
- return;
- }
- cli_name[num - 1] ='\0';
- printf("Client'sname is %s.\n",cli_name);
-
- while (num =recv(connfd, recvbuf, MAXDATASIZE,0)) {
- recvbuf[num] ='\0';
- printf("Receivedclient( %s ) message: %s",cli_name, recvbuf);
- int i = 0;
- for (i = 0;i < num - 1; i++) {
- if((recvbuf[i]>='a'&&recvbuf[i]<='z')||(recvbuf[i]>='A'&&recvbuf[i]<='Z'))
- {
- recvbuf[i]=recvbuf[i]+ 3;
- if((recvbuf[i]>'Z'&&recvbuf[i]<='Z'+3)||(recvbuf[i]>'z'))
- recvbuf[i]=recvbuf[i]- 26;
- }
- sendbuf[i] =recvbuf[i];
- }
- sendbuf[num - 1]= '\0';
-
- send(connfd,sendbuf,strlen(sendbuf),0);
- }
- close(connfd);
- }
2、mproc_client.c內容如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <string.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include <netdb.h>
-
- #define PORT 1234
- #define MAXDATASIZE100
- void process(FILE*fp, int sockfd);
- char *getMessage(char* sendline,int len, FILE* fp);
-
- int main(int argc,char *argv[])
- {
- int fd;
- struct hostent *he;
- struct sockaddr_in server;
-
- if (argc !=2) {
- printf("Usage:%s <IP Address>\n",argv[0]);
- exit(1);
- }
-
- if((he=gethostbyname(argv[1]))==NULL){
- printf("gethostbyname() error\n");
- exit(1);
- }
- if((fd=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(fd,(struct sockaddr *)&server,sizeof(server))==-1){
- printf("connect() error\n");
- exit(1);
- }
-
- process(stdin,fd);
-
- close(fd);
- }
-
- void process(FILE *fp, int sockfd)
- {
- char sendline[MAXDATASIZE],recvline[MAXDATASIZE];
- int num;
-
- printf("Connected to server. \n");
- printf("Input client's name : ");
- if (fgets(sendline, MAXDATASIZE, fp) == NULL) {
- printf("\nExit.\n");
- return;
- }
- send(sockfd,sendline, strlen(sendline),0);
- while(getMessage(sendline, MAXDATASIZE, fp) != NULL) {
- send(sockfd,sendline, strlen(sendline),0);
-
- if ((num =recv(sockfd, recvline, MAXDATASIZE,0)) == 0) {
- printf("Server terminated.\n");
- return;
- }
-
- recvline[num]='\0';
- printf("Server Message: %s\n",recvline);
-
- }
- printf("\nExit.\n");
- }
-
- char *getMessage(char* sendline,int len, FILE* fp)
- {
- printf("Inputstring to server:");
- return(fgets(sendline,MAXDATASIZE, fp));
- }