win32网络编程(学习笔记):多客户端简单聊天室,实现简易FTP,UDP套接字编程、TCP的11状态

13 篇文章 0 订阅
10 篇文章 0 订阅

目录

0x00 多客户端简单聊天室

0x01 实现文件传输

0x02 UDP套接字编程

0x03 TCP 的11种状态补充


0x00 多客户端简单聊天室

思路:

主线程循环不断接受客户端连接:

  1. 创建一个全局数组来保存每次连接主套接字产生的从套接字(为什么要全局数组,为了线程之间通信)
  2. 循环调用accept函数接受客户端的连接

每个客户端连接上成功后,创建一个线程来负责和这个客户端通信

客户端发送给服务器的数据,服务器接受后需要转发给当前连接上服务器的所有客户端

客户端需要有两个线程:

  1. 一个线程循环不断接受用户输入,并发送给服务器
  2. 另一个线程循环不断接受服务器发来的数据并显示

代码:

服务器代码:

// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <windows.h>


#define MAXCLIENT 1024
//全局数组 保存主套接字管辖的众多子套接字
SOCKET connClient[MAXCLIENT];
//全局变量 保存当前连接成功的客户端数量
int clientNum = 0;

//用来和客户端通信的线程函数
//@param index 子套接字在connClient数组中的索引
void Chat(LPVOID index) {
	int idx = (int)index;
	char buff[1024];
	char temp[1024];
	int r;
	while (1) {
		
		r = recv(connClient[idx], buff, 1023, NULL);
		//NULL表示以阻塞方式接受
		//也就说当recv返回时,只有两种可能
		//r > 0 接收到的数据字节数>0,即接受到了客户端的数据
		//r < 0 客户端已经断开了连接
		if (r > 0) {
			buff[r] = 0;//添加字符串结束符号 '\0'
			printf("%d:>>%s\n", idx, buff);
			//广播给所有连接上服务器的客户端
			for (int i = 0; i < clientNum; i++) {
				memset(temp, 0, 1024);
				sprintf(temp, "%d:%s", idx, buff);//来自idx的buff
				send(connClient[i], temp, strlen(temp), NULL);
			}
		}
	}
}


int main()
{
	//1.获取版本信息
	WSADATA wsaData;
	//参数1:协议版本号 WORD 类型 
	//参数2:存储版本信息的结构体的地址
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//MAKEWORD是一个宏函数,传入两个字节类型的数据,整合成一个WORD类型的数据
	//MAKEWORD(2,2) 就是产生一个高字节为2,低字节为2的16位的数据
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		printf("得到的版本号不正确,请求版本失败\n");
		return -1;
	}	
	else {
		printf("请求版本成功\n");
	}
	//2 创建tcp socket
	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (SOCKET_ERROR == serverSocket) { //如果返回值为-1
		printf("创建tcp套接字失败:%d\n",GetLastError());
		return -1;
	}
	printf("创建socket成功\n");
	//3 创建协议地址族 (配置控制信息)
	SOCKADDR_IN serverAddr = { 0 };
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.107"); //将点分十进制转化32位unsigned int
	serverAddr.sin_port = htons(10001); //因为我们的主机是小端系统,路由器交换机是大端系统,所以我们发给大端系统的端口号应该小端转大端
	//4 绑定
	int r = bind(serverSocket,(sockaddr*) &serverAddr, sizeof serverAddr);
	if (-1 == r) {
		printf("绑定失败!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -1;
	}
	printf("绑定成功!\n");
	//5 监听
	r = listen(serverSocket, 10);
	if (-1 == r) {
		printf("监听失败!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -1;
	}
	printf("监听成功!\n");
	//6 接收客户端连接
	SOCKADDR_IN clientAddr = { 0 }; //用于接收客户端的协议地址族(就是控制信息)
	int len = sizeof clientAddr; //用于接收客户端协议地址族信息的大小
	for (int i = 0; i < MAXCLIENT; i++) {
		connClient[i] = accept(serverSocket, (sockaddr*)&clientAddr, &len);
		//SOCKET connClient = accept(serverSocket, NULL, NULL); //如果不想存储客户端的协议地址族信息,可以传NULL地址
		if (SOCKET_ERROR == connClient[i]) {
			printf("接收客户端连接失败!\n");
			closesocket(serverSocket);
			WSACleanup();
			return -1;
		}
		//inet_ntoa() 将32位unisigned int 转化为点分十进制的string
		printf("有客户端连接到服务器了:%s:%d!\n", inet_ntoa(clientAddr.sin_addr), clientAddr.sin_port);
		clientNum++;
		CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Chat, (LPVOID)i, NULL, NULL);
	}
	//8 关闭socket
	closesocket(serverSocket);
	//9 清除协议版本
	WSACleanup();
	while (1);
	return 0;
}


 

客户端代码:

// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <windows.h>
#include <graphics.h>

//声明一个全局的socket 为了线程之间共享
SOCKET clientSocket;
//从服务器接受数据并显示到客户端
void recvAndShow() {
	char buff[1024] = { 0 };
	int r;
	int idx;
	char message[1024] = { 0 };
	char currentTime[1024];
	SYSTEMTIME sysTime;
	int n=0; //计数 第几条信息
	while (1) {
		r = recv(clientSocket, buff, 1023, NULL);
		//接受到服务器端传来的数据格式:  idx:数据
		if (r > 0) {
			buff[r] = 0;
			//从buff中拆分出idx和数据
			memset(message, 0, sizeof message);
			sscanf(buff, "%d:%s", &idx, message); //sscanf 从数组中拆分格式化的数据
			//显示idx 显示当前时间
			GetLocalTime(&sysTime); //获取当前系统时间
			sprintf(currentTime, "%d %04d/%02d%02d %02d:%02d:%02d",
				idx, sysTime.wYear, sysTime.wMonth, sysTime.wDay,
				sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
			outtextxy(0, 40 * n, currentTime);
			//显示数据
			outtextxy(10, 40 * n + 20, message);
			n++;
		}
	}
}

int main()
{
	initgraph(200, 400, SHOWCONSOLE);
	//1.获取版本信息
	WSADATA wsaData;
	//参数1:协议版本号 WORD 类型 
	//参数2:存储版本信息的结构体的地址
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//MAKEWORD是一个宏函数,传入两个字节类型的数据,整合成一个WORD类型的数据
	//MAKEWORD(2,2) 就是产生一个高字节为2,低字节为2的16位的数据
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		printf("得到的版本号不正确,请求版本失败\n");
		return -1;
	}
	else {
		printf("请求版本成功\n");
	}
	//2 创建tcp socket
	clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (SOCKET_ERROR == clientSocket) { //如果返回值为-1
		printf("创建tcp套接字失败:%d\n", GetLastError());
		return -1;
	}
	printf("创建socket成功\n");
	//3 配置服务器协议地址族 (配置控制信息)
	SOCKADDR_IN serverAddr = { 0 };
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.107"); //将点分十进制转化32位unsigned int
	serverAddr.sin_port = htons(10001); //因为我们的主机是小端系统,路由器交换机是大端系统,所以我们发给大端系统的端口号应该小端转大端
	//4 连接服务器
	int r;
	r = connect(clientSocket, (sockaddr*)&serverAddr, sizeof serverAddr);
	if (SOCKET_ERROR == r) {
		printf("连接服务器失败\n");
		return -1;
	}
	printf("连接服务器成功!\n");
	
	//4.创建一个线程专门负责从服务器接受数据并显示
	HANDLE h1 =  CreateThread(NULL, NULL,(LPTHREAD_START_ROUTINE)recvAndShow, NULL, NULL,NULL);
	
	//5 向服务器发送数据
	char buff[1024];
	while (1) {
		memset(buff, 0, 1024);
		printf("请输入要发送的数据:");
		scanf("%s", buff);
		r = send(clientSocket, buff, strlen(buff),NULL);
		if (r > 0) {
			printf("发送%d字节数据成功!\n", r);
		}
		else {
			printf("发送失败!\n");
		}
	}

	//结束recvAndShow线程
	TerminateThread(h1, 1);
	//6 关闭socket
	closesocket(clientSocket);
	//7 清除协议版本
	WSACleanup();
	while (1);
	return 0;

}


0x01 实现文件传输

思路:

接受端:

1.接受文件名

2.接受文件大小

3.创建文件

4.循环接受文件内容

5.关闭保存

// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <windows.h>

int main()
{
	//1.获取版本信息
	WSADATA wsaData;
	//参数1:协议版本号 WORD 类型 
	//参数2:存储版本信息的结构体的地址
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//MAKEWORD是一个宏函数,传入两个字节类型的数据,整合成一个WORD类型的数据
	//MAKEWORD(2,2) 就是产生一个高字节为2,低字节为2的16位的数据
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		printf("得到的版本号不正确,请求版本失败\n");
		return -1;
	}
	else {
		printf("请求版本成功\n");
	}
	//2 创建tcp socket
	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (SOCKET_ERROR == serverSocket) { //如果返回值为-1
		printf("创建tcp套接字失败:%d\n", GetLastError());
		return -1;
	}
	printf("创建socket成功\n");
	//3 创建协议地址族 (配置控制信息)
	SOCKADDR_IN serverAddr = { 0 };
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.107"); //将点分十进制转化32位unsigned int
	serverAddr.sin_port = htons(10001); //因为我们的主机是小端系统,路由器交换机是大端系统,所以我们发给大端系统的端口号应该小端转大端
	//4 绑定
	int r = bind(serverSocket, (sockaddr*)&serverAddr, sizeof serverAddr);
	if (-1 == r) {
		printf("绑定失败!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -1;
	}
	printf("绑定成功!\n");
	//5 监听
	r = listen(serverSocket, 10);
	if (-1 == r) {
		printf("监听失败!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -1;
	}
	printf("监听成功!\n");
	//6 接收客户端连接
	SOCKADDR_IN clientAddr = { 0 }; //用于接收客户端的协议地址族(就是控制信息)
	int len = sizeof clientAddr; //用于接收客户端协议地址族信息的大小
	SOCKET connClient = accept(serverSocket, (sockaddr*)&clientAddr, &len);
	//SOCKET connClient = accept(serverSocket, NULL, NULL); //如果不想存储客户端的协议地址族信息,可以传NULL地址
	if (SOCKET_ERROR == connClient) {
		printf("接收客户端连接失败!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -1;
	}
	//inet_ntoa() 将32位unisigned int 转化为点分十进制的string
	printf("有客户端连接到服务器了:%s:%d!\n", inet_ntoa(clientAddr.sin_addr), clientAddr.sin_port);
	//7 通信
	//7.1 接受文件名
	char fileName[256] = { 0 };
	r = recv(connClient, fileName, 255, NULL);
	if (r > 0) {
		fileName[r] = 0;
		printf("接受文件名成功:%s\n",fileName);
	}
	//7.2 创建文件
	FILE* fp = fopen(fileName, "wb");
	if (NULL == fp) {
		printf("创建文件失败!\n");
	}
	printf("创建文件成功!\n");
	//7.3 接收文件大小
	int fileSize;
	r = recv(connClient, (char*)&fileSize, 4, NULL);
	if (r > 0) {
		printf("接收文件大小成功:%d\n", fileSize);
	}
	//这时文件一定创建好了
	//7.4 循环接受文件内容
	char buff[1024];
	int count = 0; //记录我当前接收了多少
	while (1) {
		r = recv(connClient, buff, 1024, NULL);
		if (r > 0) {
			count += r;
			fwrite(buff,1, r, fp);
			if (count >= fileSize) break;
		}
	}
	fclose(fp);
	printf("文件接受完毕!\n");
	//8 关闭socket
	closesocket(serverSocket);
	//9 清除协议版本
	WSACleanup();
	while (1);
	return 0;
}

发送端:

1.发送文件名

2.打开文件,获取文件大小并发送

3.循环发送文件内容

4.关闭文件

// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <windows.h>


int main()
{
	//1.获取版本信息
	WSADATA wsaData;
	//参数1:协议版本号 WORD 类型 
	//参数2:存储版本信息的结构体的地址
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//MAKEWORD是一个宏函数,传入两个字节类型的数据,整合成一个WORD类型的数据
	//MAKEWORD(2,2) 就是产生一个高字节为2,低字节为2的16位的数据
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		printf("得到的版本号不正确,请求版本失败\n");
		return -1;
	}
	else {
		printf("请求版本成功\n");
	}
	//2 创建tcp socket
	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (SOCKET_ERROR == clientSocket) { //如果返回值为-1
		printf("创建tcp套接字失败:%d\n", GetLastError());
		return -1;
	}
	printf("创建socket成功\n");
	//3 配置服务器协议地址族 (配置控制信息)
	SOCKADDR_IN serverAddr = { 0 };
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.107"); //将点分十进制转化32位unsigned int
	serverAddr.sin_port = htons(10001); //因为我们的主机是小端系统,路由器交换机是大端系统,所以我们发给大端系统的端口号应该小端转大端
	//4 连接服务器
	int r;
	r = connect(clientSocket, (sockaddr*)&serverAddr, sizeof serverAddr);
	if (SOCKET_ERROR == r) {
		printf("连接服务器失败\n");
		return -1;
	}
	printf("连接服务器成功!\n");

	//5 通信
	char fileName[256] = { 0 };
	printf("请输入要发送的文件名:");
	scanf("%s", fileName);
	send(clientSocket, fileName, strlen(fileName), NULL);
	FILE* fp = fopen(fileName, "rb");
	if (NULL == fp) {
		printf("打开文件失败!\n");
		return -1;
	}
	fseek(fp, 0, SEEK_END);//定位文件指针到文件末尾
	int fileSize = ftell(fp); //返回文件指针当前位置,以字节为单位
	fseek(fp, 0, SEEK_SET);//定位文件指针到文件开头

	r = send(clientSocket, (char*)&fileSize,4, NULL);
	if (r > 0) {
		printf("发送文件大小成功:%d\n", fileSize);
	}
	//循环发送数据
	char buff[1024];
	while (1) {
		r = fread(buff, 1, 1024, fp);
		if (r > 0) {
			send(clientSocket,buff, r, NULL);
		}
		else {
			break;
		}
	}
	fclose(fp);
	//6 关闭socket
	closesocket(clientSocket);
	//7 清除协议版本
	WSACleanup();
	while (1);
	return 0;

}

0x02 UDP套接字编程

UDP套接字编程思路:

服务器:

1.请求协议版本

2.创建socket

3.确定协议地址族

4.绑定

5.通信



#include "pch.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <windows.h>

int main()
{
	//1.获取版本信息
	WSADATA wsaData;
	//参数1:协议版本号 WORD 类型 
	//参数2:存储版本信息的结构体的地址
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//MAKEWORD是一个宏函数,传入两个字节类型的数据,整合成一个WORD类型的数据
	//MAKEWORD(2,2) 就是产生一个高字节为2,低字节为2的16位的数据
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		printf("得到的版本号不正确,请求版本失败\n");
		return -1;
	}
	else {
		printf("请求版本成功\n");
	}
	//2 创建udp socket
	SOCKET serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (SOCKET_ERROR == serverSocket) { //如果返回值为-1
		printf("创建udp套接字失败:%d\n", GetLastError());
		return -1;
	}
	printf("创建socket成功\n");
	//3 创建协议地址族 (配置控制信息)
	SOCKADDR_IN serverAddr = { 0 };
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.107"); //将点分十进制转化32位unsigned int
	serverAddr.sin_port = htons(10001); //因为我们的主机是小端系统,路由器交换机是大端系统,所以我们发给大端系统的端口号应该小端转大端
	//4 绑定
	int r = bind(serverSocket, (sockaddr*)&serverAddr, sizeof serverAddr);
	if (-1 == r) {
		printf("绑定失败!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -1;
	}
	printf("绑定成功!\n");
	//5 通信
	char  buff[1024];
	while (1) {
		r = recv(serverSocket, buff, 1023, NULL);
		if (r > 0) {
			buff[r] = 0;
			printf(">>%s\n", buff);
		}
	}
	//8 关闭socket
	closesocket(serverSocket);
	//9 清除协议版本
	WSACleanup();
	while (1);
	return 0;
}

客户端:

1.请求协议版本

2.创建socket

3.获取服务器协议地址族

4.通信



#include "pch.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <windows.h>

int main()
{
	//1.获取版本信息
	WSADATA wsaData;
	//参数1:协议版本号 WORD 类型 
	//参数2:存储版本信息的结构体的地址
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//MAKEWORD是一个宏函数,传入两个字节类型的数据,整合成一个WORD类型的数据
	//MAKEWORD(2,2) 就是产生一个高字节为2,低字节为2的16位的数据
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		printf("得到的版本号不正确,请求版本失败\n");
		return -1;
	}
	else {
		printf("请求版本成功\n");
	}
	//2 创建udp socket
	SOCKET clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (SOCKET_ERROR == clientSocket) { //如果返回值为-1
		printf("创建udp套接字失败:%d\n", GetLastError());
		return -1;
	}
	printf("创建socket成功\n");
	//3 配置服务器的协议地址族 (配置控制信息)
	SOCKADDR_IN serverAddr = { 0 };
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.107"); //将点分十进制转化32位unsigned int
	serverAddr.sin_port = htons(10001); //因为我们的主机是小端系统,路由器交换机是大端系统,所以我们发给大端系统的端口号应该小端转大端


	//5 通信
	int r;
	char  buff[1024];
	while (1) {
		memset(buff, 0, 1024);
		printf("请输入要发送的信息:");
		scanf("%s", buff);
		//用clientSocket 套接字将buff中的内容发送给serverAddr中配置的udp端口
		r = sendto(clientSocket, buff, strlen(buff), NULL, (sockaddr*)&serverAddr, sizeof serverAddr);

	}
	//8 关闭socket
	closesocket(clientSocket);
	//9 清除协议版本
	WSACleanup();
	while (1);
	return 0;
}

0x03 TCP 的11种状态补充

三次握手:

四次挥手:

比如说:客户端要断开连接,先给服务器发送fin

服务器收到fin后回应一个ack,然后开始结束自己未结束的工作

残局收拾完后,服务器给客户端发送一个fin,客户端响应一个ack

断开连接

tcp端口的11种状态:

CLOSED:连接建立前和连接断开后的状态

LISTENING:监听状态,服务器端口已经准备好了,在等待客户端连接

SYN_RCVD: 服务器收到了SYN,但是还没有发送SYN ACK 的状态

该状态很短暂

SYN_SEND : 连接请求SYN发送后,收到ACK之前的状态

ESTABLISHED:数据传输通道建立完毕

CLOSE_WAIT: 客户端发送FIN后,接收到ACK前的状态

LAST_ACK: 客户端接收到FIN,但是还未发送ACK (这段时间客户端在回收自己的资源)

FIN_WAIT_1: 服务器向对方发送FIN后,接收ACK前

FIN_WAIT_2: 收到对方接到FIN后的ACK,等待对方发送FIN

CLOSING:双方同时发送FIN,同时收到ACK

TIME_WAIT:网络延迟等待状态

 

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值