多线程服务器是对多进程服务器的改进。由于多进程服务器在创建进程时会消耗大量的系统资源,因此使用线程来替换进程,这样可以更快地创建服务处理器。据统计,创建线程和创建进程要快10100倍,因此线程被称为“轻量级”进程。线程与进程的区别在于,一个进程中的所有线程共享相同的信息,例如全局内存、全局变量等。这种机制带来了同步问题。
tcp多线程并发服务器框架:
我们在使用多线程并发服务器的时候,直接使用上面的框架,我们只修改()里面的内容。
代码示例:
#
#
#
#
#
#
#
#
/************************************************* *** ************************
函数名:void *(void *arg)
函数函数:线程函数,处理客户信息
功能参数:连接的
函数返回:无
****************************************************** ** *********************/
无效*(无效*arg)
{
= 0;
[1024] = “”; // 接收缓冲区
= (int)arg; //传入连接的套接字
// 接收数据
而(( = recv(, ,(), 0)) > 0)
{
(“: %s\n”, ); // 打印数据
发送(,,,,0);// 将数据发送回客户端
}
(” !\n”);
关(); //关闭连接的
;
}
//================================================= ==== ================
// 语法:void main(void)
// 实现函数:main函数,建立TCP并发服务器
//入口参数:无
// 退出参数:无
//================================================= ==== ================
(,char*argv[])
{
= 0; // 插座
= 0;
= 0;
; // 服务器地址结构
= 8080; // 监听端口
;
(“TCP 在端口 %d!\n”, 端口);
= (, , 0); // 创建一个 TCP 套接字
如果(<0)
{
(“错误”);
退出(-1);
}
bzero(&,()); //初始化服务器地址
. = ;
. = htons(端口);
.. = htonl();
(” 到端口 %d\n”, 端口);
// 绑定
= 绑定(, (*)&,());
如果(!= 0)
{
(“绑定”);
关();
退出(-1);
}
// 听着, 变成被动的
= (, 10);
如果(!= 0)
{
(“”);
关();
退出(-1);
}
(“…\n”);
而(1)
{
[] = “”; // 用于保存客户端IP地址
; // 用于保存客户端地址
=(); // 必须初始化!!!
// 获取已建立的连接 = (, (*)&, &);
如果(<0)
{
(“这次”);
;
}
//打印客户端的ip和端口
(, &., , );
(“———————————————— ————\n”);
(“ip=%s,端口=%d\n”, ,ntohs(.));
如果(> 0)
{
//因为同一个进程中的所有线程共享内存和变量,所以在传递参数时需要特殊处理,传值。
(&, NULL, (void*), (void*)); //创建线程
(); // 线程分离,最后自动回收资源
}
}
关();
;
}
运行结果:
注意:
1、上面()函数的最后一个参数是void *类型,为什么可以传值?
而(1)
{
= (, (*)&, &);
(&, NULL, (void*), (void*));
();
}
因为 void * 是 4 个字节,而 int 类型也是 4 个字节,所以可以传值。如果是char,short,上面的值会出错
2.上述()函数的最后一个参数可以传地址吗?可以,但会导致服务器出现不可预知的问题
而(1)
{
= (, (*)&, &);
(&, NULL, (void*), (void*)&);
();
}
原因:如果有多个客户端连接到这台服务器,一般情况下,一个客户端连接对应一个,互不影响。但是,如果多个客户端同时连接到这台服务器,客户端A的连接集不会受到影响。连接是服务器正在使用这个来处理数据,它还没有被处理。突然来了一个B客户端,()之后又生成了一个。因为是地址传输,所以A客户端的连接也变成了B的。在这种情况下,服务器必须不再是 A 的客户端服务器
2.如果要给线程函数传多个参数,首先考虑结构体参数,此时传值是不行的,只能传地址。
这时候就需要考虑多任务的互斥或者同步的问题了。在这里,我们使用互斥锁来解决这个问题正在连接服务器,并确保这个结构的参数值在允许修改之前保存在一个临时变量中。
#
互斥体;// 定义互斥锁,全局变量
(&mutex, NULL); // 初始化互斥量,互斥量默认开启
// 锁定,() 将阻塞,直到它被解锁
(&mutex);
= (, (*)&, &);
//传递给回调函数的参数,&,传递地址
(&, NULL, (void*), (void*)&); //创建线程
// 线程回调函数
无效*(无效*arg)
{
= *(int*)arg; // 传入的连接套接字
// 解锁, () 唤醒, 不阻塞 (&mutex);
;
}
示例代码:
#
#
#
#
#
#
#
#
互斥体;// 定义互斥锁,全局变量
/************************************************* *** ************************
函数名:void *(void *arg)
函数函数:线程函数,处理客户信息
功能参数:连接的
函数返回:无
****************************************************** ** *********************/
无效*(无效*arg)
{
= 0;
[1024] = “”; // 接收缓冲区
= *(int*)arg; // 传入的连接套接字
//解锁,()唤醒,不阻塞
(&mutex);
// 接收数据
而(( = recv(, ,(), 0)) > 0)
{
(“: %s\n”, ); // 打印数据
发送(,,,,0);// 将数据发送回客户端
}
(” !\n”);
关(); //关闭连接的
;
}
//================================================= ==== ================
// 语法:void main(void)
// 实现函数:main函数,建立TCP并发服务器
//入口参数:无
// 退出参数:无
//================================================= ==== ================
(,char*argv[])
{
= 0; // 插座
= 0;
= 0;
; // 服务器地址结构
= 8080; // 监听端口
;
(&mutex, NULL); // 初始化互斥量,互斥量默认开启
(“TCP 在端口 %d!\n”, 端口);
= (, , 0); // 创建一个 TCP 套接字
如果(<0)
{
(“错误”);
退出(-1);
}
bzero(&,()); //初始化服务器地址
. = ;
. = htons(端口);
.. = htonl();
(” 到端口 %d\n”, 端口);
// 绑定
= 绑定(, (*)&,());
如果(!= 0)
{
(“绑定”);
关();
退出(-1);
}
// 听着, 变成被动的
= (, 10);
如果(!= 0)
{
(“”);
关();
退出(-1);
}
(“…\n”);
而(1)
{
[] = “”; // 用于保存客户端IP地址
; // 用于保存客户端地址
=(); // 必须初始化!!!
// 锁定,() 将阻塞,直到它被解锁
(&mutex);
// 获取已建立的连接
= (, (*)&, &);
如果(<0)
{
(“这次”);
;
}
//打印客户端的ip和端口
(, &., , );
(“———————————————— ————\n”);
(“ip=%s,端口=%d\n”, ,ntohs(.));
如果(> 0)
{
//传递给回调函数的参数,&,传递地址
(&, NULL, (void*), (void*)&); //创建线程
(); // 线程分离,最后自动回收资源
}
}
关();
;
}
运行结果:
注意:这种互斥锁的使用对服务器的运行效率有致命的影响
需要C/C++ Linux服务器开发学习资料加(资料包括C/C++、Linux、技术、Nginx、MySQL、Redis、ZK、流媒体、CDN、P2P、K8S、TCP/IP、协程、DPDK等)得到