# 網路程式設計 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個作業,不多也不少非常剛好,這樣同學們寫的開心,助教大哥也改的開心,大家一起開心,謝謝老師與助教大哥
### 作者: 廖經翰