I/O复用Select函数的UDP和TCP客户端和服务端
要求
- 服务端采用I/O复用模型(SELECT函数),接受客户端的时间同步请求
- 服务端采用单线程,但是要同时能够接受多个客户端的连接请求
- 服务端显示客户端的IP和端口,并回送时间信息
- 客户端包括UDP客户端和TCP客户端
设计思路
主要是在书上的代码框架上进行修改,增加功能
- 客户端除了基本的创建套接字等步骤之外,先发送一个字符串,表示开始请求时间数据,然后就等待接收时间信息,然后修改本地时间
- 对于修改本地时间,首先要将受到的时间信息处理之后放入一个SYSTEMTIME类型的变量中,然后调用SetLocalTime()来进行修改
int set_local_time(const char *time_string)
{
SYSTEMTIME system_time = { 0 };
char year[4 + 1] = { 0 };
char month[2 + 1] = { 0 };
char day[2 + 1] = { 0 };
char hour[2 + 1] = { 0 };
char minute[2 + 1] = { 0 };
char second[2 + 1] = { 0 };
char Dayofweek[2] = { 0 };
char Mill[2 + 1] = { 0 };
int index = 0;
strncpy(year, time_string + index, 4);
index += 4;
strncpy(month, time_string + index, 2);
index += 2;
strncpy(Dayofweek, time_string + index, 1);
index += 1;
strncpy(day, time_string + index, 2);
index += 2;
strncpy(hour, time_string + index, 2);
index += 2;
strncpy(minute, time_string + index, 2);
index += 2;
strncpy(second, time_string + index, 2);
index += 2;
strncpy(Mill, time_string + index, 2);
index += 2;
system_time.wYear = atoi(year);
system_time.wMonth = atoi(month);
system_time.wDay = atoi(day);
system_time.wHour = atoi(hour);
system_time.wMinute = atoi(minute);
system_time.wSecond = atoi(second);
system_time.wDayOfWeek = atoi(Dayofweek);
system_time.wSecond = atoi(Mill);
SetLocalTime(&system_time);
SetLocalTime(&system_time);
if (!SetLocalTime(&system_time))
{
cout << "Set local time failed " << WSAGetLastError()<<endl;
return -1;
}
return 0;
}
- 对于TCP连接来说,在得到信息之后要断开连接,UDP不存在连接也就不需要断开
- 服务器端在发送的时候也要注意将时间数据先进行处理,以字符串的形式发送
//处理时间,将SYSTEMTIME数据处理成字符串,便于发送给客户端
void get_time()
{
SYSTEMTIME system_time = { 0 };
GetLocalTime(&system_time);
memset(timedata, 0, sizeof(timedata));
int index = 0;
char tmp4[4];
char tmp2[2];
memset(timedata, 0, 15);
_itoa((int)system_time.wYear, tmp4, 10);
strcat(timedata, tmp4);
_itoa((int)system_time.wMonth, tmp2, 10);
strcat(timedata, tmp2);
memset(tmp2, 0, sizeof(tmp2));
_itoa((int)system_time.wDayOfWeek, tmp2, 10);
strcat(timedata, tmp2);
_itoa((int)system_time.wDay, tmp2, 10);
strcat(timedata, tmp2);
_itoa((int)system_time.wHour, tmp2, 10);
strcat(timedata, tmp2);
_itoa((int)system_time.wMinute, tmp2, 10);
strcat(timedata, tmp2);
_itoa((int)system_time.wSecond, tmp2, 10);
strcat(timedata, tmp2);
_itoa((int)system_time.wMilliseconds, tmp2, 10);
strcat(timedata, tmp2);
cout << "Server Time : "<<timedata << endl;
return;
}
- 为了要能够接受TCP和UDP数据,所以在同一个端口上要同时绑定连个SOCKET
// 为TCP套接字绑定地址和端口号
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(TCPSocket, (const struct sockaddr*)&addrServ, sizeof(SOCKADDR_IN));
if (iResult == SOCKET_ERROR)
{
printf("bind failed with error: %d\n", WSAGetLastError());
closesocket(TCPSocket);
WSACleanup();
return 1;
}
// TCP连接监听套接字
iResult = listen(TCPSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR)
{
printf("listen failed !\n");
closesocket(TCPSocket);
WSACleanup();
return -1;
}
// 为UDP套接字绑定地址和端口号
addrServ1.sin_family = AF_INET;
addrServ1.sin_port = htons(DEFAULT_PORT); // 监听端口为DEFAULT_PORT
addrServ1.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
iResult = bind(UDPSocket, (const struct sockaddr*)&addrServ1, sizeof(SOCKADDR_IN));
if (iResult == SOCKET_ERROR)
{
printf("bind failed with error: %d\n", WSAGetLastError());
closesocket(UDPSocket);
closesocket(TCPSocket);
WSACleanup();
return 1;
}
//注册TCPSOCKET和UDPSOCKET
fd_set fdRead, fdSocket;
FD_ZERO(&fdSocket);
FD_SET(TCPSocket, &fdSocket);
FD_SET(UDPSocket, &fdSocket);
- 在SELECT函数执行的过程中,如果是TCPSOCKET的连接,就将新的连接请求放入集合中,那么在后面的循环中就能处理,如果是UDPSOCKET,那么就直接用创建的UDPSOCKET和客户端进行数据通信,如果是之前的新的SOCKET连接,那就进行时间请求的通信
- 在新的TCP连接请求达到的时候,输出IP地址和端口号
//有新的连接请求
AcceptSocket = accept(TCPSocket, (sockaddr FAR*)&addrClient, &addrClientlen);
if (AcceptSocket == INVALID_SOCKET)
{
printf("accept failed !\n");
closesocket(UDPSocket);
closesocket(TCPSocket);
WSACleanup();
return 1;
}
//成功接收新的TCP连接
cout << "Received time request from ( " << inet_ntoa(addrClient.sin_addr) << " :" << addrClient.sin_port << " )" << endl;
- 在接受到UDP数据的时候,显示数据来源的IP和端口号
//获取客户端的时间请求信息