---
# System prepended metadata

title: 網路程式設計 HW10 用Select實現多工作業

---

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

### 作者: 廖經翰