# 網路程式設計 HW11 多緒網路程式作業 學生: 1084837廖經翰 ## 進階練習1. Echo Server多工 這次的作業進階練習1是利用thread來做可以多工的server,server最多可以讓10個client連線,每當有一個client連進來的時候,server就會用一個thread來服務該client,來達到多工的方式,並且還會顯示哪一個thread會去服務哪一個client,還有顯示server目前用thread服務幾個client ( client.c 程式只有一個,可以開多次來模擬多個client ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://hackmd.io/_uploads/rJiHxMIrh.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://hackmd.io/_uploads/S108eGIS3.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<winsock.h> #include<stdlib.h> #define MaxClient 10 void *thread_main(void *arg); //thread的結構 struct Thread_Args { //存client的位子 int clnt_socket[MaxClient]; //存第幾個client int clnt_index; //存共有幾個client int clnt_total; //存該socket int sock; }; int main() { //thread結構 struct Thread_Args *thread_arg; //thread id DWORD thread_id; //set WSA and initial WSAStartup WSADATA wsadata; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int serv_sock , clnt_socket; int i , n , client_len; //配置記憶體給thread結構 thread_arg = (struct Thread_Args *) malloc( sizeof(struct Thread_Args) ); //初始化全部的client位子 for( int i = 0 ; i < MaxClient ; i++ ) { thread_arg -> clnt_socket[i] = 0; } //初始化存共有幾個client的變數 thread_arg -> clnt_total = 0; //呼叫WSAStartup()註冊WinSock DLL的使用 if( WSAStartup( 0x101 , &wsadata ) != 0 ) { printf( "WSAStartup() failed" ); exit(1); } //使用TCP協定 if( ( serv_sock = socket( PF_INET , SOCK_STREAM , IPPROTO_TCP) ) < 0 ) { printf( "socket() failed" ); } //初始化server memset( &server_addr , 0 , sizeof(server_addr) ); //設定server的結構 server_addr.sin_addr.s_addr = htonl( INADDR_ANY ); server_addr.sin_family = AF_INET; server_addr.sin_port = htons( 5678 ); //綁定server結構裡設定的資訊 if( (bind( serv_sock , (struct sockaddr*) &server_addr , sizeof(server_addr) )) < 0 ) { printf( "bind() failed" ); } if( listen( serv_sock , 3 ) < 0 ) { printf( "listen() failed" ); } printf( "server is waiting for clients...\n" ); while(1) { client_len = sizeof( client_addr ); //當如果有client進來就處理該client,就處理該client for( i = 0 ; i < MaxClient ; i++ ) { if( thread_arg -> clnt_socket[i] == 0 ) { break; } } //紀錄該client是第幾個人 thread_arg -> clnt_index = i; if( ( clnt_socket = accept( serv_sock , (struct sockaddr*) &client_addr , &client_len ) ) <= 0 ) { printf( "accept() failed" ); } //把連線的socket分配給thread結構的sock,之後要傳到function裡面 thread_arg -> sock = clnt_socket; //把連線的socket分配給thread結構裡client的位子 thread_arg -> clnt_socket[i] = clnt_socket; //有client人進來,總人數加一 thread_arg -> clnt_total++; //建立一個thread來服務該client if( CreateThread( NULL , 0 , (LPTHREAD_START_ROUTINE) thread_main , thread_arg , 0 , (LPDWORD) &thread_id ) == NULL ) { printf( "CreateThread() failed" ); } printf( "New client with thread id: %1d and socket: %d ( Current client: %d )\n" , thread_id , thread_arg -> clnt_socket[i] , thread_arg -> clnt_total ); } //關閉TCP socket closesocket( serv_sock ); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } void *thread_main( void *thread_arg ) { int client_idx , clnt_sock_i , recv_msg_size; char buffer[1000]; //紀錄該client的socket clnt_sock_i = ( (struct Thread_Args *) thread_arg ) -> sock; //紀錄該client是第幾個人 client_idx = ( (struct Thread_Args *) thread_arg ) -> clnt_index; while(1) { //接收資訊 recv_msg_size = recv( clnt_sock_i , buffer , 1000 , 0 ); //如果有人中斷連線或是離開 if( recv_msg_size <= 0 ) { //總人數減一 ( (struct Thread_Args *) thread_arg ) -> clnt_total--; //初始化該client的位子 ( (struct Thread_Args *) thread_arg ) -> clnt_socket[client_idx] = 0; printf( "connection[ socket: %d ] closed... ( Current client: %d )\n" , clnt_sock_i , ( (struct Thread_Args *) thread_arg ) -> clnt_total ); break; } //傳送資訊 send( clnt_sock_i , buffer , strlen(buffer)+1 , 0 ); } //關閉該client的socket closesocket( clnt_sock_i ); //初始化該client的位子 ( (struct Thread_Args *) thread_arg ) -> clnt_socket[client_idx] = 0; return( NULL ); } ``` client程式碼: ```c= #include<stdio.h> #include<string.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[100] = "I love NP!"; //呼叫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) ); while(1) { strcpy( str , "How are you?" ); //傳送訊息給server n = send( sd , str , strlen(str)+1 , 0 ); printf( "send: %s\n" , str ); if( n < 0 ) { break; } //緩衝時間1秒 Sleep( 1000 ); //接收訊息 n = recv( sd , str , MAXLINE , 0 ); if( n <= 0 ) { break; } printf( "recv: %s\n" , str ); } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 進階練習2. 聊天室Server多工 作業進階練習2是利用thread來做可以多工的server聊天室,也就是server可以利用thread來創建一間聊天室,並且可以同時創建很多間聊天室來達到多工的功能,一間聊天室可以服務兩個client,所以client.c會模擬兩個client來測試,每當執行client.c的時候就會有兩個client連入server,這樣server就用一個thread來服務這兩個client變成一個聊天室,並且可以同時開很多聊天室,為了要讓client可以自由發言,所以使用之前教的非阻攔模式功能來讓client沒有發言的限制 ( client.c 程式只有一個,但一個程式模擬兩個client ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://hackmd.io/_uploads/ryEurG8Sn.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://hackmd.io/_uploads/BJptSzIr2.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<winsock.h> #include<stdlib.h> void *thread_main(void *arg); //thread的結構 struct Thread_Args { int client_socket1; int client_socket2; }; int main() { //thread結構 struct Thread_Args *thread_arg; //thread id DWORD thread_id; //set WSA and initial WSAStartup WSADATA wsadata; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int serv_sock , clnt_sock1 , clnt_sock2 ; int i , n , client_len; //呼叫WSAStartup()註冊WinSock DLL的使用 if( WSAStartup( 0x101 , &wsadata ) != 0 ) { printf( "WSAStartup() failed" ); exit(1); } //使用TCP協定 if( ( serv_sock = socket( PF_INET , SOCK_STREAM , IPPROTO_TCP) ) < 0 ) { printf( "socket() failed" ); } //初始化server memset( &server_addr , 0 , sizeof(server_addr) ); //設定server的結構 server_addr.sin_addr.s_addr = htonl( INADDR_ANY ); server_addr.sin_family = AF_INET; server_addr.sin_port = htons( 5678 ); //綁定server結構裡設定的資訊 if( (bind( serv_sock , (struct sockaddr*) &server_addr , sizeof(server_addr) )) < 0 ) { printf( "bind() failed" ); } if( listen( serv_sock , 3 ) < 0 ) { printf( "listen() failed" ); } printf( "server is waiting for clients...\n" ); while(1) { client_len = sizeof( client_addr ); //處理client 1的連接 if( ( clnt_sock1 = accept( serv_sock , (struct sockaddr*) &client_addr , &client_len ) ) <= 0 ) { printf( "accept() failed" ); } //處理client 2的連接 if( ( clnt_sock2 = accept( serv_sock , (struct sockaddr*) &client_addr , &client_len ) ) <= 0 ) { printf( "accept() failed" ); } //配置記憶體給thread結構 thread_arg = (struct Thread_Args *) malloc( sizeof(struct Thread_Args) ); //把client1的socket放入thread結構裡面 thread_arg -> client_socket1 = clnt_sock1; //把client2的socket放入thread結構裡面 thread_arg -> client_socket2 = clnt_sock2; //建立一個thread來服務兩個client if( CreateThread( NULL , 0 , (LPTHREAD_START_ROUTINE) thread_main , thread_arg , 0 , (LPDWORD) &thread_id ) == NULL ) { printf( "CreateThread() failed" ); } printf( "New chat-room with thread id: %1d and socket1: %d or socket2: %d\n" , thread_id , clnt_sock1 , clnt_sock2 ); } //關閉TCP socket closesocket( serv_sock ); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } void *thread_main( void *thread_arg ) { int client_sock1 , client_sock2 , recv_msg_size; char buffer[1000]; int nerror; //紀錄該client1的socket client_sock1 = ( (struct Thread_Args *) thread_arg ) -> client_socket1; //紀錄該client2的socket client_sock2 = ( (struct Thread_Args *) thread_arg ) -> client_socket2; //當imode為1的時候,設定為非攔阻模式 u_long imode = 1; //設定client1的socket為為非攔阻模式 ioctlsocket( client_sock1 , FIONBIO , &imode ); //設定client2的socket為為非攔阻模式 ioctlsocket( client_sock2 , FIONBIO , &imode ); while(1) { //接收client1的資訊 if( ( recv_msg_size = recv( client_sock1 , buffer , 1000 , 0 ) ) > 0 ) { //傳送資訊給client2 send( client_sock2 , buffer , strlen(buffer)+1 , 0 ); } nerror = WSAGetLastError(); //如果client1中斷連線或是離開,就給出訊息並且結束 if( nerror != WSAEWOULDBLOCK && nerror != 0 ) { printf( "Disconnected! error code: %d\n" , nerror ); break; } //接收client2的資訊 if( ( recv_msg_size = recv( client_sock2 , buffer , 1000 , 0 ) ) > 0 ) { //傳送資訊給client1 send( client_sock1 , buffer , strlen(buffer)+1 , 0 ); } nerror = WSAGetLastError(); //如果client2中斷連線或是離開,就給出訊息並且結束 if( nerror != WSAEWOULDBLOCK && nerror != 0 ) { printf( "Disconnected! error code: %d\n" , nerror ); break; } //緩衝時間1秒 Sleep( 1000 ); } //關閉client1的socket closesocket( client_sock1 ); //關閉client2的socket closesocket( client_sock2 ); //釋放該thread的結構 free( thread_arg ); return( NULL ); } ``` client程式碼: ```c= #include<stdio.h> #include<string.h> #include<winsock.h> #define MAXLINE 1024 int main() { //set WSA and initial WSAStartup SOCKET sd1 , sd2; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str1[100] = "I love NP!"; char str2[100] = "I loven Algo!"; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用TCP協定 sd1 = socket( AF_INET , SOCK_STREAM , 0 ); //使用TCP協定 sd2 = 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( sd1 , (LPSOCKADDR) &serv , sizeof(serv) ); connect( sd2 , (LPSOCKADDR) &serv , sizeof(serv) ); while(1) { //client1傳送訊息給server n = send( sd1 , str1 , strlen(str1)+1 , 0 ); printf( "send: %s\n" , str1 ); if( n < 0 ) { break; } //緩衝時間1秒 Sleep( 1000 ); //接收client2的訊息 n = recv( sd2 , str1 , MAXLINE , 0 ); if( n <= 0 ) { break; } printf( "recv: %s\n" , str1 ); //client2傳送訊息給server n = send( sd2 , str2 , strlen(str2)+1 , 0 ); printf( "send: %s\n" , str2 ); if( n < 0 ) { break; } //緩衝時間1秒 Sleep( 1000 ); //接收client1的訊息 n = recv( sd1 , str2 , MAXLINE , 0 ); if( n <= 0 ) { break; } printf( "recv: %s\n" , str2 ); } //關閉TCP socket closesocket(sd1); closesocket(sd2); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 進階練習3. Client多工 作業進階練習3是要去改聊天室的client,要讓client利用thread來達到多工的功能,也就是讓client可以輸入文字的同時也可以顯示聊天室的文字內容,server是用進階練習2的server,而client的main主要會去負責處理文字的輸入與傳送,然後client會使用一個thread,thread主要是負責處理顯示聊天室裡的訊息,這樣子client就可以顯示聊天室中的訊息也可以輸入文字到聊天室裡面 ( client.c 程式只有一個,可以開多次來模擬多個client ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://hackmd.io/_uploads/HJL7qzLr2.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://hackmd.io/_uploads/BJHVcGUB2.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<winsock.h> #include<stdlib.h> void *thread_main(void *arg); //thread的結構 struct Thread_Args { int client_socket1; int client_socket2; }; int main() { //thread結構 struct Thread_Args *thread_arg; //thread id DWORD thread_id; //set WSA and initial WSAStartup WSADATA wsadata; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int serv_sock , clnt_sock1 , clnt_sock2 ; int i , n , client_len; //呼叫WSAStartup()註冊WinSock DLL的使用 if( WSAStartup( 0x101 , &wsadata ) != 0 ) { printf( "WSAStartup() failed" ); exit(1); } //使用TCP協定 if( ( serv_sock = socket( PF_INET , SOCK_STREAM , IPPROTO_TCP) ) < 0 ) { printf( "socket() failed" ); } //初始化server memset( &server_addr , 0 , sizeof(server_addr) ); //設定server的結構 server_addr.sin_addr.s_addr = htonl( INADDR_ANY ); server_addr.sin_family = AF_INET; server_addr.sin_port = htons( 5678 ); //綁定server結構裡設定的資訊 if( (bind( serv_sock , (struct sockaddr*) &server_addr , sizeof(server_addr) )) < 0 ) { printf( "bind() failed" ); } if( listen( serv_sock , 3 ) < 0 ) { printf( "listen() failed" ); } printf( "server is waiting for clients...\n" ); while(1) { client_len = sizeof( client_addr ); //處理client 1的連接 if( ( clnt_sock1 = accept( serv_sock , (struct sockaddr*) &client_addr , &client_len ) ) <= 0 ) { printf( "accept() failed" ); } //處理client 2的連接 if( ( clnt_sock2 = accept( serv_sock , (struct sockaddr*) &client_addr , &client_len ) ) <= 0 ) { printf( "accept() failed" ); } //配置記憶體給thread結構 thread_arg = (struct Thread_Args *) malloc( sizeof(struct Thread_Args) ); //把client1的socket放入thread結構裡面 thread_arg -> client_socket1 = clnt_sock1; //把client2的socket放入thread結構裡面 thread_arg -> client_socket2 = clnt_sock2; //建立一個thread來服務兩個client if( CreateThread( NULL , 0 , (LPTHREAD_START_ROUTINE) thread_main , thread_arg , 0 , (LPDWORD) &thread_id ) == NULL ) { printf( "CreateThread() failed" ); } printf( "New chat-room with thread id: %1d and socket1: %d or socket2: %d\n" , thread_id , clnt_sock1 , clnt_sock2 ); } //關閉TCP socket closesocket( serv_sock ); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } void *thread_main( void *thread_arg ) { int client_sock1 , client_sock2 , recv_msg_size; char buffer[1000]; int nerror; //紀錄該client1的socket client_sock1 = ( (struct Thread_Args *) thread_arg ) -> client_socket1; //紀錄該client2的socket client_sock2 = ( (struct Thread_Args *) thread_arg ) -> client_socket2; //當imode為1的時候,設定為非攔阻模式 u_long imode = 1; //設定client1的socket為為非攔阻模式 ioctlsocket( client_sock1 , FIONBIO , &imode ); //設定client2的socket為為非攔阻模式 ioctlsocket( client_sock2 , FIONBIO , &imode ); while(1) { //接收client1的資訊 if( ( recv_msg_size = recv( client_sock1 , buffer , 1000 , 0 ) ) > 0 ) { //傳送資訊給client2 send( client_sock2 , buffer , strlen(buffer)+1 , 0 ); } nerror = WSAGetLastError(); //如果client1中斷連線或是離開,就給出訊息並且結束 if( nerror != WSAEWOULDBLOCK && nerror != 0 ) { printf( "Disconnected! error code: %d\n" , nerror ); break; } //接收client2的資訊 if( ( recv_msg_size = recv( client_sock2 , buffer , 1000 , 0 ) ) > 0 ) { //傳送資訊給client1 send( client_sock1 , buffer , strlen(buffer)+1 , 0 ); } nerror = WSAGetLastError(); //如果client2中斷連線或是離開,就給出訊息並且結束 if( nerror != WSAEWOULDBLOCK && nerror != 0 ) { printf( "Disconnected! error code: %d\n" , nerror ); break; } //緩衝時間1秒 Sleep( 1000 ); } //關閉client1的socket closesocket( client_sock1 ); //關閉client2的socket closesocket( client_sock2 ); //釋放該thread的結構 free( thread_arg ); return( NULL ); } ``` client程式碼: ```c= #include<stdio.h> #include<string.h> #include<winsock.h> #define MAXLINE 1024 void *thread_main(void *arg); //thread的結構 struct Thread_Args { SOCKET thread_sd; }; int main() { //thread結構 struct Thread_Args *thread_arg; //thread id DWORD thread_id; //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str[1024]; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //配置記憶體給thread結構 thread_arg = (struct Thread_Args *) malloc( sizeof(struct Thread_Args) ); //把socket放入thread結構裡面的socket thread_arg -> thread_sd = sd; //設定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) ); //建立一個thread來服務兩個client if( CreateThread( NULL , 0 , (LPTHREAD_START_ROUTINE) thread_main , thread_arg , 0 , (LPDWORD) &thread_id ) == NULL ) { printf( "CreateThread() failed" ); } //負責文字的輸入與傳送 while(1) { gets( str ); //client傳送訊息給server n = send( sd , str , strlen(str)+1 , 0 ); printf( "send: %s\n" , str ); if( n < 0 ) { printf( "send error...\n" ); } } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } //負責顯示聊天室的訊息 void *thread_main( void *thread_arg ) { SOCKET thread_sock; char str[MAXLINE]; int n; //紀錄client的socket thread_sock = ( (struct Thread_Args *) thread_arg ) -> thread_sd; while(1) { //接收聊天室的訊息 n = recv( thread_sock , str , MAXLINE , 0 ); if( n <= 0 ) { break; } printf( "recv: %s\n" , str ); } //關閉TCP socket closesocket(thread_sock); //結束WinSock DLL的使用 WSACleanup(); return NULL; } ``` ## 本週心得 這次老師教的課程為thread,其實我很早就想要了解與學習thread的應用了,這次終於有機會可以好好的學習有關於thread的使用與講解,原來thread的應用有很多,就好像可以指派很多人去幫忙並且一起解決事情,分工合作的概念,這樣可以讓速度與效能提升不少,而且之前寫的聊天室作業非常陽春,輸入與輸入的程式還要分兩個執行,這樣就感覺很麻煩,但多虧了thread,終於寫出比較像樣的聊天室程式了,而且thread還可以搭配之前老師教的非阻攔模式與select()一起使用,這樣說不定就會有更好的功能與效能也說不定,但是thread也有一些缺點,也就是程式上需要寫或是定義比較多東西,也要去避免資料可能會有覆蓋的情況發生,所以在寫thread的時候要特別的小心去處理這些資料的傳遞與資料被修改問題,這次作業只有3個,我寫的比較輕鬆也非常開心,這樣壓力比較小一點,助教也很開心,因為改一位同學只要看3個程式題目就好,話說助教大哥HW08和HW09的成績記得給老師,感謝助教大哥改作業那麼辛苦,也感謝老師提供教學的影片,終於學到thread的應用與課程,非常期待老師下禮拜的課程與內容,感謝老師與助教大哥 ### 作者: 廖經翰