总结~~~

本文介绍了网络通信的基本概念及常用网络命令,详细解析了服务器与客户端通信的实现步骤,并展示了如何通过多线程技术实现服务器一对多的数据处理。

网络通信

.常用的网络命令 

ping——用来查看网络是否通畅,不能用来证明主机上是否开放了某个端口 

telnet——如果网络通畅,可以用telnet命令连接对方的端口,如果能连接上,证明对方端口打开。

netstat——打印出电脑与其他服务器建立的TCP连接或UDP连接信息。

 

 

.网络通信的基本原理 服务器与客户端的通信 主要实现方法

 第一步,创建服务器 ServerSocket server = new ServerSocket(端口号);

 第二步,等待其他客户机来连接 java.net.Socket client = server.accept(); 注:调用server.accept()方法会阻塞程序。 

 第三步,从Socket连接对象上调用方法得到输入输出流 java.net.Socket client = server.accept(); OutputStream out = client.getOutputStream(); InputStream ins = client.getInputStream();

 第四步,使用输入输出流对象进行数据的读写 String s="汉字charactor123\n\r"; byte[] data=s.getBytes();   out.write(data);   out.flush(); client.close(); 将上述代码整合就可以得到一个基础的通信程序。 但是这个服务器只能用来等待一个客户端的连接,一旦客户机连接上来收到服务器发出的一条消息后服务器就退出了。而事实上我们的服务器是一对多的持续处理数据。为了实现服务器的持续等待新的客户机接入,我们可以将accept方法放入一个循环中:

 第五步,设定服务器顺循环等待 while(true){     Socket client=server.accept();     } 

第六步,读取字符 通过in=ins.read()语句可以将一个字符读取到in变量中,要注意这个字符是原字符在服务器端先转化成字节,然后将字节信息通过信道传到客户端,客户端将字节转型成字符型得到的字符。每次只能读一个,为了能够循环读取服务器发来的字符,可以使用while语句。例如: while(in!=13){     in=ins.read();     System.out.println(in); 当服务器发来的不是回车时,客户机可以源源不断地接收服务器发来的字节。

 第七步,读取字符串 上一步中实现客户机了一个个字符的读取,但是在很多情况下,我们的信息不是以一个一个的字节为单位发送的,倘若我们要读取的是一整个字符串(一次性读取很多个字节),可以定义这样一个函数: private String readString(InputStream ins) throws Exception{     StringBuffer str=new StringBuffer();     char c=0;     while(c!=13){     int i=ins.read();     c=(char)i;     stb.append(c);     }     String inputS=stb.toString().trim();//这个函数用来去掉字符串尾部的空格     return inputS; 这样,当我们需要客户机接收一个字符串的时候,可以调用这个函数,例如:     String inputS=readString(ins);     while(!inputS.equals("bye")){         data=s.getBytes();         out.write(data);         out.flush();         inputS=readString(ins);     } 这段代码的意思是不断地通过readString函数读取客户端那边发来的字符串,如果字符串不为"bye"那么就不断循环读取。 通过上述的知识,一个可以进行字符串通信的服务器客户机就可以实现了。 但是现在的通信程序依然是一对一的,即一个服务器同一时刻只能跟一个客户端通信,而现实生活中的服务器如果一个时刻只能跟一个客户端通信显然太无效率。要实现一对多的服务,我们需要进行多线程服务器的改造。

 第八步,多线程服务器 其实只需要将每个客户端节点放入服务器的一个线程就可以,请参考以下代码 public class ServerThread extends Thread{     private Socket client;     //线程对象要处理的连接变量     private OutputStream out;  //输出流对象     public ServerThread(Socket sc){         //传入一个要处理的连接对象,即每个线程均含有一个主要的客户端        this.client = sc;     }     public void run(){        processChat(this.client);//对客户端的操作都写在这个函数里     } 然后在服务器创建客户端节点的时候把创建的节点传入一个新的线程并启动这个线程就可以了 ServerThread st=new ServerThread(client); st.start(); 这样,一个像模像样的多线程服务器就能实现了 

 

在这个过程中我们需要注意的问题有: 1.从服务器到客户端与从客户端到服务器发送与接收信息的方法是完全相同的,即只要建立了连接之后,知道了从服务器向客户端发送信息的方法,同样的原理就可以写出从客户端到服务器发送消息的代码。 2.如果按照上述提供的方法进行字符串传输时,不能正常传输汉字。因为每个汉字由两个字节组成,在发送数据时,我们把汉字化成了两个字节,接收后又把每个字节转换成了相应的字符,相当于把原来的一个汉字转换成了两个字符,显示出来的是乱码。比较好的解决办法是用DataInputSteamDataOutputStream把信息按照数据类型的方式发送接收与还原。但是底层的实现依然是通过字符的传递来实现的。 3.目前我们的通信程序只能传递字符串。如果改用DataInputStream DataOutputStream可以传送整数或其他基本数据类型。如何实现多种数字类型的传递?这需要我们设计自己的传送格式,例如在信息前事先发送信息的大小、信息的类型等信息,而接受的时候每次也先对接收到的头几位格式进行分析判断进行不同的操作。只要发送端与接收端信息格式一直保持一直,就能实现多种类型信息的传输。 4.我比较讨厌通信的主要原因是,通信是即画图板的保存、哈弗曼压缩后有一个跟位运算关系密切的课程。位运算很不直观,只要位对不齐或者长度出现问题就会出现各种错误。通信阶段极容易出现字符数量或者格式错误的情况,在文件传输的时候要慎重。 5.关于多人聊天。要用到队列哦,即将所有客户端的线程放到一个队列里,每当服务器收到一条消息,就将这条消息发给所有的客户端。 

 

下面是在学习中遇到的一些重点 1.对于阻塞状态  某些进入阻塞状态的方法只有等到特定动作发生后才能继续进行 这样会影响到后继方法的调用 所以对于会发生阻塞状态的代码 一般都要放进独立的线程中去执行 2.程序代码中的封装思路  封装是面向对象的一大特性但是对于如何封装 为何封装都需要在实践中自己体会    封装的典型思路:方法的设计要考虑以后变化的需求 3.内存泄露问题  当某些存放在队列中的线程退出后 并没有从保存它的队列中移除 所以在测试时往往不能体现出来  如果应用到某些大访问量的服务器 则很快就会导致内存溢出  所以要在程序中定义一个从队列中移除的方法

<!--EndFragment-->
03-15
<think>嗯,用户问的是在Linux或Unix系统中,sudo命令与波浪号(~)组合使用的功能。首先,我需要确定波浪号在命令行中的一般用途,以及当它与sudo一起使用时可能有什么不同。 我记得在Linux中,波浪号~通常代表当前用户的主目录。比如,cd ~会切换到用户的家目录。而如果后面跟用户名,比如~username,就代表该用户的主目录。不过,当使用sudo时,执行命令的用户权限可能会改变,这时候波浪号的解析可能会有变化。 接下来,我需要考虑sudo的执行环境。默认情况下,sudo可能会重置环境变量,或者保持某些变量不变,这取决于配置。例如,在sudo的配置中,是否设置了env_reset或env_keep等选项,这些会影响环境变量的保留情况,进而影响波浪号的解析。 然后,具体到用户的问题,sudo后面跟着命令中包含波浪号的情况。比如,sudo vim ~/file.txt,这里的波浪号是扩展为当前用户的主目录,还是切换为root用户的主目录呢?这时候可能需要看sudo的配置以及是否启用了某些选项,比如-H参数,它可以将HOME环境变量设置为目标用户(通常是root)的主目录。 另外,用户可能在使用sudo时遇到路径问题,特别是当他们试图访问自己主目录下的文件时,可能会因为权限问题或路径解析错误而导致操作失败。例如,如果使用sudo时波浪号被解析为root的主目录,而用户原本想操作的是自己的主目录,这时候就可能出现混淆。 还需要考虑不同的shell对波浪号的处理。有些shell可能在命令执行前就展开波浪号,而有些可能在sudo环境下处理方式不同。例如,如果使用sudo bash -c 'echo ~',这里的波浪号会根据bash的执行环境来解析,可能是root的主目录。 此外,是否有相关的sudo选项可以改变波浪号的行为,比如使用-i参数来模拟登录shell,这样波浪号会被正确解析为目标用户的主目录。例如,sudo -i command可能会让波浪号指向root的/home目录。 最后,用户可能需要具体的示例来说明不同情况下sudo与波浪号组合的行为差异,以及如何避免常见的错误。例如,明确使用绝对路径代替波浪号,或者在需要时调整sudo的参数以确保环境变量正确设置。 总结起来,回答需要涵盖以下几点: 1. 波浪号在普通情况和sudo下的不同解析方式。 2. sudo的环境变量处理对波浪号的影响。 3. 使用sudo的不同选项(如-H、-i)如何改变波浪号的行为。 4. 提供示例和解决方案,帮助用户正确使用。 5. 可能的常见错误及避免方法。 同时,根据用户提供的引用内容,引用[1]提到了sudoers文件的配置和visudo命令的使用,虽然和波浪号直接关联不大,但可能在涉及sudo权限配置时有间接联系。不过,用户的问题更侧重于波浪号的使用,因此可能需要更侧重于环境变量和路径解析方面的解释。</think>在Linux/Unix系统中,`sudo`命令与波浪号`~`的组合使用涉及路径解析的特殊性,其行为取决于环境变量和用户权限配置。以下是关键要点解析: ### 一、波浪号的默认解析规则 1. **普通用户的波浪号** `~`默认指向当前用户的主目录,如`/home/username`。例如: ```bash echo ~/Documents # 输出 /home/yourname/Documents ``` 2. **sudo环境下的波浪号** 当使用`sudo`时,默认**不会切换HOME环境变量**: ```bash sudo echo ~ # 仍显示当前用户的主目录,如/home/yourname sudo echo ~root # 显示root用户的主目录(通常是/root) ``` ### 二、通过参数控制环境变量 1. **-H参数(明确继承目标用户环境)** 强制将HOME设为目标用户(默认为root)的主目录: ```bash sudo -H bash -c 'echo ~' # 输出/root ``` 2. **-i参数(模拟登录Shell)** 完全加载目标用户的环境配置: ```bash sudo -i echo ~ # 输出/root ``` ### 三、路径解析的典型场景对比 | 命令格式 | 波浪号解析结果 | 是否访问成功 | |---------------------------|------------------------|----------------------------------| | `sudo vim ~/file` | 当前用户主目录 | 需root权限操作当前用户文件时失败 | | `sudo -H vim ~/file` | root用户主目录(/root) | 文件需存在于/root目录下 | | `sudo -u otheruser echo ~`| otheruser的主目录 | 需otheruser存在且权限允许 | ### 四、常见问题解决方案 1. **明确指定绝对路径** ```bash sudo cp /home/yourname/file /target # 替代sudo cp ~/file /target ``` 2. **临时切换用户环境** ```bash sudo -i -u mysql # 以mysql用户身份进入其主目录 ``` 3. **调试环境变量** ```bash sudo env | grep HOME # 查看实际生效的HOME变量[^1] ``` ### 五、配置影响说明 在`/etc/sudoers`中,以下配置会影响波浪号解析: - `env_reset`:控制是否重置环境变量(默认启用) - `env_keep = "HOME"`:保留原用户的HOME变量[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值