Unix網絡編程---第四次做業編程
要求:數組
服務器利用I/O複用技術,實現同時向多個客戶提供服務。服務器
服務端:網絡
接收客戶鏈接請求,並打印客戶IP地址及端口號,而後接收客戶發來的字符串,並打印該字符串和其來自與哪一個客戶。同時向客戶返回該字符串。併發
客戶端:socket
從命令行接收服務器地址,並向服務器發起鏈接請求,鏈接成功後,從標準輸入接收字符串併發送給服務器,等待服務器響應並打印接收的信息函數
思路:this
針對本次做業,即經過select設置須要等待的描述符。首先將監聽套接字描述符在描述符集中打開,當有新的客戶請求鏈接時,select返回,監聽套接字可讀,因而創建鏈接,當鏈接創建之後,產生新的已鏈接套接字描述符,將其在描述符集中打開,此後每次select返回,檢查哪些套接字描述符可用。spa
針對本題,我沒有作題目中的要求功能,而是相對簡單相似的,經過用I/O複用解決第三個做業中存在當服務端被kill後,客戶端沒法得知的問題。命令行
程序實現:
服務器端:my_server4.c
1 #include <sys/socket.h> 2 #include <sys/types.h>/*The funcion sizeof,socklen_t need*/ 3 #include <netinet/in.h>/*The funcion sockaddr_in need*/ 4 #include <unistd.h> 5 #include <arpa/inet.h>/*The funcion inet_ntoa need*/ 6 #include <string.h>/*The funcion strlen need*/ 7 #include <errno.h>/*errno == EINTR*/ 8 #include <sys/wait.h>/*WNOHANG*/ 9 #include <pthread.h> 10 11 #define UPORT 8088 /*This is the port number used by me */ 12 #define MAXLINE 255 13 #define LISTENQ 32 14 #define NAMELEN 21 15 typedef struct { 16 char buf[MAXLINE+1]; 17 ssize_t n; 18 int sockfd; 19 char name[NAMELEN+1]; 20 } readline; 21 22 pthread_key_t ser_key; 23 pthread_once_t ser_once=PTHREAD_ONCE_INIT; 24 25 void str_echo( readline *tsd); 26 void sig_chld(int signo); 27 void ser_destructor(void *ptr); 28 void service_once(void); 29 static void *doit(void *arg); 30 void echo_name(readline *tsd); 31 32 int main(int argc, char **argv) 33 { 34 int listenfd ,connfd, reuse=1;//if the value of reuse is not zero, mean open this reuse address selection, or else ban this function 35 int *cfdp; 36 struct sockaddr_in servaddr, cliaddr; 37 socklen_t clilen; 38 pthread_t tid,tid1; 39 listenfd = socket(AF_INET, SOCK_STREAM, 0); 40 bzero(&servaddr, sizeof(servaddr)); 41 servaddr.sin_family = AF_INET; 42 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 43 servaddr.sin_port = htons(UPORT); /* daytime server */ 44 if( setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1){ 45 perror("There is an error occured when the program set REUSEADDR symbol\n"); 46 return -1; 47 } 48 if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr))==-1){ 49 perror("%s\r\n","bind error"); 50 exit(-1); 51 } 52 listen(listenfd, LISTENQ); 53 signal(SIGCHLD, sig_chld); 54 for ( ; ; ) { 55 clilen=sizeof(cliaddr); 56 cfdp=(int *)malloc(sizeof(int)); 57 if((*cfdp = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen))==-1){ 58 perror("%s\r\n","An error occured while tring to creat a connfd! "); 59 exit(-1); 60 } 61 printf("the new connection address is:%s:%d\r\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); 62 if(pthread_create(&tid, NULL, &doit, cfdp )!=0){ 63 perror("pthread_create: error\n"); 64 exit(-1); 65 } 66 } 67 } 68 void echo_name(readline *tsd){ 69 char tmp; 70 int i, j; 71 char name[21];//all 72 strcpy(tsd->buf,"Dear client please input your name: "); 73 if(write(tsd->sockfd, tsd->buf,strlen(tsd->buf)+1)==-1) { 74 perror("write error"); 75 exit(-1); 76 } 77 78 if ((tsd->n=read(tsd->sockfd,tsd->name, NAMELEN)) > 0) { /*tsd->=*tsd.n*/ 79 tsd->name[tsd->n-1]=0;/*change '\n' to 0*/ 80 printf("the client's name: [ %s ]\n", tsd->name); 81 strcpy(tsd->buf,"Now,you can begin to input the string you need to conver!\n"); 82 if(write(tsd->sockfd, tsd->buf,strlen(tsd->buf)+1)==-1) { 83 perror("write error"); 84 exit(-1); 85 } 86 } 87 if (tsd->n<0 && errno == EINTR) { 88 perror("read:error interrupt"); 89 } 90 else if (tsd->n<0) { 91 perror("str_echo:read error"); 92 exit(-1); 93 } 94 } 95 void str_echo( readline *tsd) { 96 char tmp; 97 int i, j; 98 again: 99 while ( (tsd->n=read(tsd->sockfd,tsd->buf, MAXLINE)) > 0) { /*tsd->=*tsd.n*/ 100 tsd->buf[tsd->n]=0; 101 printf("client [ %s ] input string:%s",tsd->name,tsd->buf); 102 for(i=0, j=tsd->n-2; i<j; i++, j--) { 103 tmp=tsd->buf[i]; 104 tsd->buf[i]=tsd->buf[j]; 105 tsd->buf[j]=tmp; 106 } 107 if(write(tsd->sockfd, tsd->buf, tsd->n)==-1) { 108 perror("write error"); 109 exit(-1); 110 } 111 printf("inverted order %s's string:%s",tsd->name,tsd->buf); 112 } 113 if (tsd->n<0 && errno == EINTR) { 114 goto again; 115 } 116 else if (tsd->n<0) { 117 perror("str_echo:read error"); 118 exit(-1); 119 } 120 } 121 void sig_chld(int signo) 122 { 123 pid_t pid; 124 int stat; 125 while( (pid = waitpid(-1,&stat,WNOHANG))>0) 126 printf("child %d terminated\n", pid); 127 return; 128 } 129 void ser_destructor(void *ptr) { 130 free(ptr); 131 printf("one of the tsd end:%d\n",pthread_self()); 132 } 133 void service_once(void) { 134 pthread_key_create(&ser_key, ser_destructor); 135 } 136 137 static void *doit(void *arg) { 138 readline *tsd; 139 if(pthread_detach(pthread_self())!=0) { 140 perror("pthread_detach:error\n"); 141 exit(-1); 142 } 143 pthread_once(&ser_once,service_once); 144 if( (tsd=pthread_getspecific(ser_key)) == NULL){ 145 tsd=calloc(1,sizeof(readline)); 146 pthread_setspecific(ser_key,tsd); 147 tsd->sockfd=*( (int*)arg); 148 } 149 //printf("%d\n",tsd->sockfd); 150 echo_name(tsd); 151 str_echo( tsd); 152 if(close(*( (int*)arg))==-1){ 153 perror("close:error\n"); 154 exit(-1); 155 } 156 pthread_exit(0); 157 return; 158 }
客戶端:my_client4.c
1 #include <sys/socket.h> 2 #include <sys/types.h> 3 #include <netinet/in.h> 4 #include <unistd.h> 5 #include <stdio.h> 6 #include <string.h> 7 #include <errno.h> 8 #include <sys/select.h> 9 #include <sys/time.h> /*there is the time struct in the select variate*/ 10 11 #define UPORT 8088 /*This is the port number used by me */ 12 #define MAXLINE 255 13 14 void str_cli(FILE *fp, int sockfd) { 15 char recvline[MAXLINE],sendline[MAXLINE],buf[MAXLINE+1];/*recv or send use a buffer array*/ 16 fd_set rset; 17 int maxfdp,n; 18 int stdineof=0; 19 FD_ZERO(&rset); 20 for(;;){ 21 if(stdineof==0){ 22 FD_SET(fileno(fp),&rset); 23 } 24 FD_SET(sockfd,&rset); 25 maxfdp=(fileno(fp)>sockfd)?fileno(fp):sockfd+1; 26 if(select(maxfdp, &rset, NULL, NULL, NULL)==-1) { 27 perror("select error\n"); 28 exit(-1); 29 } 30 if(FD_ISSET(sockfd,&rset)){ 31 if( (n=read(sockfd, recvline, MAXLINE)) <= 0 ) { 32 if(stdineof==1){ 33 printf("you have terminated the conection with server!\n"); 34 return;/*normal termination*/ 35 } 36 else{ 37 printf("server terminated prematurely!\n"); 38 exit(0); 39 } 40 } 41 write(fileno(stdout),recvline,n); 42 } 43 if(FD_ISSET(fileno(fp),&rset)){ 44 memset(buf,0,sizeof(buf)); 45 if( (n=read(fileno(fp), sendline, MAXLINE))<= 0 ) { 46 stdineof=1; 47 shutdown(sockfd,SHUT_WR);/*send FIN*/ 48 FD_CLR(fileno(fp),&rset); 49 continue; 50 } 51 sendline[strlen(sendline)]='\0'; 52 if(write(sockfd, sendline, n)==-1){ 53 perror("write sockfd:error!\n"); 54 exit(-1); 55 } 56 //memset(buf,0,sizeof(buf)); 57 } 58 } 59 } 60 61 62 int main(int argc, char **argv) 63 { 64 int sockfd, n; 65 struct sockaddr_in servaddr; 66 67 if (argc != 2){ 68 perror("usage: a.out <IPaddress>"); 69 exit(-1); 70 } 71 72 if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ 73 perror("socket error"); 74 exit(-1); 75 } 76 77 bzero(&servaddr, sizeof(servaddr)); 78 servaddr.sin_family = AF_INET; 79 servaddr.sin_port = htons(UPORT); /* daytime server */ 80 if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){ 81 printf("inet_pton error for %s", argv[1]); 82 exit(-1); 83 } 84 85 if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){ 86 perror("connect error"); 87 exit(-1); 88 } 89 str_cli(stdin,sockfd); /*do it all*/ 90 91 exit(0); 92 }
運行截圖:
編譯:gcc -pthread my_client4.c -o my_client4
Gcc -pthread my_server4.c -o my_server4
服務端運行:./my_server3
客戶端1運行:./my_client3 192.168.1.128
客戶端2運行:./my_client3 192.168.1.128
本實驗是在兩臺虛擬機上操做
服務端ip:192.168.1.128
客戶端1 ip:192.168.1.119
客戶端2 ip:192.168.1.128
服務端實現:利用線程專用數據TSD,並設置析構函數ser_destructor當線程退出時調用。經過listen套接字,爲每一個客戶建立一個線程,並經過線程專用數據存儲客戶名與套接字描述符。
客戶端實現:利用I/O複用,設置須要等待的描述符集,標準輸入描述符與socket描述符,爲每一個須要監聽的動做設置相應的打開位,當select返回時檢測是哪個描述符可讀,因而進行相應的操做。
1、服務端:
2、客戶端:
總結:
一、 本做業的服務端相對於做業三,稍有改動。因爲在做業三中客戶端是利用fets來循環獲取標準輸入的字符串,並在讀入後在最後一個位置加上\0的字符串結束符,因此當寫給服務端時,服務端沒必要給字符串加結束符。可是因爲在本次做業中客戶端直接從stdin中讀數據,而後寫給服務端,因此在服務端處理時須要加字符串結束符\0,以避免多於的字符返回回來,或者說處理的時候出錯。
二、 經過n來記錄read, write所操做的字節數,來肯定字符串邊界。
三、 注意strlen,與sizeof區別,前者爲字符串長度,後者爲內存大小,數組中即爲數組的長度。