TCP服务器的演变过程:揭秘使用多线程实现一对多的TCP服务器

使用多线程实现一对多的TCP服务器

  • 一、前言
  • 二、新增使用的API
    • 2.1、pthread_create() 函数
    • 2.2、pthread_exit()函数
  • 三、实现步骤
  • 四、完整代码
  • 五、TCP客户端
    • 5.1、自己实现一个TCP客户端
    • 5.2、Windows下可以使用NetAssist的网络助手工具
  • 小结

一、前言

手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。

为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。本节在上一章节的基础上,添加多线程,为每个新接入的客户端分配线程,实现一个服务器程序处理多个客户端连接。

二、新增使用的API

2.1、pthread_create() 函数

pthread_create函数原型:

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,   			//新创建的线程ID指向的内存单元。const pthread_attr_t *restrict attr,		//线程属性void *(*start_rtn)(void *), 			//线程函数的地址void *restrict arg 				//线程函数所需的参数);

2.2、pthread_exit()函数

函数pthread_exit()终止调用线程,并通过retval返回一个值,该值(如果线程是可连接的)可用于调用pthread_join()的同一进程中的另一个线程。

pthread_cleanup_push()建立的任何尚未弹出的清理处理程序都会弹出(按与推送顺序相反的顺序)并执行。如果线程有任何特定于线程的数据,那么在执行清理处理程序之后,将以未指定的顺序调用相应的析构函数。

当线程终止时,进程共享资源(例如,互斥锁、条件变量、信号量和文件描述符)不会被释放,并且使用atexit()注册的函数也不会被调用。

在进程中的最后一个线程终止后,进程终止为调用atexit(),出口状态为零;因此,进程共享资源被释放,并且使用atexit()注册的函数被调用。

函数原型:

#include <pthread.h>void pthread_exit(void *retval);

三、实现步骤

使用多线程方案,来一个连接请求则创建一个线程。
在这里插入图片描述

(1)创建socket。

int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));return SOCKET_CREATE_FAILED;
}

(2)绑定地址。

struct sockaddr_in server;
memset(&server,0,sizeof(server));server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_BIND_FAILED;
}

(3)设置监听。

if(-1==listen(listenfd,BLOCK_SIZE)){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_LISTEN_FAILED;
}

(4)接收连接。

struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_ACCEPT_FAILED;
}

(5)为每个连接创建线程。

pthread_t thread;
if(0!=pthread_create(&thread,NULL,routine,&clientfd))
{printf("pthread_create failed");}

(6)在线程函数里面接收数据。

char buf[BUFFER_LENGTH]={0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {printf("connection dropped\n");}
printf("recv --> %s\n",buf);

(7)在线程函数里面发送数据。

if(-1==send(clientfd,buf,ret,0))
{printf("errno = %d, %s\n",errno,strerror(errno));
}

(8)关闭文件描述符。

close(listenfd);

四、完整代码

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>#include <errno.h>
#include <string.h>
#include <unistd.h>#include <pthread.h>#define LISTEN_PORT     8888
#define BLOCK_SIZE      10
#define BUFFER_LENGTH   1024enum ERROR_CODE{SOCKET_CREATE_FAILED=-1,SOCKET_BIND_FAILED=-2,SOCKET_LISTEN_FAILED=-3,SOCKET_ACCEPT_FAILED=-4
};void *routine(void *arg)
{int clientfd=*((int*)arg);while(1){// 5.char buf[BUFFER_LENGTH]={0};int ret=recv(clientfd,buf,BUFFER_LENGTH,0);if(ret==0) {printf("connection dropped\n");break;}printf("fd=%d recv --> %s\n",clientfd,buf);send(clientfd,buf,ret,0);}close(clientfd);
}int main(int argc,char **argv)
{// 1.int listenfd=socket(AF_INET,SOCK_STREAM,0);if(listenfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));return SOCKET_CREATE_FAILED;}// 2.struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_addr.s_addr=htonl(INADDR_ANY);server.sin_port=htons(LISTEN_PORT);if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_BIND_FAILED;}// 3.if(-1==listen(listenfd,BLOCK_SIZE)){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_LISTEN_FAILED;}printf("listen port: %d\n",LISTEN_PORT);struct sockaddr_in client;socklen_t len=sizeof(client);int clientfd=-1;while(1){// 4.memset(&client,0,sizeof(client));clientfd=accept(listenfd,(struct sockaddr*)&client,&len);if(clientfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));continue;}printf("accept successdul, fd = %d\n",clientfd);pthread_t thread;if(0!=pthread_create(&thread,NULL,routine,&clientfd)){printf("pthread_create failed");break;}}close(listenfd);return 0;
}

编译:

gcc -o server server.c -lpthread

注意,由于使用了多线程,在编译时要加上 -lpthread 连接多线程库。

五、TCP客户端

5.1、自己实现一个TCP客户端

自己实现一个TCP客户端连接TCP服务器的代码,主要是连接服务器,发送数据、接收数据功能:

#include <stdio.h>
#include <sys/socket.h>#include <netinet/in.h>
#include <arpa/inet.h>#include <errno.h>
#include <string.h>#include <unistd.h>
#include <stdlib.h>#define BUFFER_LENGTH   1024enum ERROR_CODE{SOCKET_CREATE_FAILED=-1,SOCKET_CONN_FAILED=-2,SOCKET_LISTEN_FAILED=-3,SOCKET_ACCEPT_FAILED=-4
};int main(int argc,char** argv)
{if(argc<3){printf("Please enter the server IP and port.");return 0;}printf("connect to %s, port=%s\n",argv[1],argv[2]);int connfd=socket(AF_INET,SOCK_STREAM,0);if(connfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));return SOCKET_CREATE_FAILED;}struct sockaddr_in serv;serv.sin_family=AF_INET;serv.sin_addr.s_addr=inet_addr(argv[1]);serv.sin_port=htons(atoi(argv[2]));socklen_t len=sizeof(serv);int rwfd=connect(connfd,(struct sockaddr*)&serv,len);if(rwfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));close(rwfd);return SOCKET_CONN_FAILED;}int ret=1;while(ret>0){char buf[BUFFER_LENGTH]={0};printf("Please enter the string to send:\n");scanf("%s",buf);send(connfd,buf,strlen(buf),0);memset(buf,0,BUFFER_LENGTH);printf("recv:\n");ret=recv(connfd,buf,BUFFER_LENGTH,0);printf("%s\n",buf);}close(rwfd);return 0;
}

编译:

gcc -o client client.c

5.2、Windows下可以使用NetAssist的网络助手工具

在这里插入图片描述

下载地址:http://old.tpyboard.com/downloads/NetAssist.exe

小结

这里基于上一个章节的内容进行了升级,由原来的一对一连接通信升级为一对多连接通信,可以接受多个客户端同时连接,并为每个连接分配一个线程。

但是,使用多线程会有一个瓶颈,受CPU的核心数和内存大小线程,一台机器可以并发的线程数量是有限的,内存大小也是有限的;这个模式下1024个线程基本就是一台服务器机器的极限。

除了可以使用多线程实现一对多发服务外,还可以使用多进程来实现这个功能。这个我们将在下一个章节进行实现。

在这里插入图片描述

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

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

相关文章

h5网站开发-页面底部 鼠标悬停显示二维码

一、实现效果 1.鼠标悬停到图标时&#xff0c;显示相应的二维码 2.二维码盒子的宽度是自适应的&#xff08;可以根据数量自动的撑开&#xff0c;并且居中显示&#xff09; 二、代码&#xff1a; <!DOCTYPE html> <html><head><meta http-equiv"Cont…

湖北省工程类助理工程师申报评审通过不是难事

湖北省工程类助理工程师申报评审通过不是难事 想要初级职称/助理工程师工程类&#xff0c;建筑施工、土木工程、市政、路桥、水利水电、机电、园林、测绘等一系列建筑类的初级职称。12月份交资料&#xff0c;春节前可以评审出来。 初级职称申报周期-一个月左右 一般初级职称申…

渗透测试和漏洞扫描有什么区别

渗透测试和漏洞扫描是网络安全领域中非常重要的两种技术手段&#xff0c;它们都可以帮助组织或企业发现和修复系统中的漏洞和弱点。然而&#xff0c;这两种技术手段在目的、深度、方法和时间和成本等方面存在显著的区别。 首先我们来了解下渗透测试和漏洞扫描分别是什么&#x…

家政上门服务系统|上门服务系统让家政服务更便捷

家政上门服务系统搭建的目的是为了让用户在家政服务的过程中能够更加轻松、便捷地完成各项服务需求。我们的系统集成了多项先进功能&#xff0c;使得用户无需再花费时间和精力去寻找合适的服务员工。通过系统&#xff0c;用户只需在手机或者电脑上输入相关需求&#xff0c;系统…

C# 创建MVC项目+Layui框架

目录 1、创建MVC 2、下载Layui 3、修改模板页 3.1 修改_Layout.cshtml页面 3.2创建侧边导航-BaseController控制器 3.3将控制器原本引用的 Controller改为BaseController 1、创建MVC 2、下载Layui 地址&#xff1a;Layui - 极简模块化前端 UI 组件库(官方文档) 将文件…

数智金融技术峰会|数新网络受邀分享《金融信创湖仓一体数据平台架构实践》,敬请期待

12月23日&#xff0c;数新网络参加DataFunSummit 2023&#xff1a;数智金融技术峰会。会上&#xff0c;数新CTO原攀峰将为大家带来《金融信创湖仓一体数据平台架构实践》 主题分享。 本次峰会由DataFun联合火山引擎、蓝驰等知名企业举办&#xff0c;将共同为大家带来一场数智金…

uni-app学习记录

uni-app官网学习记录 uni-app注意点记录 页面跳转注意事项 navigateTo, redirectTo 只能打开非 tabBar 页面。switchTab 只能打开 tabBar 页面。reLaunch 可以打开任意页面。不能在首页 onReady 之前进行页面跳转。 页面通讯 // 发起页面uni.$emit(update,{msg:页面更新})//…

天锐绿盾数据安全加密系统

天锐绿盾数据安全加密系统是一款全面、高效、易用的数据安全防护产品&#xff0c;它以数据防泄密为核心&#xff0c;提供了一系列完善的数据安全保护措施。 PC访问地址&#xff1a;www.drhchina.com 该系统主要针对企业局域网环境&#xff0c;对所有终端进行规范化管控&#x…

Rust学习:HelloWorld

Rust学习&#xff1a;HelloWorld HelloWorldRust语言简介主要特点先看程序分析程序 HelloWorld Rust语言简介 Rust是一种系统编程语言&#xff0c;旨在提供内存安全、并发性和性能。它由Mozilla Research开发&#xff0c;旨在解决C和C语言中的一些关键问题&#xff0c;特别是…

飞天使-k8s知识点5-kubernetes基础名词扫盲

文章目录 deploymentspodNodeserviceskubectl 实现应用伸缩kubectl 实现滚动更新kubernetes架构 deployments 中文文档 http://docs.kubernetes.org.cn/251.htmldeployment是用来创建和更新应用的&#xff0c;master 会负责将创建好的应用实例调度到集群中的各个节点 应用实例…

谷氨酰胺转氨酶活性分析试剂盒(Transglutaminase Activity Assay Kit)

谷氨酰胺转胺酶 谷氨酰胺转胺酶&#xff08;TGs&#xff09;是一种钙依赖性酶&#xff0c;参与蛋白质或肽的翻译后修饰以及如凝血、凋亡、细胞分化和细胞外基质稳定等各种细胞和生物过程。人谷氨酰胺转胺酶家族由8个催化活性组分TG1-7和XIIIa因子&#xff0c;以及一个缺乏活性…

【算法刷题】Day22

文章目录 1. 按摩师题干&#xff1a;算法原理&#xff1a;&#xff08;dp&#xff09;1. 状态表示&#xff1a;2. 状态转移方程3. 初始化4. 填表顺序5. 返回值 代码&#xff1a; 2. 寻找数组的中心下标题干&#xff1a;算法原理&#xff1a;&#xff08;前缀和&#xff09;代码…