windows网络编程(九)——IOCP+多线程实现简单的聊天(windows服务器端 windows客户端)

本文介绍了一个基于IOCP(I/O完成端口)机制的聊天室应用程序,包括服务器端和客户端的实现细节。该程序利用Windows API进行多线程并发处理,支持多个客户端同时连接并进行消息交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IOCP思维导图:



1.服务器端

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>

#pragma comment(lib,"ws2_32.lib");//加载ws2_32.dll

#define BUF_SIZE 100
#define READ	3
#define	WRITE	5

typedef struct    // socket info
{
	SOCKET hClntSock;
	SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

typedef struct    // buffer info
{
	OVERLAPPED overlapped;
	WSABUF wsaBuf;
	char buffer[BUF_SIZE];
	int rwMode;    // READ or WRITE 读写模式
} PER_IO_DATA, *LPPER_IO_DATA;

unsigned int  WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void ErrorHandling(char *message);
SOCKET ALLCLIENT[100];
int clientcount = 0;
HANDLE hMutex;//互斥量

int main(int argc, char* argv[])
{

	hMutex = CreateMutex(NULL, FALSE, NULL);//创建互斥量

	WSADATA	wsaData;
	HANDLE hComPort;
	SYSTEM_INFO sysInfo;
	LPPER_IO_DATA ioInfo;
	LPPER_HANDLE_DATA handleInfo;

	SOCKET hServSock;
	SOCKADDR_IN servAdr;
	int  i;
	DWORD recvBytes = 0,flags = 0;

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);//创建CP对象
	GetSystemInfo(&sysInfo);//获取当前系统的信息

	for (i = 0; i < sysInfo.dwNumberOfProcessors; i++)
		_beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);//创建=CPU个数的线程数

	hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);//不是非阻塞套接字,但是重叠IO套接字。
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(1234);

	bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
	listen(hServSock, 5);

	while (1)
	{
		SOCKET hClntSock;
		SOCKADDR_IN clntAdr;
		int addrLen = sizeof(clntAdr);

		hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);

		handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));//和重叠IO一样
		handleInfo->hClntSock = hClntSock;//存储客户端套接字

		WaitForSingleObject(hMutex, INFINITE);//线程同步

		ALLCLIENT[clientcount++] = hClntSock;//存入套接字队列

		ReleaseMutex(hMutex);

		memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);

		CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);//连接套接字和CP对象
																				//已完成信息将写入CP对象
		ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));//存储接收到的信息
		memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
		ioInfo->wsaBuf.len = BUF_SIZE;
		ioInfo->wsaBuf.buf = ioInfo->buffer;//和重叠IO一样
		ioInfo->rwMode = READ;//读写模式

		WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf),//非阻塞模式
			1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);
	}
	CloseHandle(hMutex);//销毁互斥量
	return 0;
}

unsigned int WINAPI EchoThreadMain(LPVOID pComPort)//线程的执行
{
	HANDLE hComPort = (HANDLE)pComPort;
	SOCKET sock;
	DWORD bytesTrans;
	LPPER_HANDLE_DATA handleInfo;
	LPPER_IO_DATA ioInfo;
	DWORD flags = 0;

	while (1)//大循环
	{
		GetQueuedCompletionStatus(hComPort, &bytesTrans,//确认“已完成”的I/O!!
			(LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);//INFINITE使用时,程序将阻塞,直到已完成的I/O信息写入CP对象
		sock = handleInfo->hClntSock;//客户端套接字

		if (ioInfo->rwMode == READ)//读写模式(此时缓冲区有数据)
		{
			puts("message received!");
			if (bytesTrans == 0)    // 连接结束
			{
				WaitForSingleObject(hMutex, INFINITE);//线程同步

				closesocket(sock);
				int i = 0;
				while (ALLCLIENT[i] == sock){ i++; }
				ALLCLIENT[i] = 0;//断开置0

				ReleaseMutex(hMutex);

				free(handleInfo); free(ioInfo);
				continue;
			}
			int i = 0;

			for (; i < clientcount;i++)
			{
				if (ALLCLIENT[i] != 0)//判断是否为已连接的套接字
				{
					if (ALLCLIENT[i] != sock)
					{
						LPPER_IO_DATA newioInfo;
						newioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));//动态分配内存
						memset(&(newioInfo->overlapped), 0, sizeof(OVERLAPPED));
						strcpy(newioInfo->buffer, ioInfo->buffer);//重新构建新的内存,防止多次释放free
						newioInfo->wsaBuf.buf = newioInfo->buffer;
						newioInfo->wsaBuf.len = bytesTrans;
						newioInfo->rwMode = WRITE;

						WSASend(ALLCLIENT[i], &(newioInfo->wsaBuf),//回声
							1, NULL, 0, &(newioInfo->overlapped), NULL);
					}
					else
					{
						memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
						ioInfo->wsaBuf.len = bytesTrans;
						ioInfo->rwMode = WRITE;
						WSASend(ALLCLIENT[i], &(ioInfo->wsaBuf),//回声
							1, NULL, 0, &(ioInfo->overlapped), NULL);
					}
				}
			}
			ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));//动态分配内存
			memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
			ioInfo->wsaBuf.len = BUF_SIZE;
			ioInfo->wsaBuf.buf = ioInfo->buffer;
			ioInfo->rwMode = READ;
			WSARecv(sock, &(ioInfo->wsaBuf),//再非阻塞式接收
				1, NULL, &flags, &(ioInfo->overlapped), NULL);
		}
		else
		{
			puts("message sent!");
			free(ioInfo);
		}
	}
	return 0;
}

void ErrorHandling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}


2.客户端

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h> 
#define BUF_SIZE 1000
#define NAME_SIZE 20

#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll  

unsigned WINAPI SendMsg(void * arg);//发送信息函数
unsigned WINAPI RecvMsg(void * arg);//接受信息函数
void ErrorHandling(char * msg);//错误返回函数

int haveread = 0;
char NAME[50];//[名字]
char ANAME[50];
char msg[BUF_SIZE];//信息

int main(int argc, char *argv[])
{

	printf("请输入网名:");
	scanf("%s", NAME);
	WSADATA wsaData;
	SOCKET hSock;
	SOCKADDR_IN servAdr;
	HANDLE hSndThread, hRcvThread;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hSock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = inet_addr("127.0.0.1");
	servAdr.sin_port = htons(1234);

	if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
		ErrorHandling("connect() error");

	int resultsend;
	puts("Welcome to joining our chatting room!\n");
	sprintf(ANAME, "[%s]", NAME);

	hSndThread =
		(HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&hSock, 0, NULL);//写线程
	hRcvThread =
		(HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&hSock, 0, NULL);//读线程

	WaitForSingleObject(hSndThread, INFINITE);//等待线程结束
	WaitForSingleObject(hRcvThread, INFINITE);
	closesocket(hSock);
	WSACleanup();
	system("pause");
	return 0;
}

unsigned WINAPI SendMsg(void * arg)   // send thread main
{
	SOCKET sock = *((SOCKET*)arg);
	char name_msg[NAME_SIZE + BUF_SIZE];
	char padd[2];
	fgets(padd, 2, stdin);//多余的'\n'
	printf("\n send message:");
	while (1)
	{
		{
			fgets(msg, BUF_SIZE, stdin);
			if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n"))
			{
				closesocket(sock);
				exit(0);
			}
			sprintf(name_msg, "[%s] %s", NAME, msg);
			char numofmsg = strlen(name_msg) + '0';
			char newmsg[100]; newmsg[0] = numofmsg; newmsg[1] = 0;//第一个字符表示消息的长度
			strcat(newmsg, name_msg);
			int result = send(sock, newmsg, strlen(newmsg), 0);
			if (result == -1)return -1;//发送错误
		}
	}
	return NULL;
}

unsigned WINAPI RecvMsg(void * arg)  // read thread main
{
	SOCKET sock = *((SOCKET*)arg);
	char name_msg[NAME_SIZE + BUF_SIZE];
	int str_len = 0;
	while (1)
	{
		{
			char lyfstr[1000] = { 0 };
			int totalnum = 0;
			str_len = recv(sock, name_msg, 1, 0);//读取第一个字符!获取消息的长度
			if (str_len == -1)//读取错误
			{
				printf("return -1\n");
				return -1;
			}
			if (str_len == 0)//读取结束
			{
				printf("return 0\n");
				return 0;//读取结束
			}
			totalnum = name_msg[0] - '0';
			int count = 0;

			do
			{
				str_len = recv(sock, name_msg, 1, 0);

				name_msg[str_len] = 0;

				if (str_len == -1)//读取错误
				{
					printf("return -1\n");
					return -1;
				}
				if (str_len == 0)
				{
					printf("return 0\n");
					return 0;//读取结束
				}
				strcat(lyfstr, name_msg);
				count = str_len + count;

			} while (count < totalnum);

			lyfstr[count] = '\0';
			printf("\n");
			strcat(lyfstr, "\n");
			fputs(lyfstr, stdout);
			printf(" send message:");
			fflush(stdout);
			memset(name_msg, 0, sizeof(char));
		}
	}
	return NULL;
}

void ErrorHandling(char * msg)
{
	fputs(msg, stderr);
	fputc('\n', stderr);
	exit(1);
}


3.结果


4.不足

未考虑断开连接后的内存处理相关的问题,只考虑了正常连接时的聊天功能。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值