基础通信模型——服务器端

140 阅读5分钟

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

网络编程

这个星期开的网络编程课,没想到是用C++语言讲解的,之前了解学习过一点java的通信模型,所以理解起来还好吧...

里面的代码是老师给出,我照着敲了一遍加上了一些注释。

服务器端:

  1. 打开网络库
    • The WSAStartup function initiates use of the Winsock DLL by a process.
    • WSAStartup函数通过进程启动Winsock DLL的使用。
    • 返回值类型为int
    • 如果NRes==0证明启动成功
    • 在程序结束时要关闭
//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;
	}
	
  1. 校验版本
  • HIBYTE: 参数:无Value类型 作用:Retrieves the high-order byte from the given 16-bit value. 从给定的16位值检索高阶字节。
  • LOBYTE: 与HIBYTE类似,从给定的16位值检索低阶字节。
//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),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。 一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。 从所处的地位来讲,套接字上联应用进程,下联网络协议栈, 是应用程序通过网络协议进行通信的接口, 是应用程序与网络协议根进行交互的接口

TCP的SOCKET实例化

socket的三个参数:
        地址类型、套接字类型、协议类型。

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

使用完毕后需要销毁句柄
//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. 绑定地址与端口

bind三个参数:服务器SOCKET句柄、sockaddr结构体指针、sockaddr结构体长度

sockaddr_in 更加的方便填写端口号和IP地址, 因此参数2用sockaddr_in来定义, 然后再强转为sockaddr类型传入bind

//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");
  1. 开始监听

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设置为最大合理值。
        

//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;
	}

  1. 等待客户端连接

//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");
  1. 与客户端收发消息(循环)

//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
		}

	}
  1. 关闭资源
	
	closesocket(socketClient);
	closesocket(socketServer);
	WSACleanup();

	free(lpw);