# 網路程式設計 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的應用與課程,非常期待老師下禮拜的課程與內容,感謝老師與助教大哥
### 作者: 廖經翰