# 網路程式設計 HW10 用Select實現多工作業 學生: 1084837廖經翰 ## 練習1. 一送一收,觀察檢查表的運作 這次的作業練習1是利用檢查表與select()來處理socket,client會每隔10秒傳送 How are you? 給server,而server一開始會先初始化檢查表,再來會把client_sd加入到檢查表裡面,接著會用select()並且等待socker進來,最後再去查看檢查表裡面的內容,如果檢查表裡面有client_sd的話就把從client傳送過來的訊息給接收並且印出來 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/HJramXAV2.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/HyuMNmREh.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXRECV 1024 int main() { //set WSA and initial WSAStartup SOCKET serv_sd , clnt_sd; WSADATA wsadata; fd_set readfds; struct sockaddr_in serv , clnt; int i , n; int activity; char str[100] = "I love NP!"; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 serv_sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //綁定server結構裡設定的資訊 bind( serv_sd , (LPSOCKADDR) &serv , sizeof(serv) ); listen( serv_sd , 3 ); int len_clnt = sizeof( clnt ); printf( "TCP server waits for client...\n"); clnt_sd = accept( serv_sd , (LPSOCKADDR) &clnt , &len_clnt ); printf( "client is connected: socket fd is %d , ip is %s , port is %d\n" , clnt_sd , inet_ntoa(clnt.sin_addr) , ntohs(clnt.sin_port) ); while(1) { //初始化fd printf( "[1] clear the socket fd set...\n" ); FD_ZERO( &readfds ); //把client_sd設定fd printf( "[2] add client socket to fd set...\n" ); FD_SET( clnt_sd , &readfds ); //使用select(),並且等待socket進來 printf( "[3] call select() and waiting...\n" ); activity = select( 0 , &readfds , NULL , NULL , NULL ); //查看使用select()的結果 printf( "[4] wake up from select(): %d\n" , activity ); //如果select()的結果為錯誤訊息的話,就結束程式 if( activity == SOCKET_ERROR ) { printf( "select() call failed with error code: %d\n" , WSAGetLastError() ); exit( EXIT_FAILURE ); } if( FD_ISSET( clnt_sd , &readfds ) ) { //接收訊息 n = recv( clnt_sd , str , MAXRECV , 0 ); printf( "recv from client: %s\n" , str ); if( n <= 0 ) { printf( "connection closed...\n" ); break; } } } //關閉TCP socket closesocket(serv_sd); closesocket(clnt_sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```c= #include<stdio.h> #include<winsock.h> int main() { //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str[1024] = "How are you?"; char str_r[1024]; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //連接至server connect( sd , (LPSOCKADDR) &serv , sizeof(serv) ); printf( "client has connected to server...\n" ); while(1) { //緩衝時間10秒 Sleep(10000); //每隔10秒就傳送訊息 send( sd , str , strlen(str)+1 , 0 ); printf( "send: %s (in 10 sec)\n" , str ); } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 練習2. 增加為兩個client,server會echo 作業練習2是需要把原本server只服務一個client,變成需要服務兩個client,然後利用檢查表與select()來處理socket,client1和client2都是每隔10秒傳送 How are you? 給server,並且接收server送過來的訊息,而server要處理兩個client,一開始會先初始化檢查表,再來會把client1_sd和client2_sd設定到檢查表裡面,接著會用select()並且等待socker進來,最後要去查看client1_sd和client2_sd有沒有設定檢查表,如果client1_sd和client2_sd都有設定檢查表的話,就接收client送過來的訊息,並且把該訊息再回傳給client ( client.c 程式只有一個,但要開兩次也就是 client1 與 client2 ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/ByKGq7RV3.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/H1MrcX042.png" /></center> <center>client1</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/S1OUcXRV2.png" /></center> <center>client2</center> <br> server程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXRECV 1024 int main() { //set WSA and initial WSAStartup SOCKET serv_sd , clnt1_sd , clnt2_sd; WSADATA wsadata; fd_set readfds; struct sockaddr_in serv , clnt; int i , n , max_clients = 30 , activity; char str[MAXRECV]; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 serv_sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //綁定server結構裡設定的資訊 bind( serv_sd , (LPSOCKADDR) &serv , sizeof(serv) ); listen( serv_sd , 3 ); int len_clnt = sizeof( clnt ); printf( "TCP server waits for client...\n"); //client1的設定 clnt1_sd = accept( serv_sd , (LPSOCKADDR) &clnt , &len_clnt ); printf( "client 1 is connected: socket fd is %d , ip is %s , port is %d\n" , clnt1_sd , inet_ntoa(clnt.sin_addr) , ntohs(clnt.sin_port) ); //client2的設定 clnt2_sd = accept( serv_sd , (LPSOCKADDR) &clnt , &len_clnt ); printf( "client 2 is connected: socket fd is %d , ip is %s , port is %d\n" , clnt2_sd , inet_ntoa(clnt.sin_addr) , ntohs(clnt.sin_port) ); while(1) { //初始化fd printf( "[1] clear the socket fd set...\n" ); FD_ZERO( &readfds ); //把client1和client2的socket設定fd printf( "[2] add client socket to fd set...\n" ); FD_SET( clnt1_sd , &readfds ); FD_SET( clnt2_sd , &readfds ); //使用select(),並且等待socket進來 printf( "[3] call select() and waiting...\n" ); activity = select( 0 , &readfds , NULL , NULL , NULL ); //查看使用select()的結果 printf( "[4] wake up from select(): %d\n" , activity ); //如果select()的結果為錯誤訊息的話,就結束程式 if( activity == SOCKET_ERROR ) { printf( "select() call failed with error code: %d\n" , WSAGetLastError() ); exit( EXIT_FAILURE ); } //client1的FD_ISSET檢查 if( FD_ISSET( clnt1_sd , &readfds ) ) { //接收訊息 n = recv( clnt1_sd , str , MAXRECV , 0 ); printf( "recv from client 1: %s\n" , str ); //從client1接收訊息之後再回送回去 send( clnt1_sd , str , strlen(str)+1 , 0 ); } //client2的FD_ISSET檢查 if( FD_ISSET( clnt2_sd , &readfds ) ) { //接收訊息 n = recv( clnt2_sd , str , MAXRECV , 0 ); printf( "recv from client 2: %s\n" , str ); //從client2接收訊息之後再回送回去 send( clnt2_sd , str , strlen(str)+1 , 0 ); } } //關閉TCP socket closesocket(serv_sd); closesocket(clnt1_sd); closesocket(clnt2_sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXLINE 1024 int main() { //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str[1024] = "How are you?"; char str_r[1024]; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //連接至server connect( sd , (LPSOCKADDR) &serv , sizeof(serv) ); printf( "client has connected to server...\n" ); while(1) { //緩衝時間10秒 Sleep(10000); //每隔10秒就傳送訊息 send( sd , str , strlen(str)+1 , 0 ); printf( "send: %s (in 10 sec)\n" , str ); recv( sd , str_r , MAXLINE , 0 ); printf( "recv: %s\n" , str_r ); } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 練習3. 服務多個client的echo server 作業練習3是要讓server服務多個client,可以連線過來的client上限我設10位,然後client就像練習2的client一樣為每隔10秒傳送 How are you? 給server,並且接收server送過來的訊息,而server可以利用檢查表與select()來處理更多的client socket,並且client可以隨時連進server,不一定要server一開始就連線,然後當有client中斷連線的時候,server會給出通知,有新的client連進來server也會給出通知,最後會去查看檢查表,如果該client有設定檢查表的話,就接收client送過來的訊息,並且把該訊息再回傳給client ( client.c 程式只有一個,可以開多次來模擬多個client ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/rJcgH40E2.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://hackmd.io/_uploads/S1sM9HCE2.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXRECV 1024 #define MAXCLI 10 //client連線人數上限為10 int main() { //set WSA and initial WSAStartup SOCKET serv_sd , new_socket , clnt_sd[MAXCLI]; WSADATA wsadata; fd_set readfds; struct sockaddr_in serv , clnt; int i , n , activity , client_num = 0; char str[MAXRECV]; //初始化所有的client for( int i = 0 ; i < MAXCLI ; i++ ) { clnt_sd[i] = 0; } //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 serv_sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = INADDR_ANY; serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //綁定server結構裡設定的資訊 bind( serv_sd , (LPSOCKADDR) &serv , sizeof(serv) ); listen( serv_sd , 3 ); while(1) { //初始化fd printf( "[1] clear the socket fd set...\n" ); FD_ZERO( &readfds ); //把server的socket設定到fd printf( "[2] add server socket to fd set...\n" ); FD_SET( serv_sd , &readfds ); //把所有client的socket設定到fd printf( "[2] add client[] socket to fd set...\n" ); for( int i = 0 ; i < MAXCLI ; i++ ) { //如果有client進來,就把他加入到fd裡面 if( clnt_sd[i] > 0 ) { FD_SET( clnt_sd[i] , &readfds ); } } //使用select(),並且等待socket進來 printf( "[3] call select() and waiting...\n" ); activity = select( 0 , &readfds , NULL , NULL , NULL ); //查看使用select()的結果 printf( "[4] wake up from select(): %d\n" , activity ); //如果select()的結果為錯誤訊息的話,就結束程式 if( activity == SOCKET_ERROR ) { printf( "select() call failed with error code: %d\n" , WSAGetLastError() ); exit( EXIT_FAILURE ); } //server的FD_ISSET檢查 if( FD_ISSET( serv_sd , &readfds ) ) { //新的client加入 int len_clnt = sizeof( clnt ); new_socket = accept( serv_sd , (struct sockaddr*) &clnt , (int*) &len_clnt ); printf( "new connection: socket fd is %d , ip is %s , port is %d\n" , new_socket , inet_ntoa(clnt.sin_addr) , ntohs(clnt.sin_port) ); //如果有新的client加入,就把一個位置給該client,並且增加目前有的client人數 for( int i = 0 ; i < MAXCLI ; i++ ) { if( clnt_sd[i] == 0 ) { clnt_sd[i] = new_socket; client_num++; printf( "The %d client socket is in clnt_sd[%d]\n" , client_num , i ); break; } } } //所有client的FD_ISSET檢查 for( int i = 0 ; i < MAXCLI ; i++ ) { if( FD_ISSET( clnt_sd[i] , &readfds ) ) { //接收訊息 n = recv( clnt_sd[i] , str , MAXRECV , 0 ); //如果該client發生錯誤,就關閉該client的socket或是輸出錯誤訊息 if( n == SOCKET_ERROR ) { int error_code = WSAGetLastError(); if( error_code == WSAECONNRESET ) { printf( "Host disconnected unexpectedly...\n" ); closesocket( clnt_sd[i] ); clnt_sd[i] = 0; client_num--; } else { printf( "recv failed with error code: %d" , error_code ); } } //如果該client停止連線,就關閉該client的socket與初始化client位置 if( n == 0 ) { printf( "Host disconnected...\n" ); closesocket( clnt_sd[i] ); clnt_sd[i] = 0; client_num--; } //如果收到訊息,就印出訊息內容,並且回傳訊息內容給該client if( n > 0 ) { printf( "recv from and echo to clnt_sd[%d]: %s\n" , i , str ); send( clnt_sd[i] , str , strlen(str)+1 , 0 ); } } } } //關閉TCP socket closesocket(serv_sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXLINE 1024 int main() { //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str[1024] = "How are you?"; char str_r[1024]; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //連接至server connect( sd , (LPSOCKADDR) &serv , sizeof(serv) ); printf( "client has connected to server...\n" ); while(1) { //緩衝時間10秒 Sleep(10000); //每隔10秒就傳送訊息 send( sd , str , strlen(str)+1 , 0 ); printf( "send: %s (in 10 sec)\n" , str ); recv( sd , str_r , MAXLINE , 0 ); printf( "recv: %s\n" , str_r ); } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 練習4. 使用writefds來傳送 作業練習4為原本一直使用的檢查表是readfds,這次要新增一個新的檢查表為writefds,client就像之前練習的client一樣為每隔10秒傳送 How are you? 給server,並且接收server送過來的訊息,而server會利用readfds檢查表與writefds檢查表和select()來處理client socket,設定writefds檢查表可以當做要從server回傳訊息給client的socket有哪些,當有新的client連進server的時候,會先把該client設定為readfds檢查表 ( flag為0 ),然後去查看每個client有沒有設定readfds檢查表,如果有的話就會去接收該client傳來的訊息,當如果收到訊息,就會印出訊息內容,並且設定該client的flag為1 ( 預設flag為0 ),當flag為1的時候就可以把該client設定writefds檢查表,而flag為0的話就把該client設定readfds檢查表,最後會去查看writefds檢查表,如果該client有設定writefds檢查表的話,就讓之前接收到的訊息,從server在回傳給client,並且把flag設定為0 ( client.c 程式只有一個,可以開多次來模擬多個client ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/S1mhJHCNh.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://hackmd.io/_uploads/SJb1lrCVn.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXRECV 1024 #define MAXCLI 10 //client連線人數上限為10 int main() { //set WSA and initial WSAStartup SOCKET serv_sd , new_socket , clnt_sd[MAXCLI]; WSADATA wsadata; fd_set readfds , writefds; struct sockaddr_in serv , clnt; int i , n , activity , client_num = 0; int ready_to_send[MAXCLI]; char str[MAXRECV]; //初始化所有的client與ready_to_send名單 for( int i = 0 ; i < MAXCLI ; i++ ) { ready_to_send[i] = 0; clnt_sd[i] = 0; } //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 serv_sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = INADDR_ANY; serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //綁定server結構裡設定的資訊 bind( serv_sd , (LPSOCKADDR) &serv , sizeof(serv) ); listen( serv_sd , 3 ); while(1) { //初始化fd printf( "[1] clear the socket fd set...\n" ); FD_ZERO( &readfds ); FD_ZERO( &writefds ); //把server的socket設定到fd printf( "[2] add server socket to fd set...\n" ); FD_SET( serv_sd , &readfds ); //把所有client的socket設定到fd printf( "[2] add client[] socket to fd set...\n" ); for( int i = 0 ; i < MAXCLI ; i++ ) { if( clnt_sd[i] > 0 ) { //如果這個client沒有要傳送訊息,就把他加入到read fd裡面 if( ready_to_send[i] == 0 ) { FD_SET( clnt_sd[i] , &readfds ); } else //如果這個client等等要傳送訊息,就把他加入到write fd裡面 { FD_SET( clnt_sd[i] , &writefds ); } } } //使用select(),並且等待socket進來 printf( "[3] call select() and waiting...\n" ); activity = select( 0 , &readfds , &writefds , NULL , NULL ); //查看使用select()的結果 printf( "[4] wake up from select(): %d\n" , activity ); //如果select()的結果為錯誤訊息的話,就結束程式 if( activity == SOCKET_ERROR ) { printf( "select() call failed with error code: %d\n" , WSAGetLastError() ); exit( EXIT_FAILURE ); } //server的FD_ISSET檢查 if( FD_ISSET( serv_sd , &readfds ) ) { //新的client加入 int len_clnt = sizeof( clnt ); new_socket = accept( serv_sd , (struct sockaddr*) &clnt , (int*) &len_clnt ); printf( "new connection: socket fd is %d , ip is %s , port is %d\n" , new_socket , inet_ntoa(clnt.sin_addr) , ntohs(clnt.sin_port) ); //如果有新的client加入,就把一個位置給該client,並且增加目前有的client人數 for( int i = 0 ; i < MAXCLI ; i++ ) { if( clnt_sd[i] == 0 ) { clnt_sd[i] = new_socket; client_num++; printf( "The %d client socket is in clnt_sd[%d]\n" , client_num , i ); break; } } } //所有client的FD_ISSET檢查 for( int i = 0 ; i < MAXCLI ; i++ ) { if( FD_ISSET( clnt_sd[i] , &readfds ) ) { //接收訊息 n = recv( clnt_sd[i] , str , MAXRECV , 0 ); //如果該client發生錯誤,就關閉該client的socket或是輸出錯誤訊息 if( n == SOCKET_ERROR ) { int error_code = WSAGetLastError(); if( error_code == WSAECONNRESET ) { printf( "Host disconnected unexpectedly...\n" ); closesocket( clnt_sd[i] ); clnt_sd[i] = 0; client_num--; } else { printf( "recv failed with error code: %d" , error_code ); } } //如果該client停止連線,就關閉該client的socket與初始化client位置 if( n == 0 ) { printf( "Host disconnected...\n" ); closesocket( clnt_sd[i] ); clnt_sd[i] = 0; client_num--; } //如果收到訊息,就印出訊息內容,並且把該client設定為等等要傳送訊息 if( n > 0 ) { printf( "recv from clnt_sd[%d]: %s\n" , i , str ); ready_to_send[i] = 1; } } //檢查write fd名單,讓有在write fd名單裡面的client傳送訊息,並且把該client設定為沒有要傳送訊息 if( FD_ISSET( clnt_sd[i] , &writefds ) ) { send( clnt_sd[i] , str , strlen(str)+1 , 0 ); ready_to_send[i] = 0; } } } //關閉TCP socket closesocket(serv_sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXLINE 1024 int main() { //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str[1024] = "How are you?"; char str_r[1024]; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //連接至server connect( sd , (LPSOCKADDR) &serv , sizeof(serv) ); printf( "client has connected to server...\n" ); while(1) { //緩衝時間10秒 Sleep(10000); //每隔10秒就傳送訊息 send( sd , str , strlen(str)+1 , 0 ); printf( "send: %s (in 10 sec)\n" , str ); recv( sd , str_r , MAXLINE , 0 ); printf( "recv: %s\n" , str_r ); } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 練習5. 使用timeout,不卡住,跳出select()作其他事 作業練習5是要讓select()增加一個timeout的參數,client就像之前練習一樣每隔10秒傳送 How are you? 給server,並且接收server送過來的訊息,而server一開始會先初始化兩個檢查表,再來會把client設定到readfds檢查表裡面,接著會用select()並且設定timeout的參數,設定的時間為5秒等待socker進來,如果超過5秒的話就直接跳過等待,並且執行下面的程式,執行下面的程式都跟練習4一樣 ( client.c 程式只有一個,可以開多次來模擬多個client ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/HyGmQrC42.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/B16vQSAV2.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXRECV 1024 #define MAXCLI 10 //client連線人數上限為10 int main() { //set WSA and initial WSAStartup SOCKET serv_sd , new_socket , clnt_sd[MAXCLI]; WSADATA wsadata; fd_set readfds , writefds; TIMEVAL timeout; struct sockaddr_in serv , clnt; int i , n , activity , client_num = 0; int ready_to_send[MAXCLI]; char str[MAXRECV]; //初始化所有的client與ready_to_send名單 for( int i = 0 ; i < MAXCLI ; i++ ) { ready_to_send[i] = 0; clnt_sd[i] = 0; } //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 serv_sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = INADDR_ANY; serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //綁定server結構裡設定的資訊 bind( serv_sd , (LPSOCKADDR) &serv , sizeof(serv) ); listen( serv_sd , 3 ); while(1) { //初始化fd printf( "[1] clear the socket fd set...\n" ); FD_ZERO( &readfds ); FD_ZERO( &writefds ); //把server的socket設定到fd printf( "[2] add server socket to fd set...\n" ); FD_SET( serv_sd , &readfds ); //把所有client的socket設定到fd printf( "[2] add client[] socket to fd set...\n" ); for( int i = 0 ; i < MAXCLI ; i++ ) { if( clnt_sd[i] > 0 ) { //如果這個client沒有要傳送訊息,就把他加入到read fd裡面 if( ready_to_send[i] == 0 ) { FD_SET( clnt_sd[i] , &readfds ); } else //如果這個client等等要傳送訊息,就把他加入到write fd裡面 { FD_SET( clnt_sd[i] , &writefds ); } } } //設定timeout的秒數與使用select(),並且等待socket進來 printf( "[3] call select() and waiting...\n" ); timeout.tv_sec = 5; timeout.tv_usec = 0; activity = select( 0 , &readfds , &writefds , NULL , &timeout ); //查看使用select()的結果 printf( "[4] wake up from select(): %d\n" , activity ); //如果select()的結果為錯誤訊息的話,就結束程式 if( activity == SOCKET_ERROR ) { printf( "select() call failed with error code: %d\n" , WSAGetLastError() ); exit( EXIT_FAILURE ); } //如果select()的結果為0的話,表示timeout了 if( activity == 0 ) { printf( "No message: select() has waited for 5 sec...\n" ); } //server的FD_ISSET檢查 if( FD_ISSET( serv_sd , &readfds ) ) { //新的client加入 int len_clnt = sizeof( clnt ); new_socket = accept( serv_sd , (struct sockaddr*) &clnt , (int*) &len_clnt ); printf( "new connection: socket fd is %d , ip is %s , port is %d\n" , new_socket , inet_ntoa(clnt.sin_addr) , ntohs(clnt.sin_port) ); //如果有新的client加入,就把一個位置給該client,並且增加目前有的client人數 for( int i = 0 ; i < MAXCLI ; i++ ) { if( clnt_sd[i] == 0 ) { clnt_sd[i] = new_socket; client_num++; printf( "The %d client socket is in clnt_sd[%d]\n" , client_num , i ); break; } } } //所有client的FD_ISSET檢查 for( int i = 0 ; i < MAXCLI ; i++ ) { if( FD_ISSET( clnt_sd[i] , &readfds ) ) { //接收訊息 n = recv( clnt_sd[i] , str , MAXRECV , 0 ); //如果該client發生錯誤,就關閉該client的socket或是輸出錯誤訊息 if( n == SOCKET_ERROR ) { int error_code = WSAGetLastError(); if( error_code == WSAECONNRESET ) { printf( "Host disconnected unexpectedly...\n" ); closesocket( clnt_sd[i] ); clnt_sd[i] = 0; client_num--; } else { printf( "recv failed with error code: %d" , error_code ); } } //如果該client停止連線,就關閉該client的socket與初始化client位置 if( n == 0 ) { printf( "Host disconnected...\n" ); closesocket( clnt_sd[i] ); clnt_sd[i] = 0; client_num--; } //如果收到訊息,就印出訊息內容,並且把該client設定為等等要傳送訊息 if( n > 0 ) { printf( "recv from clnt_sd[%d]: %s\n" , i , str ); ready_to_send[i] = 1; } } //檢查write fd名單,讓有在write fd名單裡面的client傳送訊息,並且把該client設定為沒有要傳送訊息 if( FD_ISSET( clnt_sd[i] , &writefds ) ) { send( clnt_sd[i] , str , strlen(str)+1 , 0 ); ready_to_send[i] = 0; } } } //關閉TCP socket closesocket(serv_sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```c= #include<stdio.h> #include<winsock.h> #define MAXLINE 1024 int main() { //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str[1024] = "How are you?"; char str_r[1024]; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //連接至server connect( sd , (LPSOCKADDR) &serv , sizeof(serv) ); printf( "client has connected to server...\n" ); while(1) { //緩衝時間10秒 Sleep(10000); //每隔10秒就傳送訊息 send( sd , str , strlen(str)+1 , 0 ); printf( "send: %s (in 10 sec)\n" , str ); recv( sd , str_r , MAXLINE , 0 ); printf( "recv: %s\n" , str_r ); } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 本週心得 這次老師教的課程為select()與檢查表的應用,我覺得select是非攔阻模式的優點之組合,不但效能上的使用比非攔阻模式還來的低,也可以使用非攔阻模式的功能,也就是不會一直卡在等待socket的狀態,如果真的要說select()的缺點的話,那應該就是程式上可能要寫比較多設定與程式碼,要初始化檢查表,並且把socket設定檢查表之類的,所以每個方式都有各自的優缺點,至於使用者要使用哪一種方式的話就要依據使用者的需求來使用了,這次老師上課採用讓同學自願上去台上分享作業,我覺得非常好,不但可以減少時間上的使用,也可以讓一些作業寫得好的同學自己上去分享,這樣作業寫不好的同學也不會上去,不然發生上次的事情,我在台下也很傻眼,最近的教學內容老師好像都沒有講到有關於UDP的應用,希望之後老師可以講解一下UDP的其他應用,謝謝老師這周所教的select(),這樣我又學到了一種有關於網路程式設計的方法,說不定以後進入職場會有幫助,我自己收穫非常多,也非常期待老師之後的課程與教學,這次的作業雖然沒有到太多,但還是有一點多,我覺得作業的數量最好為4個作業,不多也不少非常剛好,這樣同學們寫的開心,助教大哥也改的開心,大家一起開心,謝謝老師與助教大哥 ### 作者: 廖經翰