【网络编程】利用I/O复用模型实现一个时间同步服务器

时间:2021-03-28 17:58:06

实验要求:

1.    服务端采用I/O复用模型(select函数)接收客户端的时间同步请求;

2.    服务端采用单线程,但要能同时接收多客户端的连接请求,显示客户端IP和端口,并向其回送时间信息。

3.    客户端尝试同时使用UDP和TCP来实现。

注:借助I/O复用模型,用单线程达到多线程的效果


下面的代码不对

server:

// EchoTCPServer-select.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<ctime>
#include<time.h>

using namespace std;

// 连接到winsock2对应的lib文件: Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")

#define DEFAULT_BUFLEN 512 //默认缓冲区长度为512
#define DEFAULT_PORT 27015 //默认服务器端口号为27015
int _tmain(int argc, _TCHAR* argv[])
{
/////////////////////////////////////////udp
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaDataudp;
if (WSAStartup(sockVersion, &wsaDataudp) != 0)
{
return 0;
}

SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (serSocket == INVALID_SOCKET)
{
printf("socket error !");
WSACleanup();
return 0;
}

sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(DEFAULT_PORT);
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("bind error !");
closesocket(serSocket);
WSACleanup();
return 0;
}

cout << "listening the client" << endl;

sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
int cnt = 0;
char RecvBuffer[64];

/////////////////////////////////////////tcp
WSADATA wsaDatatcp;
int iResult;
SOCKET ServerSocket = INVALID_SOCKET;
SOCKET AcceptSocket = INVALID_SOCKET;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
sockaddr_in addrClient;
int addrClientlen = sizeof(sockaddr_in);

// 初始化 Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaDatatcp);
if (iResult != 0)
{
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}

// 创建用于监听的套接字
ServerSocket = socket(AF_INET,SOCK_STREAM, IPPROTO_IP);
if(ServerSocket == INVALID_SOCKET)
{
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}

// 为套接字绑定地址和端口号
SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(DEFAULT_PORT); // 监听端口为DEFAULT_PORT
addrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
iResult = bind(ServerSocket,(const struct sockaddr*)&addrServ,sizeof(SOCKADDR_IN));
if (iResult == SOCKET_ERROR)
{
printf("bind failed with error: %d\n", WSAGetLastError());
closesocket(ServerSocket);
WSACleanup();
return 1;
}

// 监听套接字
iResult = listen(ServerSocket, SOMAXCONN);
if(iResult == SOCKET_ERROR)
{
printf("listen failed !\n");
closesocket(ServerSocket);
WSACleanup();
return -1;
}
printf("TCP server starting\n");
printf("UDP server starting\n");
fd_set fdRead,fdSocket;
fd_set fdReadudp, fdSocketudp;
FD_ZERO(&fdSocketudp);
FD_SET(serSocket, &fdSocketudp);
FD_ZERO( &fdSocket );
FD_SET( ServerSocket, &fdSocket);

while( TRUE)
{

//////udp/////
fdReadudp = fdSocketudp;
iResult = select(0, &fdReadudp, NULL, NULL, NULL);
if (iResult > 0)
{

//有网络事件发生
//确定有哪些套接字有未决的I/O,并进一步处理这些I/O
for (int i = 0; i < (int)fdSocketudp.fd_count; i++)
{
if (FD_ISSET(fdSocketudp.fd_array[i], &fdReadudp))
{
if (fdSocketudp.fd_array[i] == serSocket)
{
if (fdSocketudp.fd_count < FD_SETSIZE)
{
memset(RecvBuffer, 0, sizeof(RecvBuffer));
int Ret = recvfrom(serSocket, RecvBuffer, 64, 0, (sockaddr *)&remoteAddr, &nAddrLen);
if (Ret < 0)
{
cout << "receive error " << WSAGetLastError();
continue;
}
RecvBuffer[Ret] = 0x00;
cout << "接收到新的连接:ip: %s " << ++cnt << ": " << inet_ntoa(remoteAddr.sin_addr);
cout << " post: " << remoteAddr.sin_port << endl;

time_t t = time(0);
char tmp[64];
strftime(tmp, sizeof(tmp), "%Y/%m/%d %X", localtime(&t));
cout << "send time : ";
puts(tmp);
//将本地时间发送给客户端
sendto(serSocket, tmp, sizeof(tmp), 0, (sockaddr *)&remoteAddr, nAddrLen);
}
else
{
printf("连接个数超限!\n");
continue;
}
}
}
}
}

//tcp
//通过select等待数据到达事件,如果有事件发生,select函数移除fdRead集合中没有未决I/O操作的套接字句柄,然后返回
fdRead = fdSocket;
iResult = select( 0, &fdRead, NULL, NULL, NULL);
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] == ServerSocket)
{
if( fdSocket.fd_count < FD_SETSIZE)
{
//同时复用的套接字数量不能大于FD_SETSIZE
//有新的连接请求
AcceptSocket = accept(ServerSocket,(sockaddr FAR*)&addrClient,&addrClientlen);
if( AcceptSocket == INVALID_SOCKET)
{
printf("accept failed !\n");
closesocket(ServerSocket);
WSACleanup();
return 1;
}
//增加新的连接套接字进行复用等待
FD_SET( AcceptSocket, &fdSocket);
printf("接收到新的连接:ip: %s ", inet_ntoa(addrClient.sin_addr));
cout << "post: " << addrClient.sin_port << endl;

time_t t = time(0);
char tmp[64];
strftime(tmp, sizeof(tmp), "%Y/%m/%d %X", localtime(&t));
send(AcceptSocket, tmp, strlen(tmp), 0);
cout << "send time :";
puts(tmp);
//将本地时间发送给客户端
}
else
{
printf("连接个数超限!\n");
continue;
}
}
else
{
//有数据到达
memset(recvbuf,0,recvbuflen);
iResult = recv( fdSocket.fd_array[i], recvbuf, recvbuflen, 0);
if (iResult > 0)
{
//情况1:成功接收到数据
printf("\nBytes received: %d\n", iResult);

}
else if (iResult == 0)
{
//情况2:连接关闭
// printf("Current Connection closing...\n");
closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i], &fdSocket);
}
else
{
//情况3:接收失败
printf("recv failed with error: %d\n",WSAGetLastError() );
closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i], &fdSocket);
}
}
}
}
}
else
{
printf("select failed with error: %d\n",WSAGetLastError() );
break;
}



}

// cleanup
closesocket(ServerSocket);
WSACleanup();
return 0;
}


client_tcp:

#include "stdafx.h"  
#include <stdio.h>
#include <winsock2.h>
#include <wincrypt.h>
#include <time.h>
#include <cstring>
#include <iostream>
#include <string.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "crypt32.lib")
const int MAX1 = 1000;
using namespace std;

int main(int argc, const char*argv[])
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
int error = WSAStartup(sockVersion, &wsaData);
if (error)
{
cout << "fail to startup" << GetLastError() << endl;
WSACleanup();
return 0;
}
SOCKET socketClient = socket(AF_INET, SOCK_STREAM, 0);
if (socketClient == INVALID_SOCKET)
{
cout << "socket error! " << GetLastError() << endl;
WSACleanup();
closesocket(socketClient);
return 0;
}
char ip[20];
memset(ip, 0, sizeof(ip));
cout << "Please input the ip: ";
gets_s(ip);
cout << endl;
sockaddr_in addrServer;
addrServer.sin_addr.S_un.S_addr = inet_addr(ip);
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(27015);
connect(socketClient, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));
//FileSend(socketClient);
char revbuf[64];
memset(revbuf, 0, sizeof(revbuf));
recv(socketClient,revbuf, 64, 0);
puts(revbuf);
closesocket(socketClient);
getchar();
return 0;
}



client_udp:

#include "stdafx.h" 
#include <iostream>
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
const int MAX_IP_PATH = 15;
const int MAX_BUFFER = 10240;
const int NAME = 10240;

int main(int argc, char* argv[])
{
WORD socketVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(socketVersion, &wsaData) != 0)
{
return 0;
}
SOCKET ClientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ClientSocket == INVALID_SOCKET)
{
cout << "Create Socket Failed::" << GetLastError() << endl;
WSACleanup();
return -1;
}

char IP_ADDRESS[MAX_IP_PATH];
unsigned int PORT = 27015;
cout << "connect IP:";
cin >> IP_ADDRESS;
cout << endl;

sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.S_un.S_addr = inet_addr(IP_ADDRESS);

unsigned int msglen;
char SendBuffer[64]="ask time";
int len = sizeof(sin);
cout << "asking time"<<endl;
msglen = strlen(SendBuffer);
sendto(ClientSocket, SendBuffer, strlen(SendBuffer), 0, (sockaddr *)&sin, len);

int Ret;
char RecvBuffer[64];
Ret = recvfrom(ClientSocket, RecvBuffer, sizeof(RecvBuffer) , 0, (sockaddr *)&sin, &len);
if (Ret <0)
{
cout << "receive error:" << WSAGetLastError();
closesocket(ClientSocket);
WSACleanup();
return -1;
}
cout << "time: " << RecvBuffer << endl;

system("pause");
WSACleanup();
return 0;
}