C语言基于TCP的多线程服务器

核心思想:

1 在无限循环中 accpet()后 创建线程

2 预防多线程下的数据竞态:

accept()返回的client_sockfd 是否可以直接填入pthread_create()作为创建线程的参数?

我们观察到 while(1)中并没有阻塞的函数,假设accept()的速度足够快

他会不断地更新client_sockfd的值,而传递给pthread_create()是这个值的地址

也就是说 线程来没来得及启动(没获得cpu时间片) client_sockfd值就被更新了

我们的本意是用每个accept()返回的值,创建一条线程,很显然行为不符合预期

3 解决方案:

为了解决这个问题,最佳做法是为每个接受的连接分配一个新的内存空间来存储它的client_sockfd,并将这块内存的指针传递给新创建的线程。这样,每个线程都有自己独立的client_sockfd副本,不会受到主线程中client_sockfd值改变的影响。

4 扩展:

用同样的思想 扩展传递参数结构体

实现功能:

1 tcp多线程服务器

2 使用信号量计数的计数器

3 测试用客户端

运行注意事项:

1 先开启服务器 

2 根据实际情况修改ip地址及端口

3 手动运行客户端 可以无限次开启观察服务器现象

4 适用于unix-like 且 安装了 gnu_c 的系统 其他酌情改装

服务端:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
// 服务器地址
#define SERVER_IP "192.168.142.132"
// 服务器端口
#define SERVER_PORT 50010
// 给线程执行函数传递的参数
struct thread_args
{// 文件描述符int sockfd;// 端口号uint16_t port;// posix信号量sem_t *sem;
};
// 线程执行函数
void *start_routine(void *p)
{// 类型转换struct thread_args ta = *((struct thread_args *)p);// 赋值局部变量sem_t *sem = ta.sem;u_int16_t port = ta.port;int client_sockfd = ta.sockfd;ssize_t send_bytes, recv_bytes;char send_buf[1024] = "How can I help you today ?";char recv_buf[1024] = {0};// 连接数+1sem_post(sem);int sval;// 获取连接数并打印sem_getvalue(sem, &sval);printf("V [+] %u connected : %d\n", port, sval);// 接收数据recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes == -1){perror("recv");}if (recv_bytes == 0){printf("close by peer\n");}if (recv_bytes > 0){printf("Message : %s\n", recv_buf);}// 处理数据sleep(9);// 服务器响应send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}// 关闭socketif (close(client_sockfd) == -1){perror("close");}// 连接 -1sem_wait(sem);printf("P [-] %u disconnected \n", port);// 释放之前由malloc分配的指针free(p);// 线程退出pthread_exit(NULL);
}
int main()
{int server_sockfd, client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);socklen_t server_sockaddr_len = sizeof(server_sockaddr);pthread_t tid;// 信号量 是否存在都先卸载sem_unlink("/sem1");// 新建信号量sem_t *sem = sem_open("/sem1", O_CREAT, 0700, 0);if (sem == SEM_FAILED){perror("sem_open");}// tcp标准流程 socketserver_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd == -1){perror("socket");}// 设置套接字选项:地址端口复用int optval = 1;if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1){perror("setsockopt");}// 绑定服务器地址,recv用server_sockaddr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_family = AF_INET;if ((bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len)) == -1){perror("bind");}// 第二个参数backlog:允许排队等待接受的连接数的最大值if ((listen(server_sockfd, 16)) == -1){perror("listen");}printf("listening on : %d\n", SERVER_PORT);// 因为在一个while循环中 主线程永远不会先行结束,设置分离是安全的while (1){// 接收连接并返回client_sockfdclient_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);if (client_sockfd == -1){perror("accept");continue;}// 为每个参数结构体分配独立内存,预防多线程下的数据竞态struct thread_args *ta_p = (struct thread_args *)malloc(sizeof(struct thread_args));if (ta_p == NULL){return -1;}// 填充ta_p->port = ntohs(client_sockaddr.sin_port);ta_p->sem = sem;ta_p->sockfd = client_sockfd;// 创建线程if (pthread_create(&tid, NULL, start_routine, ta_p)){perror("pthread_create");}// 设置分离if (pthread_detach(tid)){perror("pthread_detach");}}return 0;
}

客户端:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50010int main()
{int client_sockfd;struct sockaddr_in server_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));ssize_t send_bytes, recv_bytes;char send_buf[1024] = {"hello server !!!"};char recv_buf[1024] = {0};client_sockfd = socket(AF_INET, SOCK_STREAM, 0);inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_port = htons(SERVER_PORT);server_sockaddr.sin_family = AF_INET;connect(client_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr));send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);printf("%s\n", recv_buf);close(client_sockfd);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/775895.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis-03

MyBatis 1.联查 1.1 一对一 例如&#xff0c;有user表和address表&#xff0c;其中每一个用户可以有多个地址&#xff0c;每个地址只能对应一个用户 以address表为主表&#xff0c;user表为副表&#xff0c;通过address表联查user表的形式就是一对一 如何进行一对一查询&a…

Kali远程操纵win7

一.准备 1.介绍 攻击方&#xff1a;kali IPV4:192.168.92.133 被攻击方&#xff1a;win7 IPV4:192.168.92.130 2.使用永恒之蓝漏洞 (1.使用root权限 (2.进入msfconsole (3.添加rhosts (4.run进行一下 二.进行远程操作 1.获取用户名和密码 在cmd5查询 2.获取syste…

抓包工具charles修改请求和返回数据

数据篡改的主要使用场景&#xff1a; &#xff08;1&#xff09;mock场景&#xff0c;mock入参和返回值参数&#xff0c;实现mock测试 &#xff08;2&#xff09;安全测试&#xff0c;对于支付金额等比较重要的字段&#xff0c;可以修改请求参数来进行安全测试 1.首先选择要…

[数据集][目标检测]道路交通事故检测数据集VOC+YOLO格式11819张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;11819 标注数量(xml文件个数)&#xff1a;11819 标注数量(txt文件个数)&#xff1a;11819 标…

Qt中QIcon图标设置(标题、菜单栏、工具栏、状态栏图标)

1 exe程序图标概述 在 Windows 操作系统中&#xff0c;程序图标一般会涉及三个地方&#xff1b; &#xff08;1&#xff09; 可执行程序&#xff08;以及对应的快捷方式&#xff09;的图标 &#xff08;2&#xff09; 程序界面标题栏图标 &#xff08;3&#xff09;程序在任务…

[激光原理与应用-77]:基于激光器加工板卡的二次开发软件的系统软硬件架构

目录 一、1个板卡、1个激光器、1个振镜的应用架构、1个工位 &#xff08;1&#xff09;PLC &#xff08;2&#xff09;MES &#xff08;3&#xff09;加工板卡 &#xff08;4&#xff09;激光加工板卡与激光器之间的转接卡 &#xff08;5&#xff09;DB25、DB15 &#x…

Typecho如何去掉/隐藏index.php

Typecho后台设置永久链接后&#xff0c;会在域名后加上index.php&#xff0c;很多人都接受不了。例如如下网址&#xff1a;https://www.jichun29.cn/index.php/archives/37/&#xff0c;但我们希望最终的形式是这样&#xff1a;https://www.jichun29.cn/archives/37.html。那么…

图神经网络实战(6)——使用PyTorch构建图神经网络

图神经网络实战&#xff08;6&#xff09;——使用PyTorch构建图神经网络 0. 前言1. 传统机器学习与人工智能2. 人工神经网络基础2.1 人工神经网络组成2.2 神经网络的训练 3. 图神经网络4. 使用香草神经网络执行节点分类4.1 数据集构建4.2 模型构建4.3 模型训练 5. 实现香草图神…

大话设计模式之装饰模式

装饰模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许向现有对象动态地添加新功能&#xff0c;同时又不改变其结构。装饰模式通过将对象放入包装器中来实现&#xff0c;在包装器中可以动态地添加功能。 在装饰模式中&#xff0c;通常会有…

【教程】JavaScript代码混淆及优化

摘要 本文将介绍常见的JavaScript代码混淆技术&#xff0c;包括字符串转十六进制、Unicode编码、Base64加密、数值加密、数组混淆、花指令、逗号表达式、控制流程平坦化和eval执行。通过对这些混淆技术的理解和应用&#xff0c;可以提高代码的安全性和保护知识产权。 引言 随…

(4)(4.3) Kogger Sonar

文章目录 前言 1 推荐硬件 2 配置回声探测仪模块 3 连接ArduPilot硬件 4 参数说明 前言 KOGGER 声纳(KOGGER Sonar)是一款结构紧凑、成本低廉的水下回声测深仪模块&#xff0c;带有 UART 接口&#xff0c;电源电压为 5-14v。 1 推荐硬件 CP210x USB->UART 转换器和安装…

C++第十三弹---内存管理(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、operator new与operator delete函数 1.1、operator new与operator delete函数 2、new和delete的实现原理 2.1、内置类型 2.2、自定义类型 …

electron vite vue打包

1. 安装所需依赖 electronconcurrently&#xff1a;阻塞运行多个命令&#xff0c;-k参数用来清除其它已经存在或者挂掉的进程wait-on&#xff1a;等待资源&#xff0c;此处用来等待url可访问cross-env: 该库让开发者只需要注重环境变量的设置&#xff0c;而无需担心平台设置el…

SpringBoot单元测试深入剖

SpringBoot作为一种流行的Java框架&#xff0c;其单元测试的重要性不言而喻。在这篇博客中&#xff0c;我们将深入剖析SpringBoot单元测试的底层原理。 单元测试的概念 单元测试是软件开发过程中的一个重要环节&#xff0c;它是对软件中的最小可测试单元进行检查和验证。对于…

开源项目ChatGPT-Next-Web的容器化部署(二)-- jenkins CI构建并推送镜像

一、背景 接着上文已制作好了Dockerfile&#xff0c;接下来就是docker build/tag/push等一系列操作了。 不过在这之前&#xff0c;你还必须在jenkins等CI工具中&#xff0c;拉取源码&#xff0c;然后build构建应用。 因为本文的重点不是讲述jenkins ci工具&#xff0c;所以只…

Java习题中 反转字符串 替换空格 翻转字符串里的单词 kmp算法

关于 反转字符串 空间问题&#xff1a;c的字符串可以原地修改&#xff0c;Java和python语言的字符串不可以&#xff0c;需要额外空间。这里用到的额外空间是没关系的&#xff0c;不影响原地修改的做法。 s[i] ^ s[j]; s[j] ^ s[i]; s[i] ^ s[j]; 这段代码是用来交换数组s中第…

亮数据——让你的IP走出去,让价值返回来

亮数据——让你的IP走出去&#xff0c;让价值返回来 前言跨境电商最最最大的痛点——让IP走出去超级代理服务器加速网络免费的代理管理软件亮数据解决痛点亮数据优势介绍亮数据浏览器的使用示例总结 前言 当前社会信息的价值是不可想象的&#xff0c;今天在亮数据中看到了个【…

Ipython与Jupyter之间的关系

IPython 和 Jupyter 之间的关系可以从它们的历史和目标中得到很好的解释。IPython&#xff08;Interactive Python&#xff09;最初是由 Fernando Prez 于 2001 年创建的&#xff0c;旨在提升 Python 的交互式计算体验。它提供了一个强大的交互式 Python shell 和一个面向高效计…

Linux C++ 007-指针

Linux C 007-指针 本节关键字&#xff1a;Linux、C、指针、函数指针 相关库函数&#xff1a; 基本概念 指针的作用&#xff1a;可以通过指针间接访问内存。内存编号是从0开始记录的&#xff0c;一般用于十六进制数字表示&#xff0c;可以利用指针变量保存地址。 定义和使用…

『VUE』03. 模板语法 使用js与插入html(详细图文注释)

目录 vscode终端中创建项目什么是模板语法?模板语法--js"变量"用法模板语法--插入html总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 vscode终端中创建项目 回顾一遍前面的cmd. 首先在vscode中打开一个新的空文件…