加入收藏 | 设为首页 | 会员中心 | 我要投稿 温州站长网 (https://www.0577zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

网络IO管理-简单一问一答、多线程方式

发布时间:2022-11-25 23:41:21 所属栏目:Linux 来源:互联网
导读: 思考
1. 那网络中进程之间如何通信,浏览器的进程怎么与web服务器通信的?
2. 什么时候用一请求一线程的方式?
3. 什么时候用select/poll?
4. 什么时候用epoll?
准备工作
下面展示socket几

思考

1. 那网络中进程之间如何通信,浏览器的进程怎么与web服务器通信的?

2. 什么时候用一请求一线程的方式?

3. 什么时候用select/poll?

4. 什么时候用epoll?

准备工作

下面展示socket几个常用的函数listenfd, bind, listen, accept具体作用。

// 聘请迎宾的小姐姐
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
    return 0;
}

// 迎宾的小姐姐在哪个门口工作
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
}

// 小姐姐正式走到门口开始迎宾
if (listen(listenfd, 10) == -1) {
   	printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
   	return 0;
}

// 把客户带到大厅介绍一个服务员,coonfd就是服务员
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
	printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
    return 0;
}

网络连接的简单过程

线程池linux_c#线程池调用线程_linux c高效线程池

网络IO模型阻塞IO

1. 简单一问一答服务器/客户机模型

c#线程池调用线程_线程池linux_linux c高效线程池

当用户进程调用了 read 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于network io 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的数据包),这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel一直等到数据准备好了它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。所以,blocking IO 的特点就是在 IO 执行的两个阶段(等待数据和拷贝数据两个阶段)都被block 了。

几乎所有的程序员第一次接触到的网络编程都是从 listen()、send()、recv() 等接口开始的,这些接口都是阻塞型的。使用这些接口可以很方便的构建服务器/客户机的模型。

相关视频推荐

6种epoll的设计,让你吊打面试官

网络原理tcp/udp,网络编程epoll/reactor,面试中正经“八股文”

学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

linux c高效线程池_线程池linux_c#线程池调用线程

代码展示

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define MAXLNE  4096
 
int main(int argc, char **argv) 
{
    int listenfd, connfd, n;
    struct sockaddr_in servaddr;
    char buff[MAXLNE];
 
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
 
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

线程池linux_c#线程池调用线程_linux c高效线程池

servaddr.sin_port = htons(9999); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) { printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } if (listen(listenfd, 10) == -1) { printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } struct sockaddr_in client; socklen_t len = sizeof(client); if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) { printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } printf("========waiting for client's request========\n"); while (1) { n = recv(connfd, buff, MAXLNE, 0); if (n > 0) { buff[n] = '\0'; printf("recv msg from client: %s\n", buff); send(connfd, buff, n, 0); } else if (n == 0) { close(connfd); } //close(connfd); } close(listenfd); return 0; }

2. 多线程方式

提出问题

实际上,除非特别指定,几乎所有的 IO 接口 ( 包括 socket 接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用 send()的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。

解决方案

改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。具体使用多进程还是多线程并没有一个特定的模式。

适用场景与注意的问题

户端不多的情况下,在一个局域网内,做几个人的会议这类的,理论上客户端很难突破C10k,使用 pthread_create ()创建新线程。

多线程的服务器模型似乎完美的解决了为多个客户机提供问答服务的要求,但其实并不尽然。如果要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态。

这时候很多人会考虑使用“线程池”或“连接池”。这里就需要深刻理解一下其概念,“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。

代码展示

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define MAXLNE  4096
//8m * 4G = 128 , 512
//C10k
void *client_routine(void *arg) { //
	int connfd = *(int *)arg;

	char buff[MAXLNE];
	while (1) {
		int n = recv(connfd, buff, MAXLNE, 0);
        if (n > 0) {
            buff[n] = '\0';
            printf("recv msg from client: %s\n", buff);
	    	send(connfd, buff, n, 0);
        } else if (n == 0) {
            close(connfd);
			break;
        }
	}
	return NULL;
}
int main(int argc, char **argv) 
{
    int listenfd, connfd, n;
    struct sockaddr_in servaddr;
    char buff[MAXLNE];
 
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
 
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
 
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
 
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    
	printf("========waiting for client's request========\n");
	while (1) {
		struct sockaddr_in client;
	    socklen_t len = sizeof(client);
	    if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
	        printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
	        return 0;
	    }
		pthread_t threadid;
		pthread_create(&threadid, NULL, client_routine, (void*)&connfd);
    }
    close(listenfd);
    return 0;
}

理解多线程方式不足

假设有A, B, C三个客户端连接服务器,其中A, B发送数据,C连接进来。A, B服务员正在服务,C迎宾的小姐姐迎接。这时候服务器没办法准确的预测哪个fd会有数据来临。大量的客户端链接进来,需处理多个客户端,我们不知道哪个客户端需要我们处理。这时,如果有一个组件把所有的fd放在一起线程池linux,准确的知道哪个客户端需要我们处理。

(编辑:温州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!