Windows网络通信——简单的聊天程序

网络编程

  • 前提是应用程序所在的机器在一个网络(同一主机,局域网,互联网)之中
  • 多个程序(进程)之间能交换数据
  • 全双共(双向通信),即时,安全的TCP协议,一种用于网络数据传输的协议
  • IP协议用于机器在网络中的定位

Window下用TCP协议实现网络编程

网络应用的两种架构:C/S架构,B/S架构

通用windows网络编程的步骤

服务器 客户端 备注
1. 请求协议版本 1. 请求协议版本 统一服务器和客户端使用的通信协议
2. 创建socket 2. 创建socket 类似于微信这么一个通信的工具
3. 创建本机协议地址族 3. 获取服务器的协议地址族 地址族即存储IP地址,网络端口,通信协议相关信息的结构体(类似于服务器通信工具(socket)的微信号)
4. 绑定 将协议地址族绑定到服务器的socket上
5. 监听 为了形成一个安全稳定的传输通道
6. 等待客户端连接 4. 连接服务器
7. 通信(收/发) 5. 通信(收/发)
8. 关闭socket 6. 关闭socket
9. 清理协议 7. 清理协议

Demo第一版:单客户端-服务器(双向)

客户端程序

#include <stdio.h>

#include <windows.h>//包含window封装的通信相关的结构及接口

//加载静态库,该库包含WSAStartup,WSAClean,socket等接口
#pragma comment(lib, "ws2_32.lib")

int main()
{
	//1. 请求协议版本2.2
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);//wsaData存储返回的协议版本
	if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		printf("请求协议版本失败!\n");
		return -1;
	}
	printf("请求协议版本成功!\n");

	//2. 创建socket
	//para1 —— 通信协议类型
	//para2 —— 通信所用的数据类型,TCP使用的是数据流
	//para3 —— 保护方式
	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(SOCKET_ERROR == clientSocket)//-1
	{
		printf("创建socket失败\n!");
		//清理协议
		WSACleanup();
		return -2;
	}
	printf("创建socket成功!\n");

	//3. 获取协议地址族
	SOCKADDR_IN addr = { 0 };
	addr.sin_family = AF_INET;//协议版本
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP地址,inet_addr将字符串转成整数存储
	addr.sin_port = htons(10086);//端口号,0~65535,一般用10000左右的端口
	//4. 连接服务器
	int r = connect(clientSocket, (sockaddr*)&addr, sizeof(addr));
	if(-1 == r)
	{
		printf("连接服务器失败!\n");
		//关闭socket
		closesocket(clientSocket);
		//清理协议
		WSACleanup();
		return -2;
	}

	char buff[1024];
	while(1)
	{
		//清空buff
		memset(buff, 0, 1024);
		printf("请输入内容:");
		scanf("%s", buff);
		//发送内容至服务器
		send(clientSocket, buff, strlen(buff), NULL);
	
	}


	getchar();
	return 0;
}

服务器程序

#include <stdio.h>

#include <windows.h>//包含window封装的通信相关的结构及接口

//加载静态库,该库包含WSAStartup,WSAClean,socket等接口
#pragma comment(lib, "ws2_32.lib")



int main()
{
	//1. 请求协议版本2.2
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);//wsaData存储返回的协议版本
	if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		printf("请求协议版本失败!\n");
		return -1;
	}
	printf("请求协议版本成功!\n");

	//2. 创建socket
	//para1 —— 通信协议类型
	//para2 —— 通信所用的数据类型,TCP使用的是数据流
	//para3 —— 保护方式
	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(SOCKET_ERROR == serverSocket)//-1
	{
		printf("创建socket失败\n!");
		//清理协议
		WSACleanup();
		return -2;
	}
	printf("创建socket成功!\n");

	//3. 创建协议地址族
	SOCKADDR_IN addr = { 0 };
	addr.sin_family = AF_INET;//协议版本
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP地址,inet_addr将字符串转成整数存储
	addr.sin_port = htons(10086);//端口号,0~65535,一般用10000左右的端口

	//4. 将socket和协议地址族绑定在一起
	int r = bind(serverSocket, (sockaddr*)&addr, sizeof(addr));
	if(-1 == r)
	{
		printf("绑定失败!\n");
		//关闭socket
		closesocket(serverSocket);
		//清理协议
		WSACleanup();
		return -3;
	
	}

	//5. 监听socket(防止有多个客户端同时连接服务器,则创建队列进行排队,10表示队列长度)
	r = listen(serverSocket, 10);
	if(-1 == r)
	{
		printf("监听失败!\n");
		//关闭socket
		closesocket(serverSocket);
		//清理协议
		WSACleanup();
		return -4;
	}
	printf("监听成功!\n");

	//6. 等待客户连接(阻塞函数,尾生抱柱)
	SOCKADDR_IN clientAddr = { 0 };//存储客户端的地址族
	int len = sizeof(clientAddr);
	SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &len);
	if(SOCKET_ERROR == clientSocket)//若有客户请求,但请求失败
	{
		printf("服务器宕机!\n");
		//关闭socket
		closesocket(serverSocket);
		//清理协议
		WSACleanup();
		return -5;
	}
	printf("有客户端连接了: %s\n", inet_ntoa(clientAddr.sin_addr));//inet_ntoa将整形IP转成字符串


	//7. 通信
	char buff[1024];
	while(1)
	{
		//接收客户端发过来的消息
		r = recv(clientSocket, buff, 1023, NULL);
		if(r > 0)
		{
			buff[r] = 0;//字符串的结束符 
			//打印接收到的消息
			printf(">>%s\n", buff);
		
		}
	
	}
	getchar();
	return 0;
}

Demo第二版:多客户端-服务器(广播)

  • 服务器可以接收多个客户端的消息
  • 服务器可以将每一个客户端发过来的消息转发给所有的客户端
  • 将通信逻辑放在单独的线程中,以解决多客户端并发访问的情况
  • 线程主要循环接收客户端消息并转发给所有客户端
  • 客户端需要循环发送消息,也需要循环接收消息,所以接收消息也需要单独创建一个线程

客户端修改

//1.  将创建的客户端socket改成全局变量
SOCKET clientSocket;
//2. 将接收服务器消息的逻辑封装成函数
void ReceiveMsg()
{
	char buff[1024] = { 0 };
	int r = 0;
	while(1)
	{
		r = recv(clientSocket, buff, 1023, NULL);
		if(r > 0)
		{
			buff[r] = 0;
			printf("%s\n", buff);
		}

	}
}

//3. 创建单独的接收消息线程,必须放在接收循环的前面,不然没机会创建了
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ReceiveMsg, NULL, NULL, NULL);

服务器端修改

//1. 将收到的客户端请求的socket存在数组里面,用于保存多个客户端的连接
SOCKET clientSocket[1024];

//2. 构建变量实时记录连接进来的客户端的编号及总数,记录连接客户端的个数
int count = 0;

//3. 将通信逻辑封装成函数,负责接收指定客户端的消息,并分发给所有客户端
//idx用于标识哪个客户端
void Communication(int idx)
{
	//7. 通信
	char buff[1024];
	int r = 0;
	while(1)
	{
		//接收客户端发过来的消息
		r = recv(clientSocket[idx], buff, 1023, NULL);
		if(r > 0)
		{
			buff[r] = 0;//字符串的结束符 
			//打印接收到的消息
			printf(">>%d:%s\n", idx, buff);

			//将收到的消息分发给所有的客户端
			for(int i = 0; i < count; i++)
			{
				send(clientSocket[i], buff, strlen(buff), NULL);
			}

		}

	}

}

//4. 循环接收客户端请求,并保存起来
//每接收一个客户端请求,则单独创建一个线程用于通信
	//6. 等待客户连接(阻塞函数,尾生抱柱)
	//循环接收客户端连接请求
	SOCKADDR_IN clientAddr = { 0 };//存储客户端的地址族
	int len = sizeof(clientAddr);
	
	while(1)
	{

		clientSocket[count] = accept(serverSocket, (sockaddr*)&clientAddr, &len);
		if(SOCKET_ERROR == clientSocket[count])//若有客户请求,但请求失败
		{
			printf("服务器宕机!\n");
			//关闭socket
			closesocket(serverSocket);
			//清理协议
			WSACleanup();
			return -5;
		}
		
		count++;
		printf("有客户端%d连接了: %s\n", count-1, inet_ntoa(clientAddr.sin_addr));//inet_ntoa将整形IP转成字符串
		
		//一旦有客户端连接,则专门创建一个线程,用于服务器与该客户端的通信
		CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Communication,(LPVOID)(count-1), NULL,NULL);

		
	}


猜你喜欢

转载自blog.csdn.net/tianzhiyi1989sq/article/details/93379540