C语言实现聊天室(windows版本)



来源:微信公众号「编程学习基地」
web

C语言聊天室

基于 tcp 实现群聊功能,本项目设计是在windows环境下基于套接字(Socket)和多线程编程进行开发的简易聊天室,实现了群聊功能,在VC6.0和VS2019运行测试无误。编程

运行效果

聊天室

分析设计

Windows下基于windows网络接口Winsock的通讯步骤为WSAStartup 进行初始化--> socket 建立套接字--> bind 绑定--> listen 监听--> connect 链接--> accept 接收请求--> send/recv 发送或接收数据--> closesocket 关闭 socket--> WSACleanup 最终关闭windows

通讯流程

了解完了一个 socket 的基本步骤后咱们了解一下多线程以及线程的同步。数组

多线程

线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每一个线程共享全部的进程资源,包括打开的文件、信号标识及动态分配的内存等。一个进程内的全部线程使用同一个地址空间,而这些线程的执行由系统调度程序控制,调度程序决定哪一个线程可执行以及何时执行线程。
简而言之多线程是为了提升系统的运行效率。安全

Win32 API下的多线程编程 也就是两个函数的应用CreateThread以及WaitForSingleObject,具体案例这里很少作介绍。服务器

线程的同步

每一个线程均可以访问进程中的公共变量,资源,因此使用多线程的过程当中须要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以避免破坏数据的完整性。数据之间的相互制约包括
一、直接制约关系,即一个线程的处理结果,为另外一个线程的输入,所以线程之间直接制约着,这种关系能够称之为同步关系
二、间接制约关系,即两个线程须要访问同一资源,该资源在同一时刻只能被一个线程访问,这种关系称之为线程间对资源的互斥访问,某种意义上说互斥是一种制约关系更小的同步微信

windows线程间的同步方式有四种:临界区、互斥量、信号量、事件。网络

本项目是基于事件内核对象实现的线程同步,事件内核对象是一种抽象的对象,有受信和未授信两种状态,经过等待WaitForSingleObject实现线程同步。多线程

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,	//安全属性
  BOOL                  bManualReset,		//是否手动重置事件对象为未受信对象
  BOOL                  bInitialState,		//指定事件对象建立时的初始状态
  LPCSTR                lpName				//事件对象的名称
);

设置内核对象状态socket

BOOL SetEvent(
  HANDLE hEvent	/*设置事件内核对象受信*/
);
BOOL ResetEvent(
  HANDLE hEvent	/*设置事件内核对象未受信*/
);

堵塞等待事件内核对象直到事件内核对象的状态为受信。

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

具体使用阅读全文在个人我的网站里看,篇幅太多。

服务端设计

在建立套接字绑定监听以后会有一个等待链接的过程,在接收到新链接以后,须要建立一个线程来处理新链接,当有多个新链接时可经过建立多个线程来处理新链接,

定义最大链接数量以及最大套接字和最大线程

#define MAX_CLNT 256
int clnt_cnt = 0;			//统计套接字
int clnt_socks[MAX_CLNT];	//管理套接字
HANDLE hThread[MAX_CLNT];	//管理线程

当有新链接来临的时候建立线程处理新链接,并将新链接添加到套接字数组里面管理

hThread[clnt_cnt] = CreateThread(
	NULL,		// 默认安全属性
	NULL,		// 默认堆栈大小
	ThreadProc,	// 线程入口地址(执行线程的函数)
	(void*)&clnt_sock,		// 传给函数的参数
	0,		// 指定线程当即运行
	&dwThreadId);	// 返回线程的ID号
clnt_socks[clnt_cnt++] = clnt_sock;

线程的处理函数ThreadProc不作讲解,大体就是数据的收以及群发。

主要讲解线程同步,当有多个新链接来临的时候,可能会形成多个线程同时访问同一个数据(例如clnt_cnt)。这个时候就须要线程的同步来避免破坏数据的完整性

首先是建立一个内核事件

HANDLE g_hEvent;			/*事件内核对象*/
// 建立一个自动重置的(auto-reset events),受信的(signaled)事件内核对象
g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);

而后再须要访问公共变量(例如clnt_cnt)以前进行加锁(设置等待),访问完成以后解锁(设置受信)

/*等待内核事件对象状态受信*/
WaitForSingleObject(g_hEvent, INFINITE);
hThread[clnt_cnt] = CreateThread(NULL,NULL,ThreadProc,(void*)&clnt_sock,0,&dwThreadId);
clnt_socks[clnt_cnt++] = clnt_sock;
SetEvent(g_hEvent);				/*设置受信*/

经过套接字数组来进行数据的转发实现群聊功能,此时也用到了线程同步

void send_msg(char* msg, int len)
{
	int i;
	/*等待内核事件对象状态受信*/
	WaitForSingleObject(g_hEvent, INFINITE);
	for (i = 0; i < clnt_cnt; i++)
		send(clnt_socks[i], msg, len, 0);
	SetEvent(g_hEvent);		/*设置受信*/
}

遇到的问题

等待线程返回的过程当中最早用的是WaitForSingleObject,很遗憾这是个阻塞函数,直到线程执行完成返回以后才会继续往下执行,因此后面经过WaitForMultipleObjects这个windowsAPI调用对hThread线程数组进行线程等待释放。

整个过程不算太难,主要是仅仅实现了群聊功能,因此只须要了解windows下的网络编程以及多线程编程和线程的同步方法就能够实现这个样一个功能。

源代码后台发送关键字C语言聊天室获取,socket网络编程方法可经过上期C语言实现web服务器学习,多线程以及线程的同步可经过阅读全文在个人我的网站里面

相关文章
相关标签/搜索