当前位置: 首页 > 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;首页&…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG

TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码&#xff1a;HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...

6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础

第三周 Day 3 &#x1f3af; 今日目标 理解类&#xff08;class&#xff09;和对象&#xff08;object&#xff09;的关系学会定义类的属性、方法和构造函数&#xff08;init&#xff09;掌握对象的创建与使用初识封装、继承和多态的基本概念&#xff08;预告&#xff09; &a…...