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复用。

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