利用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複用。