当前位置: 首页 > news >正文

《TCP/IP网络编程》阅读笔记--多线程服务器端的实现

目录

1--多线程的优点

2--进程和线程的差异

3--线程创建

4--线程使用

5--线程安全问题

6--互斥量

7--信号量

8--线程销毁

9--多线程并发聊天程序

9-1--服务器端

9-2--客户端

9-3--测试结果


1--多线程的优点

多进程服务器的缺点:

        ① 创建进程的过程会带来一定的开销;

        ② 为了完成进程间的数据交换,需要特殊的 IPC 技术;

        ③ 进程间的上下文切换是创建进程时的最大开销;

多线程的优点:

        ① 线程的创建和上下文切换比进程的创建和上下文切换更快;

        ② 线程间交换数据时无需特殊技术;

2--进程和线程的差异

        每个进程拥有独立的内存空间,拥有自己的数据区、堆区域和栈区域;

        每个线程只拥有自己的栈区域,线程间共享数据区和堆区域;因此线程间上下文切换时不需要切换数据区和堆,可以利用数据区和堆区域交换数据;

        

        进程:在操作系统构成单独执行流的单位;

        线程:在进程构成单独执行流的单位;

        进程是在操作系统内部生成多个执行流,线程就是在同一进程内部创建多条执行流;

3--线程创建

#include <pthread.h>
int pthread_create(pthread_t* restrict thread, const pthread_attr_t* restrict attr, void* (* start_routine)(void*), void* restrict arg);
// 成功时返回 0,失败时返回其他值
// thread 表示保存新创建线程 ID 的变量地址值
// attr 表示用于传递线程属性的参数,传递 NULL 时表示创建默认属性的线程
// start_routine 表示线程的入口函数
// arg 表示传递给线程入口函数的参数
// gcc thread1.c -o thread1 -lpthread
// ./thread1#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void* thread_main(void* arg){ // 线程入口函数int i;int cnt = *((int*)arg);for(i = 0; i < cnt; i++){sleep(1);puts("running thread");}return NULL;
}int main(int argc, char* argv[]){pthread_t t_id;int thread_param = 5;if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param) != 0){ // 创建线程puts("pthread_create() error");return -1;}sleep(10);puts("end of main");return 0;
}

4--线程使用

        调用 pthread_join(ID) 可以使进程或线程进入等待状态,直到 ID 对应的线程终止为止;

#include <pthread.h>
int pthread_join(pthread_t thread, void** status);
// 成功时返回0,失败时返回其他值
// thread 表示线程ID,只有该线程终止后才会从函数返回
// status 表示保存线程返回值的指针变量地址值 
// gcc thread2.c -o thread2 -lpthread
// ./thread2#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>void* thread_main(void *arg){int i;int cnt = *((int*)arg);char* msg = (char*)malloc(sizeof(char)*50);strcpy(msg, "Hello, I'm thread~ \n");for(i = 0; i < cnt; i++){sleep(1);puts("running thread");}return (void*)msg;
}int main(int argc, char* argv[]){pthread_t t_id;int thread_param = 5;void* thr_ret;// 创建线程if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param) != 0){puts("pthread_create() error");return -1;}// 阻塞,等待线程返回if(pthread_join(t_id, &thr_ret) != 0){puts("pthread_join() error");return -1;}// 打印线程返回值printf("Thread return message: %s \n", (char*)thr_ret);free(thr_ret);return 0;
}

5--线程安全问题

        多个线程同时调用函数执行临界区代码时,会出现问题;

        根据临界区是否引起问题,函数可分为:线程安全函数和非线程安全函数;

        线程安全函数被多个线程同时调用时不会引发问题,非线程安全函数被同时调用时会引发问题;

        以下代码展示了多个线程同时访问临界区代码操作全局变量时会出现意向不到的问题;

// gcc thread4.c -D_REENTRANT -o thread4 -lpthread
// ./thread4#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREAD 100long long num = 0;void* thread_inc(void* arg){int i;for(i = 0; i < 50000000; i++){num += 1;}return 0;
}void* thread_des(void* arg){int i;for(i = 0; i < 50000000; i++){num -= 1;}return 0;
}int main(int argc, char* argv[]){pthread_t thread_id[NUM_THREAD];int i;printf("sizeof long long: %ld \n", sizeof(long long));for(i = 0; i < NUM_THREAD; i++){// 各创建50个线程,分别执行对全局变量 num 的加减操作if(i%2){pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);}else{pthread_create(&(thread_id[i]), NULL, thread_des, NULL);}}for(i = 0; i < NUM_THREAD; i++){pthread_join(thread_id[i], NULL);}printf("result: %lld \n", num);return 0;
}

        正常结果应为0,但实际结果并不是;这就是多线程同时访问临界区,会出现数据竞争等的问题;

        线程访问变量 num 时应阻止其他线程访问,直到一个线程完成运算,这就是同步(Synchronization);线程同步用于解决线程访问顺序引发的问题;

6--互斥量

        互斥量表示不允许多个线程同时访问,主要用于解决线程同步访问的问题;

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
// 成功时返回 0,失败时返回其他值
// mutex 表示创建和销毁互斥量时传递保存和销毁互斥量的变量地址值
// attr 传递即将创建的互斥量属性,没有特别需要指定的属性时传递 NULLint pthread_mutex_lock(pthread_mutex_t* mutex); // 上锁
int pthread_mutex_unlock(pthread_mutex_t* mutex); // 解锁
// 成功时返回 0,失败时返回其他值pthread_mutex_t mutex;
pthread_mutex_lock(&mutex);
// 临界区开始
// ...
// 临界区结束
pthread_mutex_unlock(&mutex);

7--信号量

        利用二进制信号量完成控制线程程序为中心的同步方法;

#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
int sem_destroy(sem_t* sem);
// 成功时返回 0,失败时返回其他值
// sem 表示信号量的变量地址值
// pshared 传递其他值时,创建可由多个进程共享的信号量;传递 0 时,创建只允许一个进程内部使用的信号量
// value 表示指定新创建的信号量的初始值int sem_post(sem_t* sem); // 信号量增加1
int sem_wait(sem_t* sem); // 信号量减少1
// 成功时返回 0,失败时返回其他值sem_wait(&sem); // 信号量变为0
// 临界区的开始
// ...
// 临界区的结束
sem_post(&sem); // 信号量变为1

8--线程销毁

线程销毁的 3 种方法:

        ① 调用 main 函数的返回语句;

        ② 调用 pthread_join() 函数;

        ③ 调用 pthread_detach() 函数;

9--多线程并发聊天程序

9-1--服务器端

// gcc chat_server.c -D_REENTRANT -o chat_server -lpthread
// ./chat_server 9190#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>#define BUF_SIZE 100
#define MAX_CLNT 256int clnt_cnt = 0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;void send_msg(char* msg, int len){int i;pthread_mutex_lock(&mutx);for(i = 0; i < clnt_cnt; i++){write(clnt_socks[i], msg, len);}pthread_mutex_unlock(&mutx);
}void* handle_clnt(void* arg){int clnt_sock = *((int*)arg);int str_len = 0, i;char msg[BUF_SIZE];while((str_len = read(clnt_sock, msg, sizeof(msg))) != 0){send_msg(msg, str_len);}pthread_mutex_lock(&mutx);for(i = 0; i < clnt_cnt; i++){if(clnt_sock == clnt_socks[i]){while(i++ < clnt_cnt - 1)clnt_socks[i] = clnt_socks[i+1];break;}}clnt_cnt--;pthread_mutex_unlock(&mutx);close(clnt_sock);return NULL;
}void error_handling(char *msg){fputs(msg, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char* argv[]){int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;int clnt_adr_sz;pthread_t t_id;if(argc != 2){printf("Usage: %s <port>\n", argv[0]);exit(1);}pthread_mutex_init(&mutx, NULL);serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1){error_handling("bind() error");}if(listen(serv_sock, 5) == -1){error_handling("listen() error");}while(1){clnt_adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);pthread_mutex_lock(&mutx);clnt_socks[clnt_cnt++] = clnt_sock;pthread_mutex_unlock(&mutx);pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);pthread_detach(t_id);printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr));}close(serv_sock);return 0;
}

9-2--客户端

// gcc chat_client.c -D_REENTRANT -o chat_client -lpthread
// ./chat_client 127.0.0.1 9190 Yoon#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>#define BUF_SIZE 100
#define NAME_SIZE 20char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE];void* send_msg(void* arg){int sock = *((int*)arg);char name_msg[NAME_SIZE + BUF_SIZE];while(1){fgets(msg, BUF_SIZE, stdin);if(!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")){close(sock);exit(0);}sprintf(name_msg, "%s %s", name, msg);write(sock, name_msg, strlen(name_msg));}return NULL;
}void* recv_msg(void* arg){int sock = *((int*)arg);char name_msg[NAME_SIZE+BUF_SIZE];int str_len;while(1){str_len = read(sock, name_msg, NAME_SIZE+BUF_SIZE-1);if(str_len == -1){return (void*)-1;}name_msg[str_len] = 0;fputs(name_msg, stdout);}return NULL;
}void error_handling(char *msg){fputs(msg, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char* argv[]){int sock;struct sockaddr_in serv_addr;pthread_t snd_thread, rcv_thread;void* thread_return;if(argc != 4){printf("Usage: %s <IP> <port> <name>\n", argv[0]);exit(1);}sprintf(name, "[%s]", argv[3]);sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){error_handling("connect() error!");}pthread_create(&snd_thread, NULL, send_msg, (void*)&sock);pthread_create(&rcv_thread, NULL, recv_msg, (void*)&sock);pthread_join(snd_thread, &thread_return);pthread_join(rcv_thread, &thread_return);close(sock);return 0;
}

9-3--测试结果

相关文章:

《TCP/IP网络编程》阅读笔记--多线程服务器端的实现

目录 1--多线程的优点 2--进程和线程的差异 3--线程创建 4--线程使用 5--线程安全问题 6--互斥量 7--信号量 8--线程销毁 9--多线程并发聊天程序 9-1--服务器端 9-2--客户端 9-3--测试结果 1--多线程的优点 多进程服务器的缺点&#xff1a; ① 创建进程的过程会带来…...

修改el-card的header的背景颜色

修改el-card的header的背景颜色 1.修改默认样式 好处是当前页面的所有的el-card都会变化 页面卡片&#xff1a; <el-card class"box-card" ><div slot"header" class"clearfix"><span>卡片名称</span><el-button s…...

ubuntu系统中查看打开的端口

要查看Ubuntu系统中已打开的端口及其相关信息&#xff0c;可以使用以下方法&#xff1a; 打开终端&#xff08;Terminal&#xff09;。 运行以下命令以查看当前系统中的端口使用情况&#xff1a; sudo netstat -tuln这将显示所有已打开的端口及其相关信息&#xff0c;包括监听…...

Datax从mysql同步数据到HDFS

在实际使用Datax的时候&#xff0c;比较常用的是同步业务数据&#xff08;mysql中的数据&#xff09;到HDFS来实现数仓的创建&#xff0c;那么怎么实现呢&#xff1f;我们一步步来实现&#xff08;基于Datax 3.0.0&#xff09; 1、检查环境&#xff0c;需要安装完一个Datax&am…...

使用 Selenium 或其他工具模拟浏览器使用及语法代码

使用Selenium模拟浏览器使用的代码示例如下&#xff1a; from selenium import webdriverfrom selenium.webdriver.common.keys import Keys# 创建浏览器驱动实例driver webdriver.Chrome()# 打开网页driver.get("https://www.example.com")# 查找并填写表单search_…...

华为手机如何开启设置健康使用手机模式限制孩子玩手机时间?

华为手机如何开启设置健康使用手机模式限制孩子玩手机时间&#xff1f; 1、在手机上找到「设置」并点击打开&#xff1b; 2、在设置内找到「健康使用手机」并点击进入&#xff1b; 3、开启健康使用手机后&#xff0c;选择孩子使用&#xff1b; 4、在健康使用手机内&#xff0c…...

【Linux】线程池 | 自旋锁 | 读写锁

文章目录 一、线程池1. 线程池模型和应用场景2. 单例模式实现线程池(懒汉模式) 二、其他常见的锁1. STL、智能指针和线程安全2. 其他常见的锁 三、读者写者问题1. 读者写者模型2. 读写锁 一、线程池 1. 线程池模型和应用场景 线程池是一种线程使用模式。线程过多会带来调度开…...

[网鼎杯 2020 青龙组]bang 题解

写一道安卓题的WP 首先你需要一个root机&#xff0c;使用真机或者虚拟机&#xff0c;根据网上的教程刷机并获取root 我使用真机调试&#xff0c;pixel2 讲安卓包下载到真机 在PC端配置frida 对应版本的server传送到/data/local/tmp 然后进行以上操作&#xff0c;启动server …...

创建环境时提示:ERROR conda.core.link:_execute(502)

创建环境时提示&#xff1a;ERROR conda.core.link:_execute(502) 创建环境最后Executing transaction&#xff0c;失败&#xff0c;提示如下&#xff1a; Preparing transaction: done Verifying transaction: done Executing transaction: failed ERROR conda.core.link:_e…...

Python150题day07

1.5集合练习题 集合间的运算 lst1 [1, 2, 3, 5, 6, 3, 2] lst2 [2, 5, 7, 9] 哪些整数既在Ist1中&#xff0c;也在Ist2中哪些整数在Ist1中&#xff0c;不在Ist2中两个列表一共有哪些整数 虽然题目问的是两个列表之间的问题&#xff0c;但是用列表解答的效率很低&#xff0c…...

LeetCode 2596. 检查骑士巡视方案

【LetMeFly】2596.检查骑士巡视方案 力扣题目链接&#xff1a;https://leetcode.cn/problems/check-knight-tour-configuration/ 骑士在一张 n x n 的棋盘上巡视。在有效的巡视方案中&#xff0c;骑士会从棋盘的 左上角 出发&#xff0c;并且访问棋盘上的每个格子 恰好一次 。…...

大数据学习1.0-目录

学习内容持续更新ing 1.大数据学习1.1-Centos8虚拟机安装 大数据学习1.0-Centos8虚拟机安装_汉卿HanQ的博客-CSDN博客 2.大数据学习1.2-yum配置 大数据学习1.2-yum配置_汉卿HanQ的博客-CSDN博客 3.大数据学习1.3-xShell配置jdk 大数据学习1.3-xShell配置jdk_汉卿HanQ的博客…...

无涯教程-JavaScript - POWER函数

描述 POWER函数返回加到幂的数字的输出。 语法 POWER (number, power)争论 Argument描述Required/OptionalNumber 基数。 它可以是任何实数。 RequiredPowerThe exponent to which the base number is raised.Required Notes 可以使用" ^"运算符代替POWER来指示…...

ChatGPT:解释Java中 ‘HttpResponse‘ 使用 ‘try-with-resources‘ 的警告和处理 ‘Throwable‘ 打印警告

ChatGPT&#xff1a;解释Java中 ‘HttpResponse’ 使用 ‘try-with-resources’ 的警告和处理 ‘Throwable’ 打印警告 我在IDEA中对一个函数的警告点击了ignore&#xff0c;怎么撤回这个呢 ChatGPT&#xff1a; 要撤回在IDEA中对一个函数的警告的忽略&#xff0c;您可以按照以…...

Linux编辑器-gcc的使用

一&#xff1a;背景知识 1.预处理&#xff08;头文件展开、去注释、宏替换、条件编译&#xff09; 2.编译&#xff08;由C生成汇编&#xff09; 3.汇编&#xff08;生成及其可识别代码&#xff09; 4.连接&#xff08;生成可执行文件或库文件&#xff09; 二&#xff1a;gcc…...

第16篇ESP32 platformio_arduino框架 wifi联网_连接WiFi热点并连接tcp server收发数据进行通讯

第1篇:Arduino与ESP32开发板的安装方法 第2篇:ESP32 helloword第一个程序示范点亮板载LED 第3篇:vscode搭建esp32 arduino开发环境 第4篇:vscodeplatformio搭建esp32 arduino开发环境 ​​​​​​第5篇:doit_esp32_devkit_v1使用pmw呼吸灯实验 第6篇:ESP32连接无源喇叭播…...

day1| 704. 二分查找、27. 移除元素

704. 二分查找 题目链接&#xff1a;https://leetcode.cn/problems/binary-search/ 文档讲解&#xff1a;https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1fA4y1o715 1、二分法的前提 这道…...

R绘制箱线图

代码大部分来自boxplot()函数的帮助文件&#xff0c;可以通过阅读帮助文件&#xff0c;调整代码中相应参数看下效果&#xff0c;进而可以理解相应的作用&#xff0c;帮助快速掌握barplot()函数的用法。 语法 Usage(来自帮助文件) barplot(height, ...)## Default S3 method: …...

利用Audit审计系统行为

标题利用Audit审计系统行为 Linux Audit守护进程是一个可以审计Linux系统事件的框架 这个框架本身有数个组件&#xff0c;包括内核、二进制文件及其他文件。 1.内核audit&#xff1a;钩在内核中来捕获事件并将它们发送到auditd。 2.二进制文件 auditd&#xff1a;捕捉事件并…...

uniapp:不同权限设置不同的tabBar

1、在pages.json里&#xff0c;将所有tabBar涉及的页面都加进来。 我这里使用username来动态显示tabBar。 jeecg用户显示&#xff1a;首页&#xff0c;订单&#xff0c;消息&#xff0c;发现&#xff0c;我的&#xff0c;一共5个tabBar。 admin用户显示&#xff1a;首页&…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...