《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--多线程的优点 多进程服务器的缺点: ① 创建进程的过程会带来…...
修改el-card的header的背景颜色
修改el-card的header的背景颜色 1.修改默认样式 好处是当前页面的所有的el-card都会变化 页面卡片: <el-card class"box-card" ><div slot"header" class"clearfix"><span>卡片名称</span><el-button s…...
ubuntu系统中查看打开的端口
要查看Ubuntu系统中已打开的端口及其相关信息,可以使用以下方法: 打开终端(Terminal)。 运行以下命令以查看当前系统中的端口使用情况: sudo netstat -tuln这将显示所有已打开的端口及其相关信息,包括监听…...
Datax从mysql同步数据到HDFS
在实际使用Datax的时候,比较常用的是同步业务数据(mysql中的数据)到HDFS来实现数仓的创建,那么怎么实现呢?我们一步步来实现(基于Datax 3.0.0) 1、检查环境,需要安装完一个Datax&am…...
使用 Selenium 或其他工具模拟浏览器使用及语法代码
使用Selenium模拟浏览器使用的代码示例如下: from selenium import webdriverfrom selenium.webdriver.common.keys import Keys# 创建浏览器驱动实例driver webdriver.Chrome()# 打开网页driver.get("https://www.example.com")# 查找并填写表单search_…...
华为手机如何开启设置健康使用手机模式限制孩子玩手机时间?
华为手机如何开启设置健康使用手机模式限制孩子玩手机时间? 1、在手机上找到「设置」并点击打开; 2、在设置内找到「健康使用手机」并点击进入; 3、开启健康使用手机后,选择孩子使用; 4、在健康使用手机内,…...
【Linux】线程池 | 自旋锁 | 读写锁
文章目录 一、线程池1. 线程池模型和应用场景2. 单例模式实现线程池(懒汉模式) 二、其他常见的锁1. STL、智能指针和线程安全2. 其他常见的锁 三、读者写者问题1. 读者写者模型2. 读写锁 一、线程池 1. 线程池模型和应用场景 线程池是一种线程使用模式。线程过多会带来调度开…...
[网鼎杯 2020 青龙组]bang 题解
写一道安卓题的WP 首先你需要一个root机,使用真机或者虚拟机,根据网上的教程刷机并获取root 我使用真机调试,pixel2 讲安卓包下载到真机 在PC端配置frida 对应版本的server传送到/data/local/tmp 然后进行以上操作,启动server …...
创建环境时提示:ERROR conda.core.link:_execute(502)
创建环境时提示:ERROR conda.core.link:_execute(502) 创建环境最后Executing transaction,失败,提示如下: 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中,也在Ist2中哪些整数在Ist1中,不在Ist2中两个列表一共有哪些整数 虽然题目问的是两个列表之间的问题,但是用列表解答的效率很低,…...
LeetCode 2596. 检查骑士巡视方案
【LetMeFly】2596.检查骑士巡视方案 力扣题目链接:https://leetcode.cn/problems/check-knight-tour-configuration/ 骑士在一张 n x n 的棋盘上巡视。在有效的巡视方案中,骑士会从棋盘的 左上角 出发,并且访问棋盘上的每个格子 恰好一次 。…...
大数据学习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:解释Java中 ‘HttpResponse’ 使用 ‘try-with-resources’ 的警告和处理 ‘Throwable’ 打印警告 我在IDEA中对一个函数的警告点击了ignore,怎么撤回这个呢 ChatGPT: 要撤回在IDEA中对一个函数的警告的忽略,您可以按照以…...
Linux编辑器-gcc的使用
一:背景知识 1.预处理(头文件展开、去注释、宏替换、条件编译) 2.编译(由C生成汇编) 3.汇编(生成及其可识别代码) 4.连接(生成可执行文件或库文件) 二: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. 二分查找 题目链接:https://leetcode.cn/problems/binary-search/ 文档讲解:https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html 视频讲解:https://www.bilibili.com/video/BV1fA4y1o715 1、二分法的前提 这道…...
R绘制箱线图
代码大部分来自boxplot()函数的帮助文件,可以通过阅读帮助文件,调整代码中相应参数看下效果,进而可以理解相应的作用,帮助快速掌握barplot()函数的用法。 语法 Usage(来自帮助文件) barplot(height, ...)## Default S3 method: …...
利用Audit审计系统行为
标题利用Audit审计系统行为 Linux Audit守护进程是一个可以审计Linux系统事件的框架 这个框架本身有数个组件,包括内核、二进制文件及其他文件。 1.内核audit:钩在内核中来捕获事件并将它们发送到auditd。 2.二进制文件 auditd:捕捉事件并…...
uniapp:不同权限设置不同的tabBar
1、在pages.json里,将所有tabBar涉及的页面都加进来。 我这里使用username来动态显示tabBar。 jeecg用户显示:首页,订单,消息,发现,我的,一共5个tabBar。 admin用户显示:首页&…...
如何将本地的项目上传到Git
一、GitHub or GitLab or Gitee创建一个新的仓库 二、仓库路径创建成功后,将本地项目上传到git 1. 进入本地项目所在文件夹位置,右击 2.出现git命令框 输入git init 在当前项目的目录中生成本地的git管理(会发现在当前目录下多了一个.git文件…...
[php] 文件上传的一个项目emmm
项目完整地址 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><title>上传文件</title><link href"./css/bootstrap.min.css" rel"stylesheet"><style>font-face {fo…...
uniapp-时间格式和距离格式的转换
时间格式的转换 第一种是把 YYYY-MM-DD hh:mm:ss 转换成 MM月DD日 第二种是把 hh:mm:ss 转换成 hh:mm /*** 格式化时间 1* 把传入的完整时间分为 MM月DD日 的格式* returns*/ export function formatDate(timeStr) {const date new Date(timeStr);const month (date.ge…...
【卖出备兑看涨期权策略(Covered_call)】
卖出备兑看涨期权策略(Covered_call) 卖出备兑看涨期权策略是一种最基本的收入策略,该策略主要操作就是在持有标的资产的同时卖出对应的看涨期权合约,以此来作为从持有的标的资产中获取租金的一种方法。如果标的资产的价格上涨到…...
【校招VIP】测试算法考点之智力分析
考点介绍: 智力题(逻辑分析题)准备校招的同学们好好准备下,测试笔试中经常遇到。 测试算法考点之智力分析-相关题目及解析内容可点击文章末尾链接查看! 一、考点试题 1.5个囚犯在装有100颗豆子的袋子里摸,他们谁的存活几率大? 5个囚犯,分…...
【Linux 服务器运维】定时任务 crontab 详解 | 文末送书
文章目录 前言一、crontab 介绍1.1 什么是 crontab1.2 crontab 命令工作流程1.3 Linux 定时任务分类 二、crontab 用法详解2.1 crond 服务安装2.2 crontab 文件内容分析2.3 crontab 命令用法2.3.1 查看定时任务列表2.3.2 编辑/创建定时任务2.3.3 删除定时任务2.3.4 其他 cronta…...
Vue系列之入门篇
前言: 目录 一,关于Vue的简介 1.什么是Vue? 2.使用Vue框架的好处? 3. 库和框架的区别: 4. MVVM的介绍 5.Vue的入门案例 二,Vue的生命周期 一,关于Vue的简介 1.什么是Vue? Vu…...
【遥感卫星数据】Landsat数据Collection1和Collection2区别
文章目录 1 总体介绍2 Landsat Collection 13 Landsat Collection 23.1 Collection 2 Level-1产品3.2 Collection 2 Level-2产品参考资料1 总体介绍 Landsat卫星的产品数据每经过几年就会有一次改进,主要改进几何校正精度和辐射纠正精度。而且NASA/USGS每次更新产品都会把存档…...
socket() failed (24: Too many open files) while connecting to upstream, client
一、这个错误通常是因为文件句柄数目超过系统限制导致的。要解决这个问题,您可以尝试以下几个步骤: 调整系统文件句柄限制:您可以通过修改/etc/security/limits.conf文件中的nofile参数来增加系统文件句柄的最大数目。将nofile的值增加到更高…...
认识单链表
-之前我们学过储存数据的一种表——顺序表,那么为什么还有链表呢 首先我们回顾一下顺序表 顺序表是物理地址连续的一段内存空间(数组),我们通过动态内存开辟的, 那么: 顺序表也有自己的一些优点,…...
免费ppt模板下载百度云/seo关键词优化方法
>>回到总目录<< 为了不辜负已经订阅了专栏的同学们的信任,所以本专栏不会有任何的优惠活动。 另外,当订阅人数每次达到 2 n ( n > 2 ) 2^n(n>2) 2...
web网站开发实训总结/品牌推广的概念
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/81158566 定义问题 外部排序指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存&#x…...
做cpa用什么网站/网站百度权重
在使用Ueditor时,如要简化工具栏上的按钮,可以修改配置项的方法: 1. 方法一:修改 ueditor.config.js 里面的 toolbars 2. 方法二:实例化编辑器的时候传入 toolbars 参数 我一般用第二种方法, <script sr…...
青县做网站/推广运营是什么工作
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 jmeter 性能测试数据…...
烟台网站建设推荐企汇互联见效付款/在线网站建设平台
文章目录题目原文Input Specification:Output Specification:Sample Input:Sample Output:题目大意:强烈推荐,刷PTA的朋友都认识一下柳神–PTA解法大佬本文由参考于柳神博客写成 柳神的CSDN博客,这个可以搜索文章 柳神的个人博客,这个没有广告,但是不能搜索 PS 题目原文 T…...
响应式网站怎么写/网站推广要点
// socket已经连接成功 var socketOpen false // socket已经调用关闭function var socketClose false // socket发送的消息队列 var socketMsgQueue [] // 判断心跳变量 var heart // 心跳失败次数 var heartBeatFailCount 0 // 终止心跳 var heartBeatTimeOut null; //…...