一、
实验环境
Windows10 vs2017 c/c++
二、实验内容
1.设计思路
- 基于流式套接字的时间同步服务器设计
要求使用流式套接字编程,实现时间同步服务器和客户端,该服务器能够接收客户端的查询请求,获取本地时间,并将结果发送回客户端,客户端将该时间显示出来,如图1所示。
(1) time()函数、ctime()函数为时间处理函数。
(2)客户的recv函数需要循环接收。
1.1服务器(时间服务器)
#include "pch.h"
#include <iostream>
#include <time.h>
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")
#define BUF_SZIE 64
int main()
{
WSADATA wsd; //WSADATA变量
SOCKET sServer; //服务器套接字
SOCKET sClient; //客户端套接字
SOCKADDR_IN addrServ;; //服务器地址
char buf[BUF_SZIE]; //接收数据缓冲区
char buf1[BUF_SZIE]; //发送
int retVal; //返回值
//初始化套结字动态库
if (WSAStartup(MAKEWORD(2, 2), &wsd) !=
0)
{
printf("WSAStartupfailed!\n");
return -1;
}
//创建套接字
sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sServer)
{
printf("socketfailed!\n");
WSACleanup();//释放套接字资源;
return -1;
}
//服务器套接字地址
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(4999);
addrServ.sin_addr.s_addr = INADDR_ANY;
//绑定套接字
retVal=bind(sServer(LPSOCKADDR)&addrServ,sizeof(SOCKADDR_IN));
if (SOCKET_ERROR == retVal)
{
printf("bindfailed!\n");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//开始监听
retVal = listen(sServer, 3);
if (SOCKET_ERROR == retVal)
{
printf("listenfailed!\n");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//接受客户端请求
sockaddr_in addrClient;
int addrClientlen = sizeof(addrClient);
sClient=accept(sServer(sockaddr*)&addrClient,&addrClientlen);
if (INVALID_SOCKET == sClient)
{
printf("acceptfailed!\n");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
while (SOCKET_ERROR != retVal) {
//接收客户端数据
ZeroMemory(buf, BUF_SZIE);
retVal = recv(sClient, buf, BUF_SZIE, 0);
if (SOCKET_ERROR == retVal)
{
printf("recvfailed!\n");
closesocket(sClient); //关闭套接字
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
printf("%s\n", buf); //输出"MyTCP"
//退出
time_t t1;
t1 = time(&t1);
// printf("%s\n", ctime(&t1));
ZeroMemory(buf1, BUF_SZIE);
· strcpy_s(buf1, ctime(&t1));
retVal = send(sClient, buf1,strlen(buf1), 0);
if (SOCKET_ERROR == retVal)
{
printf("sendfailed!\n");
closesocket(sClient); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
}
closesocket(sClient); //关闭套接字
//}
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return 0;
}
1.2客户端(时间请求)
#include "pch.h"
#include <iostream>
#include <time.h>
#define BUF_SZIE 64
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[])
{
WSADATA wsd; //WSADATA变量
SOCKET sHost; //服务器套接字
SOCKADDR_IN servAddr; //服务器地址
char buf[BUF_SZIE]; //发送数据缓冲区
char buf1[BUF_SZIE]; //接收数据缓冲区
int retVal; //返回值
//初始化套结字动态库
if (WSAStartup(MAKEWORD(2, 2), &wsd) !=0)
{
printf("WSAStartupfailed!\n");
return -1;
}
//创建套接字
sHost= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sHost)
{
printf("socketfailed!\n");
WSACleanup();//释放套接字资源
return -1;
}
//设置服务器地址
servAddr.sin_family= AF_INET;
servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servAddr.sin_port= htons((short)4999);
int nServAddlen = sizeof(servAddr);
//连接服务器
retVal=connect(sHost(LPSOCKADDR)&servAddr,sizeof(servAddr));
if (SOCKET_ERROR == retVal)
{
printf("connectfailed!\n");
closesocket(sHost); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
while (SOCKET_ERROR != retVal) {
//向服务器发送数据
ZeroMemory(buf, BUF_SZIE);
strcpy_s(buf,"timeplease!");
retVal= send(sHost, buf, strlen(buf), 0);
if (SOCKET_ERROR == retVal)
{
printf("sendfailed!\n");
closesocket(sHost); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
ZeroMemory(buf1, BUF_SZIE);
retVal= recv(sHost, buf1, BUF_SZIE, 0);
if (SOCKET_ERROR == retVal)
{
printf("recvfailed!\n");
closesocket(sHost); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
printf("%s\n", buf1); //输出
Sleep(1000);
}
closesocket(sHost); //关闭套接字
WSACleanup(); //释放套接字资源
return 0;
}
2.基于流式套接字的服务器回射程序设计
编写一服务器程序和客户程序,要求客户每输入一行数据,服务器接收后加上echo:回送给客户程序,当客户输入“q”后退出。
(1) 为了提高流式套接字网络程序对流数据的接收能力,以循环服务器方式分别实现以下要求:
a. 如图2所示,客户端接收数据以recvline接收一行数据。
b. 如图3所示,服务器和客户端接收数据以recvn定长方式接收,其中长度由运行程序时指定。
c. 如图4所示,服务器和客户端接收数据均以recvvl变长方式接收,即将发送的消息设计为消息头和消息体,消息头包含消息体的长度。
服务器:
#include "pch.h"
#include <iostream>
#include <time.h>
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")
#define BUF_SZIE 64
using namespace std;
int main()
{
WSADATA wsd; //WSADATA变量
SOCKET sServer; //服务器套接字
SOCKET sClient; //客户端套接字
SOCKADDR_IN addrServ;; //服务器地址
char buf[BUF_SZIE]; //接收数据缓冲区
char buf1[BUF_SZIE]; //发送
int retVal; //返回值
//初始化套结字动态库
if (WSAStartup(MAKEWORD(2, 2), &wsd) !=0)
{
printf("WSAStartup failed!\n");
return -1;
}
//创建套接字
sServer
= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sServer)
{
printf("socketfailed!\n");
WSACleanup();//释放套接字资源;
return -1;
}
//服务器套接字地址
addrServ.sin_family= AF_INET;
addrServ.sin_port= htons(4999);
addrServ.sin_addr.s_addr = INADDR_ANY;
//绑定套接字
retVal=bind(sServer(LPSOCKADDR)&addrServ,sizeof(SOCKADDR_IN));
if (SOCKET_ERROR == retVal)
{
printf("bindfailed!\n");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//开始监听
retVal= listen(sServer, 3);
if (SOCKET_ERROR == retVal)
{
printf("listenfailed!\n");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//接受客户端请求
sockaddr_in addrClient;
int addrClientlen = sizeof(addrClient);
sClient=accept(sServer(sockaddr*)&addrClient,&addrClientlen);
if (INVALID_SOCKET == sClient)
{
printf("acceptfailed!\n");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
//显示客户端的IP和端口
char *pClientIP = inet_ntoa(addrClient.sin_addr);
u_short clientPort = ntohs(addrClient.sin_port);
cout<< "Accept aclient." << endl;
cout<< "IP: " << pClientIP << endl;
cout<< "Port: " << clientPort << endl;
//接收客户端数据并回射
while (SOCKET_ERROR != retVal) {
ZeroMemory(buf, BUF_SZIE);
retVal= recv(sClient, buf, BUF_SZIE, 0);
if (SOCKET_ERROR == retVal)
{
printf("recvfailed!\n");
closesocket(sClient); //关闭套接字
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
printf("echo:%s\n", buf);
ZeroMemory(buf1, BUF_SZIE);
strcpy_s(buf1,"echo:");
strncat(buf1, buf, BUF_SZIE);
retVal= send(sClient, buf1, strlen(buf1), 0);
if (SOCKET_ERROR == retVal)
{
printf("sendfailed!\n");
closesocket(sClient); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
}
closesocket(sClient); //关闭套接字
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return 0;
客户端:
#include "pch.h"
#include <iostream>
#include <time.h>
#include <stdio.h>
#define BUF_SZIE 64
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")
using namespace std;
BOOL RecvLine(SOCKET s, char* buf); //读取一行数据
int recvn(SOCKET s, char * recvbuf, unsigned int fixedlen);
int recvvl(SOCKET s, char * recvbuf, unsigned int recvbuflen);
int main(int argc, char* argv[])
{
WSADATA wsd; //WSADATA变量
SOCKET sHost; //服务器套接字
SOCKADDR_IN servAddr; //服务器地址
char buf[BUF_SZIE]; //发送数据缓冲区
char buf1[BUF_SZIE]; //接收数据缓冲区
int retVal; //返回值
//初始化套结字动态库
if (WSAStartup(MAKEWORD(2, 2), &wsd) !=0)
{
printf("WSAStartupfailed!\n");
return -1;
}
//创建套接字
sHost= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sHost)
{
printf("socketfailed!\n");
WSACleanup();//释放套接字资源
return -1;
}
//准备连接服务器
cout<< "Clientsucceeded!" << endl;
cout<< "Be ready toconnect to server..." << endl;
//设置服务器地址
servAddr.sin_family= AF_INET;
servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servAddr.sin_port= htons((short)4999);
int nServAddlen = sizeof(servAddr);
//连接服务器
retVal=connect(sHost(LPSOCKADDR)&servAddr,sizeof(servAddr));
if (SOCKET_ERROR == retVal)
{
printf("connect
failed!\n");
closesocket(sHost); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
//连接服务器成功
cout<< "Connectsuccessfully!" << endl;
while (SOCKET_ERROR != retVal) {
//向服务器发送数据
ZeroMemory(buf, BUF_SZIE);
gets_s(buf);
unsigned int len = strlen(buf);//获取读入字符串长度。
buf[len]= '\n';//结尾增加换行符。
buf[len+ 1] = '\0';//赋值新的结束符
if (buf[0] == 'q' || buf[0] == 'Q') break;
retVal= send(sHost, buf, strlen(buf), 0);
if (SOCKET_ERROR == retVal)
{
printf("send failed!\n");
closesocket(sHost); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
//行接收
if (!RecvLine(sHost,buf1))
{
printf("recvfailed!\n");
closesocket(sHost); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
printf("%s\n", buf1);
}
closesocket(sHost); //关闭套接字
WSACleanup(); //释放套接字资源
return 0;
}
BOOL RecvLine(SOCKET s, char* buf)
{
BOOL retVal = TRUE; //返回值
BOOL bLineEnd = FALSE; //行结束
int nReadLen = 0; //读入字节数
int nDataLen = 0; //数据长度
ZeroMemory(buf, BUF_SZIE);
while (!bLineEnd) //与客户端连接 没有换行
{
nReadLen= recv(s, buf + nDataLen, 1, 0);//每次接收一个字节
//错误处理
if (SOCKET_ERROR == nReadLen)
{
retVal= FALSE; //读数据失败
break; //跳出循环
}
if (0 == nReadLen)//客户端关闭
{
retVal= FALSE; //读数据失败
break; //跳出循环
}
//读入数据
if ('\n' == *(buf + nDataLen)) //换行符
{
bLineEnd= TRUE; //接收数据结束
}
else {
nDataLen+= nReadLen; //增加数据长度
}
}
return retVal;
}
服务器:(主要代码)
//接收客户端数据并回射
while (SOCKET_ERROR != retVal) {
ZeroMemory(buf, BUF_SZIE);
int iResult =recvn(sClient, buf, 2);
if (iResult != 2)
{
//如果变长消息在接收时没有返回足够的数据就返回0(连接关闭)或-1(发生错误)
if (iResult == -1)
{
printf("接收发生错误: %d\n",WSAGetLastError());
return -1;
}
else
{
printf("连接关闭\n");
return 0;
}
}
printf("ehco:%s\n", buf);
ZeroMemory(buf1, BUF_SZIE);
strcpy_s(buf1,"echo:");
strncat(buf1,buf, BUF_SZIE);
retVal= send(sClient, buf1, strlen(buf1), 0);
if (SOCKET_ERROR == retVal)
{
printf("sendfailed!\n");
closesocket(sClient); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
}
int recvn(SOCKET s, char * recvbuf, unsigned int fixedlen)
{
int iResult;//存储单次recv操作的返回值
int cnt;//用于统计相对于固定长度,剩余多少字节尚未接收
cnt= fixedlen;
while (cnt > 0)
{
iResult= recv(s, recvbuf, cnt, 0);
if (iResult < 0)
{
//数据接收出现错误,返回失败
printf("接收发生错误: %d\n", WSAGetLastError());
return -1;
}
if (iResult == 0)
{
//对方关闭连接,返回已接收到的小于fixedlen的字节数
printf("连接关闭\n");
return fixedlen - cnt;
}
//接收缓存指针向后移动
recvbuf += iResult;
//更新cnt值
cnt-= iResult;
}
return fixedlen;
}
服务器:
//接收客户端数据并回射
while (SOCKET_ERROR != retVal) {
ZeroMemory(buf, BUF_SZIE);
retVal= recvvl(sClient, buf, BUF_SZIE);
if (SOCKET_ERROR == retVal)
{
printf("recv failed!\n");
closesocket(sClient); //关闭套接字
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
printf("ehco:%s\n", buf);
ZeroMemory(buf1, BUF_SZIE);
strcpy_s(buf1,"echo:");
strncat(buf1,buf, BUF_SZIE);
unsigned int len = (unsigned int)strlen(buf1);
len= htonl(len);
retVal= send(sClient, (char*)&len, sizeof(unsigned int), 0);
if (SOCKET_ERROR == retVal)
{
printf("send failed!\n");
closesocket(sClient); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
retVal= send(sClient, buf1, strlen(buf1), 0);
if (SOCKET_ERROR == retVal)
{
printf("sendfailed!\n");
closesocket(sClient); //关闭套接字
WSACleanup(); //释放套接字资源
return -1;
}
}
int recvn(SOCKET s, char * recvbuf, unsigned int fixedlen)
{
int iResult;//存储单次recv操作的返回值
int cnt;//用于统计相对于固定长度,剩余多少字节尚未接收
cnt= fixedlen;
while (cnt > 0)
{
iResult= recv(s, recvbuf, cnt, 0);
if (iResult < 0)
{
//数据接收出现错误,返回失败
printf("接收发生错误: %d\n", WSAGetLastError());
return -1;
}
if (iResult == 0)
{
//对方关闭连接,返回已接收到的小于fixedlen的字节数
printf("连接关闭\n");
return fixedlen - cnt;
}
//printf("接收到的字节数: %d\n", iResult);
//接收缓存指针向后移动
recvbuf += iResult;
//更新cnt值
cnt-= iResult;
}
return fixedlen;
}
int recvvl(SOCKET s, char * recvbuf, unsigned int recvbuflen)
{
int iResult;//存储单次recv操作的返回值
unsigned int reclen; //用于存储报文头部存储的长度信息
//获取接收报文长度信息
iResult= recvn(s, (char *)&reclen, sizeof(unsigned int));
if (iResult != sizeof(unsigned int))
{
//如果长度字段在接收时没有返回一个整型数据就返回0(连接关闭)或-1(发生错误)
if (iResult == -1)
{
printf("接收发生错误: %d\n", WSAGetLastError());
return -1;
}
else
{
printf("连接关闭\n");
return 0;
}
}
//转换网络字节顺序到主机字节顺序
reclen= ntohl(reclen);
if (reclen > recvbuflen)
{
//如果recvbuf没有足够的空间存储变长消息,则接收该消息并丢弃,返回错误
while (reclen > 0)
{
iResult= recvn(s, recvbuf, recvbuflen);
if (iResult != recvbuflen)
{
//如果变长消息在接收时没有返回足够的数据就返回0(连接关闭)或-1(发生错误)
if (iResult == -1)
{
printf("接收发生错误: %d\n", WSAGetLastError());
return -1;
}
else
{
printf("连接关闭\n");
return 0;
}
}
reclen-= recvbuflen;
//处理最后一段数据长度
if (reclen < recvbuflen)
recvbuflen = reclen;
}
printf("可变长度的消息超出预分配的接收缓存\r\n");
return -1;
}
//接收可变长消息
iResult= recvn(s, recvbuf, reclen);
if (iResult != reclen)
{
//如果消息在接收时没有返回足够的数据就返回0(连接关闭)或-1(发生错误)
if (iResult == -1)
{
printf("接收发生错误: %d\n", WSAGetLastError());
return -1;
}
else
{
printf("连接关闭\n");
return 0;
}
}
return iResult;
}
并发服务器
并发服务器:
#include "pch.h"
#include <iostream>
#include <time.h>
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")
#define BUF_SZIE 64
using namespace std;
typedef struct my_file
{
SOCKET clientSocket; //文件内部包含了一个SOCKET 用于和客户端进行通信
sockaddr_in clientAddr; //用于保存客户端的socket地址
int id; //文件块的序号
}F;
DWORD WINAPI mywork(const LPVOID arg)
{
char buf[BUF_SZIE]; //接收数据缓冲区
char buf1[BUF_SZIE]; //发送
int retVal=0; //返回值
F *temp = (F*)arg;
char *pClientIP =inet_ntoa(temp->clientAddr.sin_addr);
u_short clientPort = ntohs(temp->clientAddr.sin_port);
cout<< "Accept a client." << endl;
cout<< "IP: " << pClientIP << endl;
cout<< "Port: " << clientPort << endl;
//接收客户端数据并回射
while (SOCKET_ERROR != retVal) {
ZeroMemory(buf, BUF_SZIE);
retVal= recv(temp->clientSocket, buf, BUF_SZIE, 0);
if (SOCKET_ERROR == retVal)
{
printf("recvfailed!\n");
closesocket(temp->clientSocket); //关闭套接字
return -1;
}
printf("%s\n", buf);
ZeroMemory(buf1, BUF_SZIE);
strcpy_s(buf1,"echo:");
strncat(buf1,buf, BUF_SZIE);
retVal= send(temp->clientSocket, buf1, strlen(buf1), 0);
if (SOCKET_ERROR == retVal)
{
printf("send failed!\n");
closesocket(temp->clientSocket); //关闭套接字
return -1;
}
}
closesocket(temp->clientSocket); //关闭套接字
}
int main()
{
WSADATA wsd; //WSADATA变量
SOCKET sServer; //服务器套接字
SOCKET sClient; //客户端套接字
SOCKADDR_IN addrServ;; //服务器地址
char buf[BUF_SZIE]; //接收数据缓冲区
char buf1[BUF_SZIE]; //发送
int retVal; //返回值
//初始化套结字动态库
if (WSAStartup(MAKEWORD(2, 2), &wsd) !=
0)
{
printf("WSAStartupfailed!\n");
return -1;
}
//创建套接字
sServer= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sServer)
{
printf("socketfailed!\n");
WSACleanup();//释放套接字资源;
return -1;
}
//服务器套接字地址
addrServ.sin_family= AF_INET;
addrServ.sin_port= htons(4999);
addrServ.sin_addr.s_addr = INADDR_ANY;
//绑定套接字
retVal= bind(sServer, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));
if (SOCKET_ERROR == retVal)
{
printf("bindfailed!\n");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
HANDLE hThread[20]; //获取句柄
//开始监听
retVal= listen(sServer, 20);
if (SOCKET_ERROR == retVal)
{
printf("listenfailed!\n");
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
return -1;
}
for (int i = 0; i < 20;i++) {
F *temp = new F; //创建新的传输结构体
sockaddr_in clntAddr;
int nSize = sizeof(SOCKADDR);
SOCKET clientSock=accept(sServer(SOCKADDR*)&clntAddr,&nSize); //temp数据成员赋值
temp->clientSocket= clientSock;
temp->id= i + 1;
temp->clientAddr= clntAddr; //通过句柄创建子线程
hThread[i]= CreateThread(NULL, 0, &mywork, temp, 0, NULL);
}
WaitForMultipleObjects(20,hThread, TRUE, INFINITE);
cout<< WSAGetLastError() << endl; //查看错误信息
closesocket(sServer); //关闭套接字
WSACleanup(); //释放套接字资源;
cout<< "serverclose!" << endl;
return 0;
}