父进程
负责监听,处理客户端的连接请求,也就是在父进程中循环调用 accept() 函数;
创建子进程:建立一个新的连接,就创建一个新的子进程,让这个子进程和对应的客户端通信;
回收子进程资源:子进程退出回收其内核 PCB 资源,防止出现僵尸进程。
子进程
负责通信,基于父进程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送;
与客户端建立链接;
收发数据:send() / write(),recv() / read()。
1、创建线程:
注意,第三个参数是void *类型,这就意味着当你需要传多个参数的时候可以把他们封装在一个结构体中。
头文件
#include
#include
创建线程
int pthread_create(
pthread_t *restrict tidp, //新创建的线程ID指向的内存单元。
const pthread_attr_t *restrict attr, //线程属性,默认为NULL
void *(*start_rtn)(void *), //线程函数
void *restrict arg //线程函数的参数
);
2、线程分离:
作用:从状态上实现线程分离,注意不是指该线程独自占用地址空间。
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程),其退出状态不由其他线程获取,而直接自己自动释放(自己清理掉PCB的残留资源)。
int pthread_detach(pthread_t thread); 成功:0;失败:错误号
3、线程锁
保护数据不会被同时修改(其实互斥锁也可以实现)。
typedef pthread_mutex_t mutex;
pthread_mutex_lock(&mutex);
pthread_mutex_destroy(&mutex);
任务:主要负责重复select,做好rdset集合并将数据传递给子进程(通过自定义结构体)。
int main()
{
//完成线程锁的初始化
pthread_mutex_init(&mutex,NULL);
// 创建监听的lfd
int lfd = socket(AF_INET, SOCK_STREAM, 0);
// 定义服务器性质,并储存在sockaddr_in addr中;绑定。
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = INADDR_ANY;
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
// 设置监听
listen(lfd, 128);
// 将监听的fd的状态检测委托给内核检测
// 初始化maxfd、rdset集合等。
int maxfd = lfd;
fd_set rdset;
fd_set rdtemp;
// 清零
FD_ZERO(&rdset);
// 将监听的lfd设置到检测的读集合中
FD_SET(lfd, &rdset);
完成线程锁的初始化,定义服务器的监听lfd以及服务器的性质;
完成服务器绑定;
定义select的成员:maxfd、rdset,并完成初始化(清零并加入);
while(1)
{
pthread_mutex_lock(&mutex);
// rdset 中是委托内核检测的所有的文件描述符
rdtemp = rdset;
pthread_mutex_unlock(&mutex);
int num = select(maxfd+1, &rdtemp, NULL, NULL, NULL);
// rdset中的数据被内核改写了, 只保留了发生变化的文件描述的标志位上的1, 没变化的改为0
// 只要rdset中的fd对应的标志位为1 -> 缓冲区有数据了
// 判断
if(FD_ISSET(lfd, &rdtemp))
{
fdinfo *info = (FDInfo *)malloc(sizeof(FDInfo));
info->fd = lfd;
info->maxfd = &maxfd;
info->rdset = &rdset;
pthread_t tid;
pthread_create(&tid,NULL,acceptConn,info);
pthread_detach(tid);
}
// 没有新连接, 通信
for(int i=0; i<maxfd+1; ++i)
{
// 判断从监听的文件描述符之后到maxfd这个范围内的文件描述符是否读缓冲区有数据
if(i != lfd && FD_ISSET(i, &rdtemp))
{
fdinfo *info = (FDInfo *)malloc(sizeof(FDInfo));
info->fd = lfd;
info->maxfd = &maxfd;
info->rdset = &rdtemp;
pthread_t tid;
pthread_create(&tid,NULL,acceptConn,info);
pthread_detach(tid);
}
}
}
close(lfd);
pthread_mutex_destroy(&mutex);
return 0;
}
1、rdset需要保证含有所有的fd,所以rdset只负责往里面加;使用rdtemp去判断; 1、由于pthread_create仅接受一个参数,由于任何类型可以和void *任意转换,所以可以 自定义结构体传递所有的参数;。 1、通过void* 实现于fdinfo * 的自由转化;
2、检测rdtemp中的lfd是否置1,如果置1有新的链接;调用链接的子线程;
3、for(int i=0; i子进程创建和传参
typedef struct fdinfo{
int fd;
int *maxfd;
fd_Set *rdset;
}FDInfo;
-------------------------------------------------------------------------------------------------------
fdinfo *info = (FDInfo *)malloc(sizeof(FDInfo));
info->fd = lfd;
info->maxfd = &maxfd;
info->rdset = &rdset;
pthread_t tid;
pthread_create(&tid,NULL,acceptConn,info);
pthread_detach(tid);
2、由于C没有构造函数,所以一般都是通过 先分配内存,然后再挨个初始化。监听子进程
void* acceptConn(void *arg)
{
printf("子线程线程ID:%ld\n",pthread_self());
fdinfo *info = (fdinfo *)arg;
struct sockaddr_in cliaddr;
int cliLen = sizeof(cliaddr);
int cfd = accept(info->fd, (struct sockaddr*)&cliaddr, &cliLen);
pthread_mutex_lock(&mutex);
FD_SET(cfd, info->rdset);
info->maxfd = &(cfd > *info->maxfd ? cfd : *info->maxfd);
pthread_mutex_unlock(&mutex);
free(info);
return NULL;
}
2、注意由于需要修改rdset和maxfd,需要上锁;
3、return NULL;退出子线程。收发子进程
void * Communication(void *arg)
{
fdinfo *info = (fdinfo *)arg;
// 接收数据
char buf[10] = {0};
// 一次只能接收10个字节, 客户端一次发送100个字节
// 一次是接收不完的, 文件描述符对应的读缓冲区中还有数据
// 下一轮select检测的时候, 内核还会标记这个文件描述符缓冲区有数据 -> 再读一次
// 循环会一直持续, 知道缓冲区数据被读完位置
int len = read(info->fd, buf, sizeof(buf));
if(len == 0)
{
printf("客户端关闭了连接...\n");
pthread_mutex_lock(&mutex);
// 将检测的文件描述符从读集合中删除
FD_CLR(info->fd, &rdset);
pthread_mutex_unlock(&mutex);
close(info->fd);
return NULL;
}
else if(len > 0)
{
// 收到了数据
// 发送数据
write(i, buf, strlen(buf)+1);
}
else
{
// 异常
perror("read");
}
}