Skip to content

Latest commit

 

History

History
643 lines (556 loc) · 18.6 KB

hw10.md

File metadata and controls

643 lines (556 loc) · 18.6 KB
tags: 網路程式設計

網路程式作業 HW10

  • 另一個實現多工技術-select()。

  • 有較低CPU使用率。

  • 不用一直查看,有資料時會「被告知」。

  • 設定保全:(要先檢查清單 -> fd_set readfds/fd_set writefds(宣告))

    • 清空檢查表 -> FD_ZERO(&readfds)/FD_ZERO(&writefds)
    • 填入要監看的socket -> FD_SET(socket1,&readfds)/FD_SET(socket2,&writefds)
  • 啟動保全:

    • 進行監看(偵測) -> select()
    • 當發出警報,核對是哪個socket,作出合適處置 -> FD_ISSET( socket1 , &readfds)/FD_ISSET( socket2 , &writefds
  • select()可監視socket三種狀態

    • 是否有資料抵達,可以讀取
    • 是否有buffer空間,可以寫入buffer
    • 是否有例外發生
  • select()的回傳值

    • 正常情況下返回偵測到的事件總數量
    • 經過了timeout時間,仍無偵測到,返回值為0
    • 如果select被中斷,它將返回-1

練習一:一送一收,觀察檢查表的運作

server

#include <stdio.h>
#include <winsock.h>
#define MAXRECV 1024

int main(){
	SOCKET sd,cli_sd;
	WSADATA wsadata;
	struct sockaddr_in serv,cli;
	int i,n,cnt=0;
	char str[1024];

	WSAStartup(0x101,&wsadata);
	sd = socket(AF_INET, SOCK_STREAM, 0);
	serv.sin_family = AF_INET;
	serv.sin_port = htons(1234);
	serv.sin_addr.s_addr = inet_addr("127.0.0.1");

	bind(sd, (struct sockaddr *) &serv, sizeof(serv));

	listen(sd,5);
	int cli_len=sizeof(cli);
	printf("Server waits.\n");
	cli_sd =  accept(sd, (struct sockaddr *)  &cli,&cli_len );
	printf("Client is connected: socked fd is %d, ip is : %s, port : %d \n",cli_sd, inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));

    fd_set readfds;
    int activity;
	while(1){//檢查
        printf("[1]clear the socket fd set. \n");
        FD_ZERO(&readfds);//清空檢查表

        printf("[2]add client socket to fd set. \n");
        FD_SET(cli_sd,&readfds);//填入要監看的socket

        printf("[3]call select() and waiting. \n");
        activity = select(0,&readfds, NULL , NULL , NULL);

        printf("[4]wake up from select():%d \n",activity);
        if(activity == SOCKET_ERROR)
        {
            printf("select call failed with error code: %d",WSAGetLastError());
            exit(EXIT_FAILURE);
        }

        if(FD_ISSET(cli_sd,&readfds))//核對是哪個socket
        {
            n = recv(cli_sd,str, MAXRECV, 0);
            printf("recv from client: %s\n",str);
            if (n <= 0 )
            {
                printf("connection closed.\n");
                break;
            }
        }
	}
	closesocket(sd);
	closesocket(cli_sd);
	WSACleanup();
}

client

#include <stdio.h>
#include <winsock.h>

int main(){
	SOCKET sd;
	WSADATA wsadata;
	struct sockaddr_in serv;
	int i,n;
	char str[100] = "How are you.";

	WSAStartup(0x101,&wsadata);
	sd = socket(AF_INET, SOCK_STREAM, 0);

	serv.sin_family = AF_INET;
	serv.sin_port = htons(1234);
	serv.sin_addr.s_addr = inet_addr("127.0.0.1");

	connect(sd, (struct sockaddr *)  &serv,sizeof(serv) );
	printf("Connect to server.\n");

	for(;;){
        Sleep(10000);
	send(sd,str, strlen(str)+1,0);
        printf("Send : %s(in 10 secs)\n",str);
	}
	closesocket(sd);
	WSACleanup();

}

練習二:增加為兩個client,server會echo

clint 和第一個只差在多了RECV,因為要有echo的效果。

#include <stdio.h>
#include <winsock.h>
#define MAXRECV 1024

int main(){
	SOCKET sd,cli_sd1,cli_sd2;
	WSADATA wsadata;
	struct sockaddr_in serv,cli;
	int i,n;
	char str[1024];

	WSAStartup(0x101,&wsadata);
	sd = socket(AF_INET, SOCK_STREAM, 0);
	serv.sin_family = AF_INET;
	serv.sin_port = htons(1234);
	serv.sin_addr.s_addr = inet_addr("127.0.0.1");

	bind(sd, (struct sockaddr *) &serv, sizeof(serv));

	listen(sd,5);
	int cli_len=sizeof(cli);
	printf("Server waits.\n");

	cli_sd1 =  accept(sd, (struct sockaddr *)  &cli,&cli_len );
	printf("Client is connected: socked fd is %d, ip is : %s, port : %d \n",cli_sd1, inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
	cli_sd2 =  accept(sd, (struct sockaddr *)  &cli,&cli_len );
        printf("Client is connected: socked fd is %d, ip is : %s, port : %d \n",cli_sd2, inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));

    fd_set readfds;
    int activity;
	while(1){//檢查
        printf("[1]clear the socket fd set. \n");
        FD_ZERO(&readfds);//清空檢查表

        printf("[2]add client socket to fd set. \n");
        FD_SET(cli_sd1,&readfds);//填入要監看的socket
        FD_SET(cli_sd2,&readfds);

        printf("[3]call select() and waiting. \n");
        activity = select(0,&readfds, NULL , NULL , NULL);

        printf("[4]wake up from select():%d \n",activity);
        if(activity == SOCKET_ERROR)
        {
            printf("select call failed with error code: %d",WSAGetLastError());
            exit(EXIT_FAILURE);
        }
        //client1
        if(FD_ISSET(cli_sd1,&readfds))//核對是哪個socket
        {
            n = recv(cli_sd1,str, MAXRECV, 0);
            printf("recv from client1: %s\n",str);
            send(cli_sd1,str,strlen(str)+1,0);
        }
        //client2
        if(FD_ISSET(cli_sd2,&readfds))//核對是哪個socket
        {
            n = recv(cli_sd2,str, MAXRECV, 0);
            printf("recv from client2: %s\n",str);
            send(cli_sd2,str,strlen(str)+1,0);
        }
	}
	closesocket(sd);
	closesocket(cli_sd1);
	closesocket(cli_sd2);
	WSACleanup();
}

client

#include <stdio.h>
#include <winsock.h>
#define MAXLINE 1024

int main(){
	SOCKET sd;
	WSADATA wsadata;
	struct sockaddr_in serv;
	int i,n;
	char str[100] = "How are you.";
	char str_r[100];

	WSAStartup(0x101,&wsadata);
	sd = socket(AF_INET, SOCK_STREAM, 0);

	serv.sin_family = AF_INET;
	serv.sin_port = htons(1234);
	serv.sin_addr.s_addr = inet_addr("127.0.0.1");

	connect(sd, (struct sockaddr *)  &serv,sizeof(serv) );
	printf("Connect to server.\n");

	for(;;){
        Sleep(10000);
        send(sd,str, strlen(str)+1,0);
        printf("Send : %s(in 10 secs)\n",str);
        recv(sd,str_r,MAXLINE,0);
        printf("Recv:%s\n",str);
	}
	closesocket(sd);
	WSACleanup();

}

練習三:服務多個client的echo server

  • 可多個echo client==隨時== 連入 server
#include <stdio.h>
#include <winsock.h>
#define MAXRECV 1024
#define MAXCLI 10

int main(){
	SOCKET sd,cli_sd[MAXCLI],new_socket;//支援多個client,所以設陣列
	WSADATA wsadata;
	struct sockaddr_in serv,cli;
	int i,n,client_num=0;
	char str[MAXRECV];

	for(int i=0;i<MAXCLI;i++) cli_sd[i] = 0;//初始化

	WSAStartup(0x101,&wsadata);
	sd = socket(AF_INET, SOCK_STREAM, 0);
	serv.sin_family = AF_INET;
	serv.sin_port = htons(1234);
	serv.sin_addr.s_addr = inet_addr("127.0.0.1");

	bind(sd, (struct sockaddr *) &serv, sizeof(serv));

	listen(sd,5);

    fd_set readfds;
    int activity;
	while(1){//檢查
        printf("[1]clear the socket fd set. \n");
        FD_ZERO(&readfds);//清空檢查表

        printf("[2]add client socket to fd set. \n");
        FD_SET(sd,&readfds);//server也要加入!!!!!

        printf("[2]add client socket to fd set. \n");
        //填入要監看的socket
        for(int i=0;i<MAXCLI;i++)//陣列 所以用迴圈
        {
            if(cli_sd[i] > 0)
            {
                FD_SET(cli_sd[i],&readfds);//填入要監看的socket
            }
        }

        printf("[3]call select() and waiting. \n");
        activity = select(0,&readfds, NULL , NULL , NULL);

        printf("[4]wake up from select():%d \n",activity);
        if(activity == SOCKET_ERROR)
        {
            printf("select call failed with error code: %d",WSAGetLastError());
            exit(EXIT_FAILURE);
        }

        //新連線
        if(FD_ISSET(sd,&readfds))
        {
            int cli_len=sizeof(cli);
            printf("Server waits.\n");
            new_socket =  accept(sd, (struct sockaddr *)  &cli,&cli_len );
            printf("New connection: socket socked fd is %d, ip is : %s, port : %d \n",new_socket, inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));

            for(int i=0;i<MAXCLI;i++)
            {
                if(cli_sd[i] == 0){
                    cli_sd[i] = new_socket;
                    client_num++;
                    printf("The %d client socket is in cli_sd[%d]\n",client_num,i);
                    break;
                }

            }
        }
            //check
        for(int i=0;i<MAXCLI;i++)
        {
            if(FD_ISSET(cli_sd[i],&readfds))
            {
                n = recv(cli_sd[i],str, MAXRECV, 0);
                if(n == SOCKET_ERROR)//發現已經結束連線
                {
                    int error__code = WSAGetLastError();
                    if(error__code == WSAECONNRESET)
                    {
                        printf("Host disconnected unexpectedly.\n");
                        closesocket(cli_sd[i]);
                        cli_sd[i] = 0;
                        client_num--;
                    }
                    else{printf("recv failed with error code : %d"),error__code;}
                }

                if(n == 0)//沒連到
                {
                    printf("Host disconnected. \n");
                    closesocket(cli_sd[i]);
                    cli_sd[i] = 0;
                    client_num--;

                }
                //有連到
                if(n > 0)
                {
                    printf("recv from and echo to cli[%d]: %s \n",i,str);
                    send(cli_sd[i],str,strlen(str)+1,0);
                }
            }
        }

	}
	closesocket(sd);
	closesocket(cli_sd[MAXCLI]);
	WSACleanup();
}

client 和 練習二相同。

練習四:使用writefds來傳送

server和上一題的差異:recv後,send透過另一種方法傳送。 每次echo的狀況會觸發兩次select,分別是recv和write的動作。

#include <stdio.h>
#include <winsock.h>
#define MAXRECV 1024
#define MAXCLI 10

int main(){
	SOCKET sd,cli_sd[MAXCLI],new_socket;//支援多個client,所以設陣列
	WSADATA wsadata;
	struct sockaddr_in serv,cli;
	int i,n,client_num=0,ready_to_send[MAXCLI];
	char str[MAXRECV];

	for(int i=0;i<MAXCLI;i++)
    {
        cli_sd[i] = 0;//初始化
        ready_to_send[i] = 0;//初始化
    }

	WSAStartup(0x101,&wsadata);
	sd = socket(AF_INET, SOCK_STREAM, 0);
	serv.sin_family = AF_INET;
	serv.sin_port = htons(1234);
	serv.sin_addr.s_addr = inet_addr("127.0.0.1");

	bind(sd, (struct sockaddr *) &serv, sizeof(serv));

	listen(sd,5);

    fd_set readfds;
    fd_set writefds;

    int activity;
	while(1){//檢查
        printf("[1]clear the socket fd set. \n");
        FD_ZERO(&readfds);//清空檢查表
        FD_ZERO(&writefds);

        printf("[2]add client socket to fd set. \n");
        FD_SET(sd,&readfds);//server也要加入!!!!!

        printf("[2]add client socket to fd set. \n");
        //填入要監看的socket
        for(int i=0;i<MAXCLI;i++)
        {
            if(cli_sd[i] > 0)//判斷要傳送還是要接收~~~~
            {
                if(ready_to_send[i]==0)
                {
                    FD_SET(cli_sd[i],&readfds);//讀入
                }
                else
                {
                    FD_SET(cli_sd[i],&writefds);//寫入
                }

            }
        }
        printf("[3]call select() and waiting. \n");
        activity = select(0,&readfds, &writefds , NULL , NULL);//多了一個writefds

        printf("[4]wake up from select():%d \n",activity);
        if(activity == SOCKET_ERROR)
        {
            printf("select call failed with error code: %d",WSAGetLastError());
            exit(EXIT_FAILURE);
        }

        //新連線
        if(FD_ISSET(sd,&readfds))
        {
            int cli_len=sizeof(cli);
            printf("Server waits.\n");
            new_socket =  accept(sd, (struct sockaddr *)  &cli,&cli_len );
            printf("New connection: socket socked fd is %d, ip is : %s, port : %d \n",new_socket, inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));

            for(int i=0;i<MAXCLI;i++)
            {
                if(cli_sd[i] == 0){
                    cli_sd[i] = new_socket;
                    client_num++;
                    printf("The %d client socket is in cli_sd[%d]\n",client_num,i);
                    break;
                }

            }
        }
            //check
        for(int i=0;i<MAXCLI;i++)
        {
            if(FD_ISSET(cli_sd[i],&readfds))
            {
                n = recv(cli_sd[i],str, MAXRECV, 0);
                if(n == SOCKET_ERROR)//發現已經結束連線
                {
                    int error__code = WSAGetLastError();
                    if(error__code == WSAECONNRESET)
                    {
                        printf("Host disconnected unexpectedly.\n");
                        closesocket(cli_sd[i]);
                        cli_sd[i] = 0;
                        client_num--;
                    }
                    else{printf("recv failed with error code : %d"),error__code;}
                }

                if(n == 0)//沒連到
                {
                    printf("Host disconnected. \n");
                    closesocket(cli_sd[i]);
                    cli_sd[i] = 0;
                    client_num--;

                }
                //有連到
                if(n > 0)
                {
                    printf("recv from and echo to cli[%d]: %s \n",i,str);
                    //send(cli_sd[i],str,strlen(str)+1,0);
                    ready_to_send[i]=1;//設1,表示有資料準備要傳送
                }
            }

            //判斷有資料要傳送
            if(FD_ISSET(cli_sd[i],&writefds))
            {
                send(cli_sd[i],str,strlen(str)+1,0);
                 ready_to_send[i]=0;//設為0
            }
        }

	}
	closesocket(sd);
	closesocket(cli_sd[MAXCLI]);
	WSACleanup();
}

client 和 練習二相同。

練習五:使用timeout,不卡住,跳出select()作其他事

  • timeout:無論是否有資料進來,select()最大偵測時間。
#include <stdio.h>
#include <winsock.h>
#define MAXRECV 1024
#define MAXCLI 10

int main(){
	SOCKET sd,cli_sd[MAXCLI],new_socket;//支援多個client,所以設陣列
	WSADATA wsadata;
	struct sockaddr_in serv,cli;
	int i,n,client_num=0,ready_to_send[MAXCLI];
	char str[MAXRECV];

	TIMEVAL timeout;//新增的(計算時間)

	for(int i=0;i<MAXCLI;i++)
    {
        cli_sd[i] = 0;//初始化
        ready_to_send[i] = 0;//初始化
    }

	WSAStartup(0x101,&wsadata);
	sd = socket(AF_INET, SOCK_STREAM, 0);
	serv.sin_family = AF_INET;
	serv.sin_port = htons(1234);
	serv.sin_addr.s_addr = inet_addr("127.0.0.1");

	bind(sd, (struct sockaddr *) &serv, sizeof(serv));

	listen(sd,5);

    fd_set readfds;
    fd_set writefds;

    int activity;
	while(1){//檢查
        printf("[1]clear the socket fd set. \n");
        FD_ZERO(&readfds);//清空檢查表
        FD_ZERO(&writefds);

        printf("[2]add client socket to fd set. \n");
        FD_SET(sd,&readfds);//server也要加入!!!!!

        printf("[2]add client socket to fd set. \n");
        //填入要監看的socket
        for(int i=0;i<MAXCLI;i++)
        {
            if(cli_sd[i] > 0)//判斷要傳送還是要接收~~~~
            {
                if(ready_to_send[i]==0)
                {
                    FD_SET(cli_sd[i],&readfds);//讀入
                }
                else
                {
                    FD_SET(cli_sd[i],&writefds);//寫入
                }

            }
        }
        printf("[3]call select() and waiting. \n");

        //set timeout
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        activity = select(0,&readfds, &writefds , NULL , &timeout);//多了一個timeout~~

        printf("[4]wake up from select():%d \n",activity);
        if(activity == SOCKET_ERROR)
        {
            printf("select call failed with error code: %d",WSAGetLastError());
            exit(EXIT_FAILURE);
        }

        //沒有事發生印出~~~
        if(activity == 0)
        {
            printf("NNo message: select() has waited for 5 sec. \n");
        }
        //新連線
        if(FD_ISSET(sd,&readfds))
        {
            int cli_len=sizeof(cli);
            printf("Server waits.\n");
            new_socket =  accept(sd, (struct sockaddr *)  &cli,&cli_len );
            printf("New connection: socket socked fd is %d, ip is : %s, port : %d \n",new_socket, inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));

            for(int i=0;i<MAXCLI;i++)
            {
                if(cli_sd[i] == 0){
                    cli_sd[i] = new_socket;
                    client_num++;
                    printf("The %d client socket is in cli_sd[%d]\n",client_num,i);
                    break;
                }

            }
        }
            //check
        for(int i=0;i<MAXCLI;i++)
        {
            if(FD_ISSET(cli_sd[i],&readfds))
            {
                n = recv(cli_sd[i],str, MAXRECV, 0);
                if(n == SOCKET_ERROR)//發現已經結束連線
                {
                    int error__code = WSAGetLastError();
                    if(error__code == WSAECONNRESET)
                    {
                        printf("Host disconnected unexpectedly.\n");
                        closesocket(cli_sd[i]);
                        cli_sd[i] = 0;
                        client_num--;
                    }
                    else{printf("recv failed with error code : %d"),error__code;}
                }

                if(n == 0)//沒連到
                {
                    printf("Host disconnected. \n");
                    closesocket(cli_sd[i]);
                    cli_sd[i] = 0;
                    client_num--;

                }
                //有連到
                if(n > 0)
                {
                    printf("recv from and echo to cli[%d]: %s \n",i,str);
                    //send(cli_sd[i],str,strlen(str)+1,0);
                    ready_to_send[i]=1;//設1,表示有資料準備要傳送
                }
            }

            //判斷有資料要傳送
            if(FD_ISSET(cli_sd[i],&writefds))
            {
                send(cli_sd[i],str,strlen(str)+1,0);
                 ready_to_send[i]=0;//設為0
            }
        }

	}
	closesocket(sd);
	closesocket(cli_sd[MAXCLI]);
	WSACleanup();
}

client 和 練習二相同。

心得

這星期的程式相對複雜了許多,有很多需要去透過if判斷,裡面我覺得比較難的是第四題,因為他不是直接用send傳回,而是改用writefds的方式另外寫,因此要先透過ready_to_send去設置有沒有東西要被傳送,再接續後面的動作,整個看完就覺得很酷!