通信流程
了解完了⼀个 socket 的基本步骤后我们了解⼀下多线程以及线程的同步。
线程的同步
每个线程都可以访问进程中的公共变量,资源,所以「使⽤多线程的过程中需要注意的问题是如何防⽌两个或两个以上的线程同时访问同⼀个数据,以免破坏数据的完整性」。数据之间的相互制约包括
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 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);  /*设置受信*/
}
客户端设计
同样也是在创建套接字连接到服务器之后,创建两个线程,⼀个线程实现数据的发送,⼀个实现数据的接收。
「发送数据到服务端」
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;
}
这样就不会阻塞等待终端输⼊之后再显⽰服务端发送过来的消息了。
遇到的问题
等待线程返回的过程中最先⽤的是WaitForSingleObject,很遗憾这是个阻塞函数,直到线程返回才会继续往下执⾏,所以后⾯通过WaitForMultipleObjects这个windowsAPI调⽤对hThread线程数组进⾏线程等待释放。
「缺陷:⾮⾼并发,对资源的利⽤不⾼,下周介绍Linux⽹络编程实现的聊天室,可能有新功能,敬请期待..」
整个过程不算太难,主要是仅实现了聊功能,所以只需要了解windows下的⽹络编程以及多线程编程和线程的同步⽅法就可以实现这个样⼀个功能。
「源代码后台发送关键字windows聊天室获取」
socket⽹络编程⽅法可参考上期
C语⾔实现web服务器
多线程以及线程的同步可通过「阅读全⽂」在我的个⼈⽹站⾥⾯查阅。
socket编程聊天室基本流程关键字【聊天室】

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。