互联网编程之多线程/线程池TCP服务器端程序设计

news2024/11/16 21:42:00

目录

需求

多线程TCP服务器

线程池TCP服务器

测试

日志模块


需求

多线程TCP服务器(30分):

设计编写一个TCP服务器端程序,需使用多线程处理客户端的连接请求。客户端与服务器端之间的通信内容,以及服务器端的处理功能等可自由设计拓展,无特别限制和要求。

线程池TCP服务器(30分):

设计编写一个TCP服务器端程序,需使用线程池处理客户端的连接请求。客户端与服务器端之间的通信内容,以及服务器端的处理功能等可自由设计拓展,无特别限制和要求,但应与第1项要求中的服务器功能一致,便于对比分析。

比较分析不同编程技术对服务器性能的影响(20分):

自由编写客户端程序和设计测试方式,对1和2中的服务器端程序进行测试,分析比较两个服务器的并发处理能力。

设计编写可重用的服务器日志程序模块,日志记录的内容和日志存储方式可自定(比如可以记录客户端的连接时间、客户端IP等,日志存储为.TXT或.log文件等),分别在1和2的服务器程序中调用该日志程序模块,使多线程TCP服务器和线程池TCP服务器都具备日志功能,注意线程之间的同步操作处理。(20分)

多线程TCP服务器

这段代码是一个基于Java的多线程服务器实现,用于接收客户端的连接并处理其发送的消息。

  1. 首先,在MultithreadingServer类的main方法中:

    • 创建了一个ServerSocket对象,并指定它监听的端口号为8888,同时设置最大连接数量为10000。
    • 进入一个无限循环,用于持续接受客户端的连接请求。
    • 每次循环,当有客户端连接时,创建一个新的MultiThread实例,并传入对应的Socket对象。
    • 同时,创建一个Logger实例,记录连接的相关信息,包括客户端的IP地址、连接时间和日志文件名。
  2. MultiThread类中:

    • 继承了Thread类,并重写了run方法。
    • run方法中,通过BufferedReaderSocket的输入流获取一个字符输入流,并通过InputStreamReader将其转换为字符流,然后读取客户端发送的数据。
    • 使用一个循环来连续读取,直到达到输入流的末尾(客户端关闭连接)为止。
    • 在每次循环中,打印接收到的消息到标准输出。
    • 最后,关闭输入流和Socket连接。

整体而言,这段代码实现了一个简单的多线程服务器,能够接收并处理客户端的连接请求,以及读取和输出客户端发送的消息。日志记录部分使用了自定义的Logger类,但其具体实现不在这段代码中显示。需要注意的是,异常处理方面可能需要根据实际需求进行补充和调整。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

public class MultithreadingServer {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8888, 10000);
            while (true) {
                Socket client = serverSocket.accept();
                new MultiThread(client).start();
                new Logger(client.getInetAddress().getHostAddress(), new Date(), "LogMultithreadingServer.txt");
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

class MultiThread extends Thread {
    private Socket socket = null;

    public MultiThread(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String message = null;
            while ((message = input.readLine()) != null) {
                System.out.println(message);
            }
            input.close();
            socket.close();
        } catch (IOException error) {
            error.printStackTrace();
        }
    }
}

线程池TCP服务器

这段代码是一个使用线程池的多线程服务器实现,与前面的代码相比,在并发处理客户端连接方面进行了改进。

  1. ThreadPoolServer类的main方法中:

    • 创建了一个具有200个线程的固定大小线程池ExecutorService
    • 创建了一个ServerSocket对象,并指定它监听的端口号为9999,同时设置最大连接数量为10000。
    • 进入一个无限循环,用于持续接受客户端的连接请求。
    • 每次循环,当有客户端连接时,将一个新的TheadPoolTask任务提交给线程池进行执行。
    • 同时,创建一个Logger实例,记录连接的相关信息,包括客户端的IP地址、连接时间和日志文件名。
  2. TheadPoolTask类中:

    • 实现了Runnable接口,并重写了run方法。
    • run方法中,通过BufferedReaderSocket的输入流获取一个字符输入流,并通过InputStreamReader将其转换为字符流,然后读取客户端发送的数据。
    • 使用一个循环来连续读取,直到达到输入流的末尾(客户端关闭连接)为止。
    • 在每次循环中,打印接收到的消息到标准输出。
    • 最后,关闭输入流和Socket连接。

整体而言,这段代码与前一段代码类似,不同之处在于使用了线程池来管理线程资源,提高了并发处理能力。通过将任务提交给线程池执行,可以控制并发线程数,并重复利用线程,避免频繁创建和销毁线程带来的开销。需要注意的是,异常处理方面可能需要根据实际需求进行补充和调整。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolServer {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(200);
        try{
            ServerSocket serverSocket=new ServerSocket(9999,10000);
            while(true){
                Socket client=serverSocket.accept();
                executorService.execute(new TheadPoolTask(client));
                new Logger(client.getInetAddress().getHostAddress(),new Date(),"LogThreadPoolServer.txt");
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }finally {
            executorService.shutdown();
        }
    }
}
class TheadPoolTask implements Runnable{
    private Socket socket=null;
    public TheadPoolTask(Socket socket){
        this.socket=socket;
    }
    public void run(){
        try{
            BufferedReader input=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String message=null;
            while((message=input.readLine())!=null){
                System.out.println(message);
            }
            input.close();
            socket.close();

        }catch (IOException error){
            error.printStackTrace();
        }
    }
}

测试

编写一个压力测试程序,测试多线程服务器和线程池服务器在高并发时的表现,实现run()方法,发起连接请求,为了保证多线程并发访问long类型变量时的线程安全性,使用线程安全的AtomicLong类来记录服务器响应的时间,如图7所示,附件已含源代码。

图7

主程序同时向两个服务器发起多个连接,连接规模从1000个连接请求开始一直增加到10000个,通过在短时间内发起大量连接请求来对服务器进行压力测试,如图8所示。

图8

测试过程数据如图9所示。

图9

分析两个服务器的表现情况,如图10所示,可见在处理大量短任务(如处理网络请求)的情况下,使用线程池可以避免频繁地创建、销毁线程所带来的开销,因此会更快一些。

图10

 

这段代码是一个简单的测试服务器和客户端的程序。

  1. TestServerClient类的main方法中:

    • 通过循环来控制不同规模(power)的测试。
    • 在每个测试规模下,通过嵌套循环启动一定数量(2000)的测试任务。
    • 每个测试任务使用TestTask类创建一个线程,构造函数传入不同的端口号(9999或8888),然后调用run方法运行测试任务。
    • 在每次测试任务完成后,将消耗的时间输出到控制台。
  2. TestTask类中:

    • 定义了一个port变量,表示客户端连接的目标端口。
    • 声明了两个静态的AtomicLong对象timePooltimeMulti,用于记录线程池和多线程方式的测试消耗时间。
    • 在构造函数中接收一个端口号,并将其赋值给port变量。
    • run方法实现了客户端的测试逻辑:
      • 创建一个空的Socket对象。
      • 构建InetSocketAddress对象,指定本地主机地址和目标端口。
      • 记录当前时间为起始时间。
      • 调用socket.connect方法与服务器建立连接,等待连接完成。
      • 关闭socket对象。
      • 记录当前时间为结束时间。
      • 根据不同的端口号,将测试消耗时间累加到相应的AtomicLong对象中。

该程序的主要目的是通过多次连接服务器的测试来比较线程池和多线程方式的性能消耗。它会启动一定数量的测试任务,并分别记录两种方式的测试消耗时间。在每次测试任务完成后,将消耗时间输出到控制台。通过对不同规模测试结果的比较,可以初步评估线程池和多线程方式的性能表现。

import java.io.IOException;
import java.net.*;
import java.util.concurrent.atomic.AtomicLong;

public class TestServerClient {
    public static void main(String[] args) {
        for (int power = 1; power <= 10; power++) {
            int scale = 2000*power ;
            for (int i = 0; i < 2000; i++) {
                if(i%2==0)
                new TestTask(9999).run();
                else new TestTask(8888).run();
            }
            System.out.printf("线程池:规模为%d消耗时间%d毫秒\n", scale/2, TestTask.timePool.get());
            System.out.printf("多线程:规模为%d消耗时间%d毫秒\n", scale/2, TestTask.timeMulti.get());
        }
    }
}

class TestTask{
    private final int port;
    public static AtomicLong timePool =new AtomicLong();
    public static AtomicLong timeMulti=new AtomicLong();

    public TestTask(int port) {
        this.port = port;
    }

    public void run() {
        try {
            Socket socket = new Socket();
            SocketAddress socketAddress=new InetSocketAddress(InetAddress.getLocalHost(), port);
            long start=System.currentTimeMillis();
            socket.connect(socketAddress);
            while(!socket.isConnected()){}
            socket.close();
            long end=System.currentTimeMillis();
            if(port==9999)
            timePool.addAndGet(end-start);
            else timeMulti.addAndGet(end-start);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
}

日志模块

这段代码是一个简单的日志记录器类。它具有以下功能:

  1. 构造函数:接受一个IP地址、日期和文件路径作为参数,并生成一条以IP地址和日期为内容的日志信息。

  2. keep() 方法:该方法使用了 synchronized 关键字,以确保在多线程环境下只有一个线程可以访问该方法。在方法内部,它创建一个 BufferedWriter 对象,并将日志内容写入指定的文件中。

总体来说,这个代码实现了一个基本的日志记录功能,将用户登录的 IP 地址和日期写入指定的文件中。

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;

public class Logger {
    private final String log;
    private final String filePath;
    public Logger(String IP, Date date,String filePath){
        log=IP+" login at "+date.toString()+'\n';
        this.filePath=filePath;
        keep();
    }
    private synchronized void keep(){
        try {
            BufferedWriter bufferedWriter=new BufferedWriter(new FileWriter(filePath,true));
            bufferedWriter.write(log);
            bufferedWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

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

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

相关文章

C语言进阶---自定类型详解(结构体+枚举+联合)

结构体 1、结构体类型的声明 1.1、结构的基础知识 结构是一些值得集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 1.2、结构体类型的声明 struct tag {member-list; }variable-list;//写法一&#xff1a; struct Stu {char name[20];int age;…

【数据结构与算法】约瑟夫环(C/C++)

实践要求 1. 问题描述 约瑟夫问题的一种描述是&#xff1a;编号为1,2,…,n的n个人按顺时针方向围坐一圈&#xff0c;每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m&#xff0c;从第一个人开始。按顺时针方向自1开始顺序报数&#xff0c;报到m时停止报数。报…

docker镜像fauria/vsftpd dockerfile解析(ENV命令,**占位符**)dockerfile命令、dockerfile指令

文章目录 fauria/vsftpddockerfile原始文件dockerfile解析 fauria/vsftpd fauria/vsftpd是一个由Docker Hub用户"fauria"创建的Docker镜像。这个镜像是基于CentOS 7构建的&#xff0c;包含了vsftpd&#xff08;Very Secure FTP Daemon&#xff09;服务&#xff0c;并…

NASA网站曝严重漏洞,或将沦为黑客钓鱼网站?

美国国家航空航天局&#xff08;NASA&#xff09;天体生物学专用网站存在一个严重的安全漏洞&#xff0c;可能通过伪装带有NASA名称的危险URL来诱骗用户访问恶意网站。 太空旅行无疑是危险的。然而&#xff0c;在访问NASA网站的时候也有可能如此。Cybernews研究团队发现了一个N…

Scala之泛型详解

泛型用于指定类或方法可以接受任意类型参数&#xff0c;参数在实际使用时才被确定&#xff0c;泛型可以有效地增强程序的适用性&#xff0c;使用泛型可以使得类或方法具有更强的通用性。泛型的典型应用场景是集合及集合中的方法参数&#xff0c;可以说同 Java 一样&#xff0c;…

基于粒子群算法的无约束优化问题求解

基于粒子群算法的无约束优化问题求解 1 引言2 粒子群算法2.1 粒子群优化原理2.2 粒子群算法寻优策略与参数控制粒子群算法流程 3 粒子群算法求解无约束优化问题3.1 粒子群算法求解Sphere函数&#xff08;单峰测试函数&#xff09;3.2 Schwefels Problem 2.26&#xff08;多峰测…

chatgpt赋能python:Win7怎么安装Python?

Win7怎么安装Python&#xff1f; 如果你正在使用Windows 7操作系统&#xff0c;想要安装Python&#xff0c;那么你来对了地方。Python是一种利用广泛的编程语言&#xff0c;可用于开发Web应用程序、数据分析和科学计算、机器学习等各种领域。 在此篇文章中&#xff0c;我们会…

MATLAB matlab人脸识别源码+使用说明+操作说明内容清晰适合新手

程序运行界面&#xff1a; 部分代码&#xff1a; function varargout facerecg(varargin) % FACERECG MATLAB code for facerecg.fig % FACERECG, by itself, creates a new FACERECG or raises the existing % singleton*. % % H FACERECG returns the hand…

Revit中如何导入、导出明细表?

Revit中明细表的作用非常大&#xff0c;项目中的数据归类整理及统计都离不开它&#xff0c;今天给大家分享一下如何在Revit中进行明细表标准的导出及导入&#xff0c;减少在实际项目中的重复性工作。 1、首先在Revit中新建一个项目文件&#xff0c;在平面视图中随便画几条管道…

用git下载gitee上的项目资源

目录 用git下载gitee上的项目资源 用git 的clone 命令 然后到gitee上复制相关的下载地址&#xff1a; 粘贴到clone后面即可&#xff08;注意地址与clone之间有空格&#xff01;&#xff01;&#xff01;&#xff09; 运行结果&#xff1a; 用git下载gitee上的项目资源 用git…

学习vue2笔记

学习vue2笔记 文章目录 学习vue2笔记脚手架文件结构关于不同版本的Vuevue.config.js配置文件ref属性props配置项mixin(混入)插件scoped样式总结TodoList案例webStorage组件的自定义事件全局事件总线&#xff08;GlobalEventBus&#xff09;消息订阅与发布&#xff08;pubsub&am…

LeetCoda 打卡day53--动态规划之最长子序列

一个人的朝圣 — LeetCode打卡第52天 知识总结 Leetcode 1143. 最长公共子序列题目说明代码说明 Leetcode 53. 最大子数组和题目说明代码说明 Leetcode 1035. 不相交的线题目说明代码说明 知识总结 今天几道最长子序列的题目, 都可以用一个固定的模版完成. 理解其中递推公式的…

字典序最小回文串

字典序最小回文串 题目解读 给你一个由 小写英文字母 组成的字符串 s &#xff0c;你可以对其执行一些操作。在一步操作中&#xff0c;你可以用其他小写英文字母 替换 s 中的一个字符。 请你执行 尽可能少的操作 &#xff0c;使 s 变成一个 回文串 。如果执行 最少 操作次数…

DAY38——动态规划

步骤&#xff1a; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 题目一. 斐波那契数列 1. 确定dp数组以及下标的含义 dp[i]的定义为&#xff1a;第i个数的斐波那契数值是dp[i] 2. 确定递推公式 状态…

FFmpegFrameGrabber视频抽帧工具类

Bytedeco 通过视频链接进行关键帧抽取图片&#xff0c;利用FFmpegFrameGrabber对视频流进行抽帧处理。 一、引入POM依赖 <dependency><groupId>org.bytedeco</groupId><artifactId>javacv</artifactId><version>1.4.1</version><…

TCP 拥塞状态机演进

下面是 TCP 拥塞状态机&#xff1a; 但它只是冰山一角&#xff0c;这只是 loss-based 状态机&#xff0c;实现一个完全的 delay-based cc 就对不上这个状态机。 该状态机来自 RFC5681&#xff0c;源自 RFC2581&#xff0c;RFC2001&#xff0c;大概在 1990 年代&#xff0c;l…

10分钟内创意爆发,这些头脑风暴技巧让你IDEA满满

当初道叔刚入广告行业&#xff0c;与同事经历了一次困扰的头脑风暴&#xff0c;老板让他们想出一个大集团公司年会的主题口号。我们7-8个团队成员耗费了一个下午的时间&#xff0c;提出了几十个提议&#xff0c;但最终硬是没有一个能满足需求。许多人可能也有过道叔这样的经历。…

Js保留树型数据指定层级

自定义一个树型的数据 const tree [{value: 1, label: "1",children: [{value: 11, label: "1-1",children: [{value: 111, label: "1-1-1"}]}]}, {value: 2, label: "2"}]保留指定层级的方法 function keepNodesAtLevel(data, level…

vue源码阅读之Watcher类

我们上次分析vue源码讲的是收集依赖&#xff0c;数据变化之后我们把依赖收集到dep类中&#xff0c;通过这个管理器进行管理。 里面有一个subs数组&#xff0c;用来存放依赖&#xff0c;并且定义了几个实例方法用来依赖进行添加&#xff0c;删除&#xff0c;通过操作。 比如ad…

mysql基础5——mysql主从

文章目录 一、基本了解二、主从原理三、主从复制3.1 从无到有3.1.1 服务器初始化3.1.2 配置主库3.1.3 配置从库3.1.4 效果验证 3.2 从有到无3.2.1 主库全备&#xff0c;并同步到从库3.2.2 配置主库3.2.3 配置从库3.2.4 效果验证 四、数据库运维4.1 几个参数4.2 查看进程列表 一…