生产者与消费者模型
生产者:生产数据的线程
消费者:使用数据的线程
仓库:临时存放数据的缓冲区(仓库解决了生产、消费不匹配)
可能产生的问题:
消费快于生产:仓库空虚、饿死
生产快于消费:仓库爆满、撑死
使用条件变量解决以上问题:
当缓冲区空的时候:消费者进程睡入条件变量(empty),通知生产者线程全部醒来(full)
当缓冲区满的时候:生产者进程睡入条件变量(full),通知消费者线程全部醒来(empty)
网络聊天室
代码中所用到的 头文件 和 使用的函数 请参照 上一篇 网络socket 以及线程池的封装
客户端业务逻辑:
1、客户端注册名字
2、发送消息|接收消息
服务端业务逻辑:
1、开启线程池、等待客户端连接
2、接收客户端名字,告诉所有的在线的客户端,XXX进入聊天室。
3、接收客户端消息,转发给其他客户端。
4、通知其他客户端 xxx退出聊天室
5、同时在线人数最多50人。
注意:任何客户端都应该可以随时进入退出。
服务器代码
#include <stdio.h>
#include <stdlib.h>
#include <network.h>
#include <stdbool.h>
#include <string.h>
#include "threadpool.h"
#define MAX_CLIENT (50)
// buf的字节数
#define BUF_SIZE (4096)
// 在线客户端nw的数组
NetWork* cli_arr[MAX_CLIENT];
// 添加nw
int add_clinw(NetWork* nw)
{
for(int i=0; i<MAX_CLIENT; i++)
{
if(NULL == cli_arr[i])
{
cli_arr[i] = nw;
return i;
}
}
}
// 转发消息给其他客户端
void send_cli_arr(const char* msg,int index)
{
for(int i=0; i<MAX_CLIENT; i++)
{
if(NULL != cli_arr[i] && i != index)
{
send_nw(cli_arr[i],msg,strlen(msg)+1);
}
}
}
// 消费者业务逻辑函数
void chat_work(void* arg)
{
// 添加arg到客户端nw数组中
int index = add_clinw(arg);
char* buf = malloc(BUF_SIZE);
// 接收昵称并转发
int ret = recv_nw(cli_arr[index],buf,BUF_SIZE);
if(0 >= ret || 0 == strncmp(buf,"quit",4))
{
free(buf);
close_nw(cli_arr[index]);
cli_arr[index] = NULL;
return;
}
strcat(buf," 进入聊天室,大家小心!");
send_cli_arr(buf,index);
buf[ret-1] = ':';
char* msg = buf+ret;
for(;;)
{
int ret =recv_nw(cli_arr[index],msg,BUF_SIZE-ret);
if(0 >= ret || 0 == strncmp(msg,"quit",4))
{
sprintf(msg,"退出聊天室!");
send_cli_arr(buf,index);
free(buf);
close_nw(cli_arr[index]);
cli_arr[index] = NULL;
return;
}
send_cli_arr(buf,index);
}
}
int main(int argc,const char* argv[])
{
if(3 != argc)
{
printf("User: ./server <ip> <port>\n");
return EXIT_SUCCESS;
}
// 启动网络服务
NetWork* svr_nw =
init_nw(SOCK_STREAM,atoi(argv[2]),argv[1],true);
if(NULL == svr_nw)
{
puts("网络错误,请检查!\n");
return EXIT_FAILURE;
}
// 创建并启动线程池
ThreadPool* threadpool = create_threadpool(MAX_CLIENT,20,chat_work);
start_threadpool(threadpool);
for(;;)
{
// 生产者 等待客户端连接
NetWork* cli_nw = accept_nw(svr_nw);
if(NULL == cli_nw)
{
puts("网络异常!\n");
continue;
}
// 添加数据到仓库
push_threadpool(threadpool,cli_nw);
}
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <network.h>
#include <string.h>
#include <pthread.h>
void* run(void* arg)
{
char recv_buf[4096] = {};
size_t buf_size = sizeof(recv_buf);
NetWork* nw = arg;
for(;;)
{
int ret = recv_nw(nw,recv_buf,buf_size);
if(0 >= ret)
{
puts("服务器正在升级!");
exit(EXIT_FAILURE);
}
printf("\r%s\n>>>",recv_buf);
fflush(stdout);
}
}
int main(int argc,const char* argv[])
{
if(3 != argc)
{
puts("User: ./client <ip> <port>");
return EXIT_SUCCESS;
}
NetWork* nw = init_nw(SOCK_STREAM,atoi(argv[2]),argv[1],false);
if(NULL == nw)
{
puts("网络有误,请检查!");
return EXIT_FAILURE;
}
char send_buf[4096] = {};
size_t buf_size = sizeof(send_buf);
printf("请输入你的昵称:");
scanf("%[^/n]%*c",send_buf);
send_nw(nw,send_buf,strlen(send_buf)+1);
// 开启接收群聊消息线程
pthread_t tid;
pthread_create(&tid,NULL,run,nw);
for(;;)
{
printf(">>>");
scanf("%s",send_buf);
int ret = send_nw(nw,send_buf,strlen(send_buf)+1);
if(0 == strncmp(send_buf,"quit",4))
{
puts("已退出当前聊天室!");
return EXIT_SUCCESS;
}
if(0 >= ret)
{
puts("服务器正在升级,请稍候");
return EXIT_FAILURE;
}
}
}