基础通信模型——客户端及完整代码与运行情况

116 阅读6分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

客户端

1. 打开网络库

	/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
	WORD wdVersion = MAKEWORD(2, 2);
	int a = *((char*)&wdVersion);
	int b = *((char*)&wdVersion + 1);

	LPWSADATA lpw = (WSADATA*)malloc(sizeof(WSADATA));
	int nRes = WSAStartup(wdVersion, lpw);

	if (0 != nRes)
	{
		switch (nRes)
		{
		case WSASYSNOTREADY:
			printf("解决方案:重启。。。");
			break;
		case WSAVERNOTSUPPORTED:
			break;
		case WSAEINPROGRESS:
			break;
		case WSAEPROCLIM:
			break;
		case WSAEFAULT:
			break;
		}
		return 0;

	}

  1. 校验版本
	if (2 != HIBYTE(lpw->wVersion) || 2 != LOBYTE(lpw->wVersion))
	{
		printf("版本有问题!");
		WSACleanup();
		return 0;
	}
  1. 创建的是服务器的SOCKET句柄
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);


	if (INVALID_SOCKET == socketServer)
	{
		int err = WSAGetLastError();


		//清理网络库,不关闭句柄
		WSACleanup();
		return 0;
	}

	printf("客户端句柄创建成功!\n");
  1. 连接服务器
struct sockaddr_in serverMsg;
	serverMsg.sin_family = AF_INET;
	serverMsg.sin_port = htons(12345);
	serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	if (connect(socketServer, (struct sockaddr*)&serverMsg, sizeof(serverMsg)) == SOCKET_ERROR)
	{
		int err = WSAGetLastError();//取错误码
		printf("连接服务器错误码为:%d\n", err);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库

		return 0;
	}

	printf("服务器端连接成功!\n");

	char ssendbuff[1500] = { 0 };
	char recvbuff[1500] = { 0 };
	while (1)
	{

		scanf("%s", ssendbuff);
		if (send(socketServer, ssendbuff, sizeof(ssendbuff), 0) == SOCKET_ERROR)
		{
			int err = WSAGetLastError();//取错误码
			printf("client send失败错误码为:%d\n", err);
			//不用关闭socket
			//根据实际情况处理
		}
		//printf("发送消息成功!\n");


		int res = recv(socketServer, recvbuff, sizeof(recvbuff), 0);
		if (res == 0)
		{
			printf("连接中断或客户端已断开连接!");
		}
		else if (res == SOCKET_ERROR)
		{
			int err = WSAGetLastError();//取错误码
			printf("client recv失败错误码为:%d\n", err);
		}
		else
		{
			printf("收到服务器消息长度:%d,具体内容为:%s\n", res, recvbuff);//打印正确接收到的数据
		}
	}
  1. 关闭资源
	closesocket(socketServer);

	WSACleanup();

	free(lpw);

完整代码

服务端

# include <WinSock2.h>
# include<stdio.h>
# pragma comment(lib, "Ws2_32.lib")

int main(void) {


//1.打开网络库

	//int WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
	WORD wdVersion = MAKEWORD(2, 2);
	/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h 
	* 获取版本为2.2
	*/
	int a = *((char*)&wdVersion);
	int b = *((char*)&wdVersion + 1);

	LPWSADATA lpw = (WSADATA*)malloc(sizeof(WSADATA));//在程序结束时free掉lpw指针
	int nRes = WSAStartup(wdVersion, lpw);
	/*
	* The WSAStartup function initiates use of the Winsock DLL by a process.
	* WSAStartup函数通过进程启动Winsock DLL的使用。
	* 返回值类型为int
	* 如果NRes==0证明启动成功
	* 在程序结束时要关闭
	*/
	
	//判断是否启动成功
	if (0 != nRes) {
		switch (nRes)
		{
			case WSASYSNOTREADY:
				printf("解决方案:重启...");
				break;
			case WSAVERNOTSUPPORTED:
				break;
			case WSAEINPROGRESS:
				break;
			case WSAEPROCLIM:
				break;
			case WSAEFAULT:
				break;
			default:
				break;
		}
		return 0;
	}
	


//2.校验版本

	/*
	* HIBYTE: 
		参数:无Value类型
		作用:Retrieves the high-order byte from the given 16-bit value.
			 从给定的16位值检索高阶字节。
	  LOBYTE:
		与HIBYTE类似,从给定的16位值检索低阶字节。
	  */
	if (2 != HIBYTE(lpw->wVersion) || 2 != LOBYTE(lpw->wVersion)) {
		printf("版本有问题!");
		WSACleanup();//关闭网络库
		return 0;
	}


//3.创建SOCKET
	/*
	(SOCKEt套接字)
	所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
	一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
	从所处的地位来讲,套接字上联应用进程,下联网络协议栈,
	是应用程序通过网络协议进行通信的接口,
	是应用程序与网络协议根进行交互的接口
	*/

	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	/*TCP的SOCKET实例化
		
		socket的三个参数:
		地址类型、套接字类型、协议类型。

		成功返回Socket句柄,失败返回INVALID_SOCKET

		使用完毕后需要销毁句柄
	*/

	//判断是否实例化成功,如果失败,关闭网络库,不关闭句柄
	if (INVALID_SOCKET == socketServer) {
		WSACleanup();
		return 0;
	}

	
//4.绑定地址与端口
	struct sockaddr_in si;//创建结构体si
	si.sin_family = AF_INET;
	si.sin_port = htons(12345);//用htons宏将整型转为端口号的无符号整型

	si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	
	/*
	bind三个参数:服务器SOCKET句柄、sockaddr结构体指针、sockaddr结构体长度

	sockaddr_in 更加的方便填写端口号和IP地址,
	因此参数2用sockaddr_in来定义,
	然后再强转为sockaddr类型传入bind
	*/
	if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
	{
		int err = WSAGetLastError();//取错误码
		printf("服务器bind失败错误码为:%d\n",err);
		closesocket(socketServer);//关闭句柄
		WSACleanup();//清空网络库

		return 0;
	}
	printf("服务器端bind成功!\n");


//5.开始监听
	/*
	listen:
		作用:The listen function places a socket in a state in which it is listening for an incoming connection.
			  listen函数将套接字置于监听传入连接的状态。
		参数:1.服务器套接字句柄
			 2.The maximum length of the queue of pending connections.
			    挂起连接队列的最大长度。
				如果设置为SOMAXCONN,则负责套接字s的底层服务提供商将backlog设置为最大合理值。
	*/
	if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) {
		int err = WSAGetLastError();//取错误码
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}


//6.等待客户端连接
	struct sockaddr_in clientMsg;
	int clientMsgLen = sizeof(clientMsg);
	SOCKET socketClient = accept(socketServer, (struct sockaddr*)&clientMsg, &clientMsgLen);
	/*
	accept用于接收客户端的句柄,三次握手的连接操作在listen时完成
	*/
	if (INVALID_SOCKET == socketClient) {
		int err = WSAGetLastError();//取错误码
		printf("获取客户端句柄失败错误码为:%d\n", err);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库

		return 0;
	}
	printf("客户端连接成功!\n");


//7.与客户端收发消息(循环)
	char recvbuff[1500] = { 0 };
	char csendbuff[1500] = { 0 };

	while (1) {
		int res = recv(socketClient, recvbuff, sizeof(recvbuff), 0);
		if (res == 0) {
			printf("连接中断或客户端已断开连接!");
		}
		else if (res == SOCKET_ERROR) {
			int err = WSAGetLastError();//取错误码
			printf("server recv失败错误码为:%d\n", err);
		}
		else {
			printf("收到客户机消息长度:%d,具体内容:%s\n",res, recvbuff);
		}

		scanf("%s", csendbuff);
		if (send(socketClient, csendbuff, sizeof(csendbuff), 0) == SOCKET_ERROR) {
			int err = WSAGetLastError();//取错误码
			printf("server send失败错误码为:%d\n", err);
			//不用关闭socket
		}

	}
	
	
	closesocket(socketClient);
	closesocket(socketServer);
	WSACleanup();

	free(lpw);

	system("pause");
	return 0;
}

客户端

#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")

int main(void)
{
	/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
	WORD wdVersion = MAKEWORD(2, 2);
	int a = *((char*)&wdVersion);
	int b = *((char*)&wdVersion + 1);

	LPWSADATA lpw = (WSADATA*)malloc(sizeof(WSADATA));
	int nRes = WSAStartup(wdVersion, lpw);

	if (0 != nRes)
	{
		switch (nRes)
		{
		case WSASYSNOTREADY:
			printf("解决方案:重启。。。");
			break;
		case WSAVERNOTSUPPORTED:
			break;
		case WSAEINPROGRESS:
			break;
		case WSAEPROCLIM:
			break;
		case WSAEFAULT:
			break;
		}
		return 0;

	}


	//校验版本	
	if (2 != HIBYTE(lpw->wVersion) || 2 != LOBYTE(lpw->wVersion))
	{
		printf("版本有问题!");
		WSACleanup();
		return 0;
	}

	//这里创建的是服务器的SOCKET句柄
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);


	if (INVALID_SOCKET == socketServer)
	{
		int err = WSAGetLastError();


		//清理网络库,不关闭句柄
		WSACleanup();
		return 0;
	}

	printf("客户端句柄创建成功!\n");

	//这里写连接服务器的代码
	struct sockaddr_in serverMsg;
	serverMsg.sin_family = AF_INET;
	serverMsg.sin_port = htons(12345);
	serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	if (connect(socketServer, (struct sockaddr*)&serverMsg, sizeof(serverMsg)) == SOCKET_ERROR)
	{
		int err = WSAGetLastError();//取错误码
		printf("连接服务器错误码为:%d\n", err);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库

		return 0;
	}

	printf("服务器端连接成功!\n");

	char ssendbuff[1500] = { 0 };
	char recvbuff[1500] = { 0 };
	while (1)
	{

		scanf("%s", ssendbuff);
		if (send(socketServer, ssendbuff, sizeof(ssendbuff), 0) == SOCKET_ERROR)
		{
			int err = WSAGetLastError();//取错误码
			printf("client send失败错误码为:%d\n", err);
			//不用关闭socket
			//根据实际情况处理
		}
		//printf("发送消息成功!\n");


		int res = recv(socketServer, recvbuff, sizeof(recvbuff), 0);
		if (res == 0)
		{
			printf("连接中断或客户端已断开连接!");
		}
		else if (res == SOCKET_ERROR)
		{
			int err = WSAGetLastError();//取错误码
			printf("client recv失败错误码为:%d\n", err);
		}
		else
		{
			printf("收到服务器消息长度:%d,具体内容为:%s\n", res, recvbuff);//打印正确接收到的数据
		}
	}


	closesocket(socketServer);

	WSACleanup();

	free(lpw);

	system("pause");
	return 0;
}

启动

这里我们要将客户端与服务端放在两个项目中,启动时先启动服务器再启动客户端。

启动服务端

image.png

启动客户端

image.png

在客户端输入传输数据

image.png

在服务端输入传输数据

image.png