1.照惯例,直接放上源码:
// 目的:使用多线程处理客户端发来的请求, 宏定义#define NUM_CLIENT 3
// 为了方便实验,设置可连接的客户端最多为3台
// 步骤:
// 1.socket()创建监听文件描述符 lfd
// 2.bind() 将lfd与ip和port绑定
// 3.listen() 设置为被动监听模式
// 4.while(1)
// {
// cfd = accept()
// pthread_create()创建子线程
// 设置 子线程为分离属性
// 子线程,循环读写
// }
//
#include "wrap.h"
#include <pthread.h>
#include <ctype.h>
#define NUM_CLIENT 3
void * mythread(void * arg)
{
int * p = (int *)arg;
int cfd = *p;
// 处理业务逻辑 读写
char buf[128];
int n = 0;
int i = 0;
while(1)
{
memset(buf, 0x00, sizeof(buf));
n = Read(cfd, buf, sizeof(buf));
if(n <= 0)
{
printf("n == [%d],read error or client closed\n",n);
if(n == 0)
{
printf("---client closed cfd == [%d]\n",cfd);
*p = -1; // 关闭连接后,又将 传递的参数相应位arr[i] 置为-1,为连接新的客户端作准备。
}
break;
}
printf("cfd == [%d] %s\n",cfd, buf);
for(i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
Write(cfd, buf, sizeof(buf));
}
// 读错误退出业务逻辑
close(cfd);
pthread_exit(NULL);
}
int main()
{
// --1--生成监听文件描述符 int socket(int domain, int type, int protocol);
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
//设置端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
// --2--绑定端口 int bind(int sockfd, const struct sockaddr *addr,
// socklen_t addrlen);
struct sockaddr_in serv;
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
// --3-- 设置为监听模式 int listen(int sockfd, int backlog);
Listen(lfd, 128);
// --4-- 进入while循环,监听连接请求,建立相应子线程
pthread_t thread[100];
int ret = 0;
int cfd;
int arr[NUM_CLIENT];
int i = 0;
int len;
for(i = 0; i < NUM_CLIENT; i++)
{
arr[i] = -1;
}
while(1)
{
// --接受连接--int accept(int sockfd, struct sockaddr *addr,
// socklen_t *addrlen);
struct sockaddr_in client;
len = sizeof(client);
cfd = Accept(lfd, (struct sockaddr *)&client, &len);
// 创建子线程
for(i = 0; i < NUM_CLIENT; i++)
{
if(arr[i] == -1)
{
arr[i] = cfd;
ret = pthread_create((thread+i), NULL, mythread, (arr+i));
if(ret != 0)
{
printf("pthread_create error, [%s]\n",strerror(ret));
}
// 打印客户端的信息
char IP[16];
inet_ntop(AF_INET, &client.sin_addr.s_addr, IP, sizeof(IP));
printf("client IP = [%s], port = [%d]\n", IP, ntohs(client.sin_port));
break;
}
}
if(i == NUM_CLIENT)
{
close(cfd);
}
// 设置子线程为分离属性
pthread_detach(thread[i]);
}
// 关闭监听文件描述符
close(lfd);
return 0;
}
2.说明:
①:头文件wrap.h下载于网络,可以直接搜索,上一篇博客也有说明。
②:设置子线程为分离属性,使得线程的退出将自动释放其资源。
3.一些细节:
关注一下这里的创建子线程的for循环,首先判断了一下 arr[i] 是否为-1,如果是才创建线程,否则跳出 for() 循环,重新阻塞等待accept()连接。
arr[i] 为-1有两种情况:
①:客户端连接还没有达到上限,这里设置的上限为3
②:客户端连接达到上限后,有客户端退出,让出了位置。
// --4-- 进入while循环,监听连接请求,建立相应子线程
pthread_t thread[NUM_CLIENT];
int ret = 0;
int cfd;
int arr[NUM_CLIENT];
int i = 0;
int len;
for(i = 0; i < NUM_CLIENT; i++)
{
arr[i] = -1;
}
while(1)
{
// --接受连接--int accept(int sockfd, struct sockaddr *addr,
// socklen_t *addrlen);
struct sockaddr_in client;
len = sizeof(client);
cfd = Accept(lfd, (struct sockaddr *)&client, &len);
// 创建子线程
for(i = 0; i < NUM_CLIENT; i++)
{
if(arr[i] == -1)
{
arr[i] = cfd;
ret = pthread_create((thread+i), NULL, mythread, (arr+i));
if(ret != 0)
{
printf("pthread_create error, [%s]\n",strerror(ret));
}
// 打印客户端的信息
char IP[16];
inet_ntop(AF_INET, &client.sin_addr.s_addr, IP, sizeof(IP));
printf("client IP = [%s], port = [%d]\n", IP, ntohs(client.sin_port));
// 设置子线程为分离属性
pthread_detach(thread[i]);
break;
}
}
if(i == NUM_CLIENT) // 客户端连接达到上限,忽略之后的连接
{
close(cfd);
}
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
子线程处理函数中,使用指针 p 接收 arg 参数的目的是获得指向数组 arr 的指针,以便在关闭客户端后重新将相应位置,重新置为 -1.
对第三点总结一下:其实就是为了实现使用一个有限长数组 arr 接受多个客户端的连接啦 ~
4.实验:
想象一下,启动服务端后,客户端去连接它,会有什么结果发生:
- 首先 客户端上限为3,第4个客户端连接应该是失败的;
- 然后 在3个已经连接的客户端中,关闭1个,再去尝试连接,应该是成功的;
- 而且 由于关闭客户端时同时回收了通信文件描述符 cfd ,所以关闭客户端后再连接应该得到以前分配过的通信文件描述符,当然回收由内核选择合适的时机去做,可能存在分配新的文件描述符。
< 1 >.首先3个客户端去连接服务端,都是成功的
再第4个去连
服务端的显示没有变化,说明没有连接成功(我没有对连接不成功的做打印,只对成功的做了打印)
还可以直接看一下相关线程信息如下。主线程4208和它的3个子线程4210、4212、4214,也说明第4个客户端没有连接成功
< 2 和 3>再,关一个再连
我下面关了两个,连了连个,关闭之后连接成功,对应上面我们的设想2
仔细观察一下,新连接的客户端,分配到的 通信文件描述符是 7和5 符合我们的设想3
再观察一下对应的线程信息:
的确是两个新建立的子线程 4295、4297
好了,over~