Socket編程 —I/O複用的時間請求

利用I/O複用模型實現一個時間同步服務器

一、實驗內容

  • 服務端採用I/O複用模型(select函數)接收客戶端的時間同步請求
  • 服務端採用單線程,但要能同時接收多客戶端的連接請 求,顯示客戶端IP和端口,並向其回送時間信息。
  • 客戶端嘗試同時使用 UDP 和 TCP 來實現。
  • 注:藉助 I/O 複用模型,用單線程達到多線程的效果。

二、實驗要求

  提交源碼(源碼編寫要規範)、可執行程序、實驗報告(要有程序運行截圖)。

三、實驗分析

select 介紹

  select函數的作用是監聽指定的多個I/O的文件描述符,在設定的時間內阻塞,當有一個或者多個I/O端口滿足某個“讀”或者“寫”的條件,則在fd_set類型參數中標記並返回。
  fd_set類型是一個默認大小爲1024位的類型,每一位代表一個I/0文件描述符。例如每個進程默認0、1、2分別表示標準輸入、標準輸出和標準錯誤輸出,所以fd_set類型1024位中0、1、2位分別代表標準輸入、標準輸出和標準錯誤輸出。同理假如進程裏創建了一個監聽socket文件描述符爲3,則fd_set的第3位代表這個監聽sockfd;假如用accept了客戶端連接返回一個fdSocket爲n,那麼fd_set的第n個位就代表這個fdSocket。(因爲在一個進程裏,某個I/O口的文件描述符是唯一的。)

fd_set fdRead,fdSocket;
FD_ZERO (&fdSocket);//將fdset所有“位”置0
FD_SET(TcpServerSocket,&fdSocket);
FD_SET(UdpServerSocket,&fdSocket);

server

#include <iostream>
#include <WinSock2.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

using namespace std;

#pragma comment (lib,"ws2_32.lib")

#define BUFFER_LEN 1024
#define IP_ADDRESS "127.0.0.1"
#define PORT 8888

int main()
{
    WSADATA Ws;
    WORD sockVersion = MAKEWORD(2,2);

    SOCKET TcpServerSocket = INVALID_SOCKET;
    SOCKET UdpServerSocket = INVALID_SOCKET;
    SOCKET ConnectSocket = INVALID_SOCKET;

    if ( WSAStartup(sockVersion, &Ws) != 0 )
    {
        cout<<"Init Windows Socket Failed:"<<GetLastError()<<endl;
        closesocket(ConnectSocket);
        return -1;
    }

    sockaddr_in ClientAddr;

    char recvbuf[BUFFER_LEN];
    char sendbuf[BUFFER_LEN];
    int iResult;
    // 創建用於監聽的套接字
    TcpServerSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if ( TcpServerSocket == INVALID_SOCKET )
    {
        cout<<"Create Socket Failed:"<<GetLastError()<<endl;
        closesocket(TcpServerSocket);
        WSACleanup();
        return -1;
    }

    // 爲套接字綁定地址和端口號
    // 監聽端口爲DEFAULT_PORT
    struct sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(PORT);
    serAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);
    memset(serAddr.sin_zero,0x00,8);

    int Addrlen = sizeof(serAddr);

    SYSTEMTIME time;
    GetLocalTime(&time);

    iResult = bind(TcpServerSocket, (struct sockaddr*)&serAddr, sizeof(serAddr));
    if(iResult == SOCKET_ERROR)
    {
        cout<<"Bind Error !"<<endl;
        return -1;
    }

    sockaddr_in remoteAddr;
    int nAddrLen = sizeof(remoteAddr);
    UdpServerSocket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    iResult = bind(UdpServerSocket,(struct sockaddr *)&serAddr,sizeof(serAddr));
    if(iResult == SOCKET_ERROR)
    {
        cout<<"Bind Error !"<<endl;
        return -1;
    }

    // 監聽套接字
    iResult = listen(TcpServerSocket,SOMAXCONN);
    if(iResult == SOCKET_ERROR)
    {
        cout<<"---Listen Failed ---!"<<endl;
        closesocket(TcpServerSocket);
        WSACleanup();
        return -1;
    }

    cout<<"---Server Starting---"<<endl;

    fd_set fdRead,fdSocket;
    FD_ZERO (&fdSocket);
    FD_SET(TcpServerSocket,&fdSocket);
    FD_SET(UdpServerSocket,&fdSocket);
    while(true)
    {
        //通過select等待數據到達事件,如果有事件發生
        //select函數移除fdRead集合中沒有未決I/O操作的套接字句柄,然後返回
        //cout<<1<<endl;
        fdRead = fdSocket;
        iResult = select(0, &fdRead, NULL, NULL, NULL);
        //cout<<2<<endl;
        if(iResult>0)
        {
            //有網絡事件發生
            //確定有哪些套接字有未決的I/O,並進一步處理這些I/O
            for (int i = 0; i<(int)fdSocket.fd_count; i++)
            {
                if(FD_ISSET(fdSocket.fd_array[i], &fdRead))
                {
                    if(fdSocket.fd_array[i] == TcpServerSocket)
                    {
                        if(fdSocket.fd_count < FD_SETSIZE)
                        {
                            //同時複用的套接字數量不能大於FD_SETSIZE
                            //有新的連接請求
                            ConnectSocket = accept(TcpServerSocket,(struct  sockaddr*)&ClientAddr, &Addrlen);
                            if (ConnectSocket == INVALID_SOCKET)
                            {
                                cout<<"---Accept Failed---"<<endl;
                                closesocket(TcpServerSocket);
                                WSACleanup();
                                return 1;
                            }
                            //增加新的連接套接字進行復用等待
                            FD_SET(ConnectSocket, &fdSocket);
                            cout<<"New Connection "<<inet_ntoa(ClientAddr.sin_addr)<<":"<<ClientAddr.sin_port<<endl;
                        }
                        else
                        {
                            cout<<"No Enough Socket"<<endl;
                        }
                    }
                    else if(fdSocket.fd_array[i]==UdpServerSocket)
                    {
                        memset(recvbuf,0,sizeof(recvbuf));
                        iResult = recvfrom (fdSocket.fd_array[i],recvbuf,sizeof(recvbuf),0,(sockaddr *)&remoteAddr,&nAddrLen);
                        if(iResult > 0)
                        {
                            cout<<"UDP Connection!"<<endl;
                            cout<<"Connection "<<inet_ntoa(remoteAddr.sin_addr)<<":"<<remoteAddr.sin_port<<endl;

                            SYSTEMTIME time;
                            GetLocalTime(&time);
                            cout<<"Linking Time:"<<time.wYear<<"/"<<time.wMonth<<"/"<<time.wDay<<"  "<<time.wHour<<":"<<time.wMinute<<"'"<<time.wSecond<<endl;

                            int ret = sendto(fdSocket.fd_array[i],(char *)&time,sizeof(time),0,(sockaddr *)&remoteAddr,nAddrLen);
                            if(ret > 0)
                            {
                                cout<<"Send Time Success!"<<endl;
                                cout<<"-----------------------------"<<endl;
                            }
                            else
                            {
                                cout<<"Send Time Error!"<<endl;
                            }
                        }
                    }
                    else
                    {
                        memset(recvbuf, 0, BUFFER_LEN);

                        iResult = recv(fdSocket.fd_array[i], recvbuf, BUFFER_LEN, 0);
                        if(iResult >0)
                        {
                            cout<<"TCP Connection!"<<endl;
                            cout<<"Connection "<<inet_ntoa(ClientAddr.sin_addr)<<":"<<ClientAddr.sin_port<<endl;
                            //回送時間
                            SYSTEMTIME time;
                            GetLocalTime(&time);

                            cout<<"Linking Time:"<<time.wYear<<"/"<<time.wMonth<<"/"<<time.wDay<<"  the day of week is "<<time.wDayOfWeek<<"  "<<time.wHour<<":"<<time.wMinute<<"'"<<time.wSecond<<"''"<<time.wMilliseconds<<endl;

                            int ret = send(fdSocket.fd_array[i], (char *)&time, sizeof(time), 0);

                            if(ret > 0)
                            {
                                cout<<"Send Time Success!"<<endl;
                            }
                            else
                            {
                                cout<<"Send Time Error!"<<endl;
                            }
                        }
                        else if(iResult == 0)
                        {
                            cout<<"Current Connection closing..."<<endl;
                            cout<<"-----------------------------"<<endl;
                            closesocket(fdSocket.fd_array[i]);
                            FD_CLR(fdSocket.fd_array[i], &fdSocket);
                        }
                        else
                        {
                            //情況2:連接關閉
                            cout<<"Receive Failed With Error"<<endl;
                            closesocket(fdSocket.fd_array[i]);
                            FD_CLR(fdSocket.fd_array[i], &fdSocket);
                        }
                    }
                }
            }
        }
        else
        {
            cout<<"Select Failed With Error:"<<WSAGetLastError()<<endl;
            break;
        }
    }

    closesocket(TcpServerSocket);
    closesocket(UdpServerSocket);
    WSACleanup();
    return 0;
}

io_udpclient

#include <iostream>
#include <stdio.h>
#include <winsock2.h>
#include <string>
#include <Windows.h>

using namespace std;

#pragma comment(lib, "ws2_32.lib")

#define BUFFER_LEN 1024

int main()
{
    WSADATA Ws;
    SOCKET ClientSocket;
    //¼ÓÔØSocket¿â
    char SendBuffer[BUFFER_LEN];
    int iResult;
    char IP_ADDRESS[15];
    unsigned short int PORT;

    if ( WSAStartup(MAKEWORD(2,2), &Ws) != 0 )
    {
        cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl;
        return -1;
    }
    //Create Socket
    ClientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if ( ClientSocket == INVALID_SOCKET )
    {
        cout<<"Create Socket Failed::"<<GetLastError()<<endl;
        return -1;
    }

    cout<<"Input Server IP:"<<endl;
    cin>>IP_ADDRESS;
    cout<<"Input Server PORT:"<<endl;
    cin>>PORT;

    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);
    ServerAddr.sin_port = htons(PORT);
    int len = sizeof(ServerAddr);

    //memset(ServerAddr.sin_zero, 0x00, 8);
    if(true)
    {
        memset(SendBuffer,0,sizeof(SendBuffer));
        strcmp(SendBuffer,"hello");
        iResult=sendto(ClientSocket,SendBuffer,sizeof(SendBuffer),0,(sockaddr *)&ServerAddr,len);
        cout<<"Client OK!"<<endl;

        SYSTEMTIME curr_st;
        SYSTEMTIME time;
        //recv(ClientSocket,(char *)&time,sizeof(time),0);
        iResult = recvfrom(ClientSocket,(char *)&time,sizeof(time),0,(sockaddr *)&ServerAddr,&len);
        if(!iResult)
        {
            cout<<"Receive Failed:"<<GetLastError()<<endl;
            WSACleanup();
            return -1;
        }
        curr_st.wYear=time.wYear-1;
        curr_st.wMonth=time.wMonth;
        curr_st.wDay=time.wDay;
        curr_st.wHour=time.wHour;
        curr_st.wMinute=time.wMinute;
        curr_st.wSecond=time.wSecond;
        curr_st.wDayOfWeek=time.wDayOfWeek;
        curr_st.wMilliseconds=time.wMilliseconds;
        cout<<"Receive Time:"<<curr_st.wYear<<"/"<<curr_st.wMonth<<"/"<<curr_st.wDay<<"  the day of week is "<<curr_st.wDayOfWeek<<"  "<<curr_st.wHour<<":"<<curr_st.wMinute<<"'"<<curr_st.wSecond<<"''"<<curr_st.wMilliseconds<<endl;
        iResult = SetLocalTime(&curr_st);
        if(!iResult)
        {
            cout<<"Failed to adjust SetTime privilege!"<<endl;
            closesocket(ClientSocket);
            WSACleanup();
            return -1;
        }
        getchar();
        getchar();
        cout<<"success"<<endl;
    }

    closesocket(ClientSocket);
    WSACleanup();
    return 0;
}

io_tcpclient

#include <iostream>
#include <winsock2.h>
#include <fstream>
#include <string>
using namespace std;

#pragma comment(lib, "ws2_32.lib")

#define BUFFER_LEN 1024

int main(int argc, char * argv[])
{
    WSADATA Ws;
    SOCKET ClientSocket;
    int iResult;
    char IP_ADDRESS[15];
    unsigned short int PORT;

    if ( WSAStartup(MAKEWORD(2,2), &Ws) != 0 )
    {
        cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl;
        return -1;
    }
    //Create Socket
    ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if ( ClientSocket == INVALID_SOCKET )
    {
        cout<<"Create Socket Failed::"<<GetLastError()<<endl;
        return -1;
    }

    cout<<"Input Server IP:"<<endl;
    cin>>IP_ADDRESS;
    cout<<"Input Server PORT:"<<endl;
    cin>>PORT;

    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);
    ServerAddr.sin_port = htons(PORT);
    int len = sizeof(ServerAddr);

    iResult = connect(ClientSocket,(struct sockaddr*)&ServerAddr, sizeof(ServerAddr));
    if ( iResult == SOCKET_ERROR )
    {
        cout<<"Connect Error::"<<GetLastError()<<endl;
        WSACleanup();
        closesocket(ClientSocket);
        return -1;
    }
    else
    {
        cout<<"Success"<<endl;
    }
    char SendBuffer[BUFFER_LEN];
    if (true )
    {
        memset(SendBuffer,0,sizeof(SendBuffer));
        strcmp(SendBuffer,"hello");
        send(ClientSocket,SendBuffer,sizeof(SendBuffer),0);
        SYSTEMTIME curr_st;
        SYSTEMTIME time;
        iResult = recv(ClientSocket, (char *)&time, sizeof(time), 0);
        if(iResult == 0 || iResult == SOCKET_ERROR)
        {
            cout<<"Received Error!"<<endl;
            closesocket(ClientSocket);   
            WSACleanup();  
            return -1;
        }
        curr_st.wYear=time.wYear-1;
        curr_st.wMonth=time.wMonth;
        curr_st.wDay=time.wDay;
        curr_st.wHour=time.wHour;
        curr_st.wMinute=time.wMinute;
        curr_st.wSecond=time.wSecond;
        curr_st.wDayOfWeek=time.wDayOfWeek;
        curr_st.wMilliseconds=time.wMilliseconds;
        cout<<"Receive Time:"<<curr_st.wYear<<"/"<<curr_st.wMonth<<"/"<<curr_st.wDay<<"  the day of week is "<<curr_st.wDayOfWeek<<"  "<<curr_st.wHour<<":"<<curr_st.wMinute<<"'"<<curr_st.wSecond<<"''"<<curr_st.wMilliseconds<<endl;
        iResult = SetLocalTime(&curr_st);
        if(!iResult)
        {
            int qwewq;
            cin>>qwewq;
            cout<<"Failed to adjust SetTime privilege"<<endl;
            closesocket(ClientSocket);
            WSACleanup();
            return -1;
        }
        cout<<"success"<<endl;
    }

    closesocket(ClientSocket);
    WSACleanup();
    return 0;
}

截圖

這裏寫圖片描述
這裏寫圖片描述
由於修改本地時間需要管理員權限,所以我在後面加了個判斷,若無管理員權限,則輸出
Failed to adjust SetTime privilege!

注:在修改時間時,必須完全獲得所有的參數值,意思是

curr_st.wYear=XXXXX
curr_st.wMonth=XXXX
curr_st.wDay=XXX
curr_st.wHour=XXX
curr_st.wMinute=XXX
curr_st.wSecond=XXX
curr_st.wDayOfWeek=XXX
curr_st.wMilliseconds=XXX

才能修改成功

如果利用管理員權限的時候運行
直接運行exe會之間修改後閃退
這裏寫圖片描述
暫時做了一個年份-1但其餘時間不變的進行測試
測試的時候修改時間成功!!!
這裏寫圖片描述

多路複用特點和應用:

  除了可以採用多進程和多線程方法實現併發服務器之外,還可以採用I/O多路複用技術。通過該技術,系統內核緩衝I/O數據,當某個I/O準備好後,系統通知應用程序該I/O可讀或可寫,這樣應用程序可以馬上完成相應的I/O操作,而不需要等待系統完成相應I/O操作,從而應用程序不必因等待I/O操作而阻塞。

  與多進程和多線程技術相比,I/O多路複用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。

對於I/O複用典型的應用如下:
- 當客戶處理多個描述字時(一般是交互式輸入和網絡套接口),必須使用I/O複用。
- 當一個客戶同時處理多個套接口時,而這種情況是可能的,但很少出現。
- 如果一個TCP服務器既要處理監聽套接口,又要處理已連接套接口,一般也要用到I/O複用。
- 如果一個服務器即要處理TCP,又要處理UDP,一般要使用I/O複用。
- 如果一個服務器要處理多個服務或多個協議,一般要使用I/O複用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章