一、实验目的和要求
1.掌握TCP/IP体系结构中端口、套接字、TCP协议概念。理解什端口的范围划分、套接字的组成等概念,掌握netstat等网络命令的使用;
2.掌握ServerSocket和Socket的使用,包括TCP连接的建立、服务器接收客户端连接请求、创建输入/输出流的方法以及关闭套接字等,注意可能会出现的异常操作;
3.理解进程和线程的概念,掌握Java多线程技术实现的主要方法,掌握多线程服务器程序的开发。
二、实验内容
1. 仔细阅读下述代码,分析功能功能,完善程序,并运行调试。
(1)客户端连接服务器端
import java.io.* //引用输入与输出类库
import java.net.* //引用网络类库
public class exp_1_1{
public static void main(String [] args){
String hostName = "www.xupt.edu.cn";
int port = 80;
Socket cs = null;
try{
cs = new Socket (hostName, port);
System.out.println("连接"+hostName+"的端口"+port+"成功");
System.out.println("对方主机" + cs.getInetAddress() + ":对方端口" + cs.getPort());
System.out.println("本地主机" + cs.getLocalAddress() + ":本地端口" + cs.getLocalPort());
cs. close ();
}catch(Exception e){
System.err.println("无法连接指定服务");
}
}
}
(2)服务器端程序
import java.io.*;
import java.net.*;
public class exp_5_4{//TCP通信,作为服务器
public static void main(String [] args) throws IOException{
ServerSocket ss = null;
try{
ss = new ServerSocket (8000);
System.out.println("服务器开始监听8000端口的连接请求");
}catch(IOException e){
System.err.println("8000端口不能使用");
System.exit(1);
}
Socket cs= null;
try{
cs = ss. accept ();
}catch(IOException e){
System.err.println("接收客户机端连接失败");
System.exit(1);
}
DataOutputStream os = new DataOutputStream(cs.getOutputStream());
DataInputStream is = new DataInputStream(cs.getInputStream() );
String inputStr, outputStr;
//输出操作
os.writeUTF("Welcome to My Chat Server");
os.flush();//立即将数据从输出缓存提交给网络发送
DataInputStream stdIn = new DataInputStream( System.in ); //获得键盘输入流
//输入操作
while((inputStr= is. readUTF ) != null){ //接受网络数据
System.out.println("Customer:" + inputStr);
System.out.print("Server:");
outputStr = stdIn.readLine(); //接受键盘输入
Os. writeUTF (outputStr); //向网络发送数据
os.flush();
if(outputStr.equals("bye")) break;
}
os.close();//流关闭
is.close();
cs.close();//套接字关闭
ss.close();
}
}
(3)客户端连接服务器端,并获得输入与输出流
import java.io.*;
import java.net.*;
public class exp_5_3{
public static void main(String args[]) throws IOException{
Socket cs = null;
DataOutputStream os = null;
DataInputStream is = null;
try{//建立socket连接
cs = new Socket ("localhost", 8000);//发出连接请求
is = new DataInputStream(cs. getInputStream ());
os = new DataOutputStream(cs. getOutputStream ());
}catch(UnknownHostException e){
System.err.println("不可识别的主机");
System.exit(0);
}catch(IOException e){
System.err.println("无法链接到服务器的8000端口");
System.exit(0);
}
DataInputStream stdIn = new DataInputStream(System.in);
System.out.print("请输入你的用户名:");
String username = stdIn.readLine();
String fromServer, fromUser;
while((fromServer = is.readUTF()) != null){
System.out.println("Server:" + fromServer);
if(fromServer.equals("bye")) break;
System.out.print("Client:");
fromUser = stdIn.readLine();
os.writeUTF(username + "#" +fromUser);
Os. flush ();
}
os.close();
is.close();
stdIn.close();
cs.close();
}
}
2. 课本例2-1的PortScanner.java实验
(1)进行测试,给出代码过程分析及部分运行结果。
(2)分析可以在哪些方面进行改进,测试并给出运行结果。
① 设置连接超时:
客户端的socket构造方法请求与服务器连接时,默认情况下,会一直等待下去,直到连接成功,或者出现异常,Socket构造方法请求连接时,受底层网络的传输速度影响,可能会处于长时间的等待状态。所以,设置连接超时可以限定等待连接的时间。
代码:
② 使用多线程进行端口扫描:
课本例2-1给的程序是利用单线程来进行端口扫描的,效率较为低下,因此可以使用多线程的方式来进行端口扫描;
代码:
3.课本例3-5 EchoServer.java,例3-6ThreadPool.java代码分析。
(1)这两个例子给出了服务端实现多线程的两种主要方法,测试两种方法的性能,分析两种实现方法的异同及其优缺点;
答:EchoServer接收到一个客户连接,就与客户进行通信,通信完毕后断开连接,然后再接受下一个客户连接,假如同时有多个客户请求连接,这些客户就必须排队等候EchoServer的响应。
ThreadPool采用线程池,不断地从工作队列中取出任务,然后执行任务,当工作线程执行完一个任务时,就会继续执行工作队列中的下一个任务。
相同之处:都采用多线程的方式来进行不同客户端socket
的事件处理
不同之处:EchoServer采用的是使用时创建线程,完成后销毁线程的方式,ThreadPool采用的是通过线程池来复用线程的方式进行多线程处理;
EchoServer优点:为每个客户分配一个工作线程。
EchoServer缺点:服务器创建和销毁工作线程的开销很大;
活动的线程也消耗系统资源;如果县城数目固定,每个线程有很长的生命周期,那么线程的切换也是相对固定的。
ThreadPool优点:减少了创建和销毁线程的次数;方便调整线程池中线程的数目。
ThreadPool缺点:需要分配核心线程数量以及创建LinkedList等集合来存储事件到来的前后顺序。
(2)画出两种实现方法中使用的主要类及其相互关系。
(3)对上述例子进行分析,自行设计一个多线程的服务器程序及其客户端程序,说明其主要功能,实现方法及运行结果。
答:主要功能:创建多线程服务器,实现客户端连接。