汉扬编程 编程大纲 在线聊天功能是如何实现的?教你用C语言打造简易聊天室

在线聊天功能是如何实现的?教你用C语言打造简易聊天室

在线聊天功能是如何实现的?教你用C语言打造简易聊天室

原创: deroy

在线聊天功能是如何实现的?教你用C语言打造简易聊天室

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

在线聊天功能是如何实现的?教你用C语言打造简易聊天室

运行效果

在线聊天功能是如何实现的?教你用C语言打造简易聊天室

聊天室

分析设计Windows下基于windows网络接口Winsock的通信步骤为「WSAStartup 进行初始化」–> 「socket 创建套接字」–> 「bind 绑定」–> 「listen 监听」–> 「connect 连接」–> 「accept 接收请求」–> 「send/recv 发送或接收数据」–> 「closesocket 关闭 socket」–> 「WSACleanup 最终关闭」。

通信流程

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

多线程线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享所有的进程资源。一个进程内的所有线程使用同一个地址空间,而这些线程的执行由系统调度程序控制,调度程序决定哪个线程可执行以及什么时候执行线程。「简而言之多线程是为了提高系统的运行效率。」

Win32 API下的多线程编程 也就是两个函数的应用创建线程CreateThread以及等待线程结束waitForSingleObject,具体案例这里不多做介绍。

线程的同步每个线程都可以访问进程中的公共变量,资源,所以「使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性」。数据之间的相互制约包括

1、「直接制约关系」,即一个线程的处理结果,为另一个线程的输入,因此线程之间直接制约着,这种关系可以称之为同步关系

2、「间接制约关系」,即两个线程需要访问同一资源,该资源在同一时刻只能被一个线程访问,这种关系称之为线程间对资源的互斥访问,某种意义上说互斥是一种制约关系更小的同步

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

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

「创建事件内核对象」

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

BOOL SetEvent(  HANDLE hEvent /*设置事件内核对象受信*/);BOOL ResetEvent(  HANDLE hEvent /*设置事件内核对象未受信*/);「堵塞等待事件内核对象直到事件内核对象的状态为受信」

DWORD WaitForSingleObject(  HANDLE hHandle,  DWORD  dwMilliseconds);

服务端设计在创建套接字绑定监听之后会有一个等待连接的过程,在接收到新连接之后,需要创建一个线程来处理新连接,当有多个新连接时可通过创建多个线程来处理新连接,

「定义最大连接数量以及最大套接字和最大线程」

#define MAX_CLNT 256int 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);  /*设置受信*/}客户端设计同样也是在创建套接字连接到服务器之后,创建两个线程,一个线程实现数据的发送,一个实现数据的接收。

「发送数据到服务端」

DWORD WINAPI send_msg(LPVOID lpParam){ int sock = *((int*)lpParam); char name_msg[NAME_SIZE + BUF_SIZE]; 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);  int nRecv = send(sock, name_msg, strlen(name_msg), 0); } return NULL;}「接收服务端数据并打印输出到显示器」

DWORD WINAPI recv_msg(LPVOID lpParam){ int sock = *((int*)lpParam); char name_msg[NAME_SIZE + BUF_SIZE]; int str_len; while (1) {  str_len = recv(sock, name_msg, NAME_SIZE + BUF_SIZE – 1, 0);  if (str_len == -1)   return -1;  name_msg[str_len] = 0;  fputs(name_msg, stdout); } return NULL;}这样就不会阻塞等待终端输入之后再显示服务端发送过来的消息了。

相关教程及完整视频 可评论获取

与编程牛人大牛在线交流,点击下方【了解更多】

本文来自网络,不代表汉扬编程立场,转载请注明出处:http://www.hyzlch.com/mianfei/6733.html

C语言干货(3):必备的知识——“补码”

C语言是怎么诞生的:在路边捡到一条鱼

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

返回顶部