Socket基础二:基于流式套接字的网络程序(时间同步服务器设计)

作者:刘磊 2020.4.26   参考书目:《Windows网络编程》刘琰等著

一、流式套接字

流式套接字是基于可靠的数据流传输服务,特点是面向连接(一对一传输),可靠,流式套接字编程与TCP协议原理关系密切。适合大数据量的数据传输应用,可靠性要求高的传输应用。

二、TCP – 传输控制协议

TCP是一个面向连接的传输层协议,提供高可靠性字节流传输服务,主要用于一次传输要交换大量报文的情形。为了保证传输可靠,TCP增加了许多开销:确认,流量控制,计时器以及连接管理等。
建立连接的三次握手:

第一次,客户端发信息给服务器,服务器收到了,确认了客户端发送正常。
第二次,服务器发信息给客户端,客户端收到了,确认了服务器接收和发送都正常。
第三次,客户端发信息给服务器,服务器周到了,确认了客户端接收正常。

关闭连接的四次挥手:

第一次,客户端没有信息要发了,便会告诉服务器我要关闭了。
第二次,服务器发送一个信号给客户端,告诉客户端,我知道你要关闭了。
第三次,服务器自己也没有信息要发送,便会告诉客户端我要关闭了。 第四次,客户端发送一个信号给服务器,告诉服务器,我知道你要关闭了。

三、流式套接字通信过程

1、服务器基本通信过程:

1)Windows Sockets DLL初始化,协商版本号。
2)创建套接字,指定使用TCP通信。
3)指定本地地址和通信端口。
4)等待客户端的连接请求。
5)进行数据传输。
6)关闭套接字。
7)结束Windows Sockets DLL的使用,释放资源

2、客户端基本通信过程:

1)Windows Sockets DLL初始化,协商版本号。
2)创建套接字,指定使用TCP通信。
3)指定服务器地址和通信端口。
4)向服务器发起连接请求。
5)进行数据传输。
6)关闭套接字。
7)结束Windows Sockets DLL的使用,释放资源。

四、基于流式套接字的时间同步服务器设计

1、服务器编程步骤

1)引用头文件。

#include <time.h>
#include <winsock.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib");

#define MAXLINE 4096	//接收缓冲区长度
#define LISTENQ 1024	//监听队列长度
#define SERVER_PORT 13	//时间同步服务器端口号

2)创建流式套接字。

if ((ListenSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)		
{		
	printf("socket函数调用失败,错误号:%d\n",WSAGetLastError());			
	WSACleanup();			
	return 1;		
}		
memset(&servaddr, 0, sizeof(servaddr));		
servaddr.sin_family = AF_INET;		
servaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1 ");
servaddr.sin_port = htons(SERVER_PORT);

3)绑定服务器的时间服务端口到已创建的套接字。

iResult = bind(ListenSocket, (struct sockaddr *) & servaddr, sizeof(servaddr));		
if (iResult == SOCKET_ERROR)		
{		
	printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());			
	closesocket(ListenSocket);			
	WSACleanup();			
	return 1;
}

4)把套接字变换成监听套接字。

iResult = listen(ListenSocket, LISTENQ);		
if (iResult == SOCKET_ERROR)		
{		
	printf("listen 函数调用错误,错误号:%d\n", WSAGetLastError());			
	closesocket(ListenSocket);			
	WSACleanup();			
	return 1;
}

5)接收客户连接,获取本地时间,发送应答。

//接收客户端连接请求,返回连接套接字ClientSocket		
ClientSocket = accept(ListenSocket, NULL, NULL);		
if (ClientSocket == INVALID_SOCKET)		
{		
	printf("accept 函数调用错误,错误号:%d\n", WSAGetLastError());			
	closesocket(ListenSocket);			
	WSACleanup();			
	return 1;		
} 		

//获取当前时间		
ticks = time(NULL);		
memset(buff, 0, sizeof(buff));		
sprintf(buff, "%.24s\r\n", ctime(&ticks));		
printf("获取当前时间:%s\n",buff); 		

//发送时间		
iSendResult = send(ClientSocket, buff, strlen(buff), 0);		
if (iSendResult == SOCKET_ERROR)		
{		
	printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());			
	closesocket(ListenSocket);			
	WSACleanup();			
	return 1;		
}
printf("向客户端发送时间成功\n");

6)主动中止连接。

iResult = shutdown(ClientSocket, SD_SEND);		
if (iResult == SOCKET_ERROR)		
{		
	printf("shutdown 函数调用错误,错误号:%d\n", WSAGetLastError());			
	closesocket(ListenSocket);			
	WSACleanup();			
	return 1;
}

7)等待其他客户端的连接请求,回到步骤5。

while(1);

8)关闭套接字,释放资源,程序终止。

closesocket(ListenSocket);		
WSACleanup();
return 0;

2、客户端编程步骤

1)引用头文件。

#include <winsock.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib"); 
#define MAXLINE 4096	//接收缓冲区长度
#define SERVER_PORT 13	//时间同步服务器端口号

2)创建流式套接字。

if ((ConnectSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)		
{		
	printf("socket函数调用失败,错误号:%d\n", WSAGetLastError());			
	WSACleanup();			
	return 1;
}

3)绑定服务器IP地址和端口

memset(&servaddr, 0, sizeof(servaddr));		
servaddr.sin_family = AF_INET;		
servaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(SERVER_PORT);

4)与服务器建立连接。

iResult = connect(ConnectSocket, (sockaddr *)&servaddr, sizeof(servaddr));
if (iResult == SOCKET_ERROR)		
{		
	printf("connect 函数调用错误,错误号:%d\n", WSAGetLastError());			
	closesocket(ConnectSocket);			
	WSACleanup();			
	return 1;
}

5)循环接收服务器返回的应答,并显示获得时间。

memset(&recvline, 0, sizeof(recvline));		
printf("当前时间是:");		
do {		
	iResult = recv(ConnectSocket, recvline, MAXLINE, 0);			
	if (iResult > 0)			
		printf("%s",recvline);			
	else			
	{			
		if (iResult == 0)					
			printf("对方连接关闭,退出\n");				
		else				
			printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());			
	}
} while (iResult > 0);

6)如果接收到服务器关闭或者网络出错的信号,则关闭套接字,释放资源,终止程序。

closesocket(ConnectSocket);		
WSACleanup();
return 0;

3、server.c

#include <time.h>
#include <winsock.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib"); 
#define MAXLINE 4096	//接收缓冲区长度
#define LISTENQ 1024	//监听队列长度
#define SERVER_PORT 13	//时间同步服务器端口号 
int main(int argc, char *argv[])
{	
	SOCKET ListenSocket = INVALID_SOCKET, ClientSocket = INVALID_SOCKET;	
	int iResult;	
	struct sockaddr_in servaddr;	
	char buff[MAXLINE];	time_t ticks;	
	int iSendResult; 	//初始化Windows Sockets DLL;协商版本号	
	
	WORD wVersionRequested;	
	WSADATA wsaData; 	//使用MAKEWORD(lowbyte, highbyte) 宏,在windef.h中声明	
	
	wVersionRequested = MAKEWORD(2, 2);	
	iResult = WSAStartup(wVersionRequested, &wsaData);	
	if (iResult != 0)	
	{	
		printf("WSAStartup函数调用错误,错误号:%d\n",WSAGetLastError());		
		return 1;	
	}	
	else	
	{	
		printf("初始化成功!\n");	
	} 	

	//创建流式套接字	
	if ((ListenSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)	
	{	
		printf("socket函数调用失败,错误号:%d\n",WSAGetLastError());		
		WSACleanup();		
		return 1;	
	}	
	memset(&servaddr, 0, sizeof(servaddr));	
	servaddr.sin_family = AF_INET;	
	servaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1 ");	
	servaddr.sin_port = htons(SERVER_PORT); 	

	//绑定服务器地址	
	iResult = bind(ListenSocket, (struct sockaddr *) & servaddr, sizeof(servaddr));	
	if (iResult == SOCKET_ERROR)	
	{	
		printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());		
		closesocket(ListenSocket);		
		WSACleanup();		
		return 1;	
	} 	
			
	//设置服务器位监听状态,监听队列长度为LISTENQ	
	iResult = listen(ListenSocket, LISTENQ);	
	if (iResult == SOCKET_ERROR)	
	{	
		printf("listen 函数调用错误,错误号:%d\n", WSAGetLastError());		
		closesocket(ListenSocket);		
		WSACleanup();	
		return 1;	
	} 

	while (1)	
	{		
		//接收客户端连接请求,返回连接套接字ClientSocket		
		ClientSocket = accept(ListenSocket, NULL, NULL);		
		if (ClientSocket == INVALID_SOCKET)		
		{			
			printf("accept 函数调用错误,错误号:%d\n", WSAGetLastError());			
			closesocket(ListenSocket);			
			WSACleanup();			
			return 1;		
		} 		

		//获取当前时间		
		ticks = time(NULL);		
		memset(buff, 0, sizeof(buff));		
		sprintf(buff, "%.24s\r\n", ctime(&ticks));		
		printf("获取当前时间:%s\n",buff); 		

		//发送时间		
		iSendResult = send(ClientSocket, buff, strlen(buff), 0);		
		if (iSendResult == SOCKET_ERROR)		
		{		
			printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());			
			closesocket(ListenSocket);			
			WSACleanup();			
			return 1;		
		}		
		printf("向客户端发送时间成功\n"); 		

		//停止连接,不再发送数据		
		iResult = shutdown(ClientSocket, SD_SEND);		
		if (iResult == SOCKET_ERROR)		
		{		
			printf("shutdown 函数调用错误,错误号:%d\n", WSAGetLastError());			
			closesocket(ListenSocket);			
			WSACleanup();			
			return 1;		
		} 		

		//关闭套接字		
		closesocket(ClientSocket);		
		printf("主动关闭连接\n");	
	} 	

	closesocket(ListenSocket);	
	WSACleanup();	
	return 0; 
}


4、client.c

#include <winsock.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib"); 
#define MAXLINE 4096	//接收缓冲区长度
#define SERVER_PORT 13	//时间同步服务器端口号 

int main(int argc, char *argv[])
{
	SOCKET ConnectSocket = INVALID_SOCKET;
	int iResult;	
	struct sockaddr_in servaddr;
	char recvline[MAXLINE + 1]; 	//初始化Windows Sockets DLL;协商版本号
		
	WORD wVersionRequested;	
	WSADATA wsaData; 	//使用MAKEWORD(lowbyte, highbyte) 宏,在windef.h中声明	
	
	wVersionRequested = MAKEWORD(2, 2);	
	iResult = WSAStartup(wVersionRequested, &wsaData);	
	if (iResult != 0)	
	{	
		printf("WSAStartup函数调用错误,错误号:%d\n", WSAGetLastError());		
		return 1;	
	}	
	else	
	{	
		printf("初始化成功!\n");	
	} 	

	//创建流式套接字	
	if ((ConnectSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)	
	{	
		printf("socket函数调用失败,错误号:%d\n", WSAGetLastError());		
		WSACleanup();		
		return 1;	
	} 	
	
	//服务器地址赋值	
	memset(&servaddr, 0, sizeof(servaddr));	
	servaddr.sin_family = AF_INET;	
	servaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");;	
	servaddr.sin_port = htons(SERVER_PORT);

 	//向服务器请求连接	
 	iResult = connect(ConnectSocket, (sockaddr *)&servaddr, sizeof(servaddr));	
 	if (iResult == SOCKET_ERROR)	
 	{	
 		printf("connect 函数调用错误,错误号:%d\n", WSAGetLastError());		
 		closesocket(ConnectSocket);		
 		WSACleanup();		
 		return 1;	
 	}

 	//持续接收数据,直到服务器关闭连接	
 	memset(&recvline, 0, sizeof(recvline));	
 	printf("当前时间是:");	
 	do {	
 		iResult = recv(ConnectSocket, recvline, MAXLINE, 0);		
 		if (iResult > 0)		
 			printf("%s",recvline);		
		else		
		{		
			if (iResult == 0)				
				printf("对方连接关闭,退出\n");			
			else			
				printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());		
		}	
	} while (iResult > 0); 	

	closesocket(ConnectSocket);	
	WSACleanup();	
	return 0;
}



5、结果图

在这里插入图片描述

原创文章 2 获赞 5 访问量 79

猜你喜欢

转载自blog.csdn.net/qq_44807456/article/details/105767538