一、实验目的:
通过本实验,学习采用Socket(套接字)设计简单的网络数据收发程序,理解应用数据包是如何通过传输层进行传送的。
二、实验内容:
Socket(套接字)是一种抽象层,应用程序通过它来发送和接收数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样。一个socket允许应用程序添加到网络中,并与处于同一个网络中的其他应用程序进行通信。一台计算机上的应用程序向socket写入的信息能够被另一台计算机上的另一个应用程序读取,反之亦然。
不同类型的socket与不同类型的底层协议族以及同一协议族中的不同协议栈相关联。现在TCP/IP协议族中的主要socket类型为流套接字(sockets sockets)和数据报套接字(datagram sockets)。流套接字将TCP作为其端对端协议(底层使用IP协议),提供了一个可信赖的字节流服务。一个TCP/IP流套接字代表了TCP连接的一端。数据报套接字使用UDP协议(底层同样使用IP协议),提供了一个"尽力而为"(best-effort)的数据报服务,应用程序可以通过它发送最长65500字节的个人信息。一个TCP/IP套接字由一个互联网地址,一个端对端协议(TCP或UDP协议)以及一个端口号唯一确定。
2.1、采用TCP进行数据发送的简单程序(java/python3.5)
2.2、采用UDP进行数据发送的简单程序(java/python3.5)
2.3多线程\线程池对比(java/python3.5)
当一个客户端向一个已经被其他客户端占用的服务器发送连接请求时,虽然其在连接建立后即可向服务器端发送数据,服务器端在处理完已有客户端的请求前,却不会对新的客户端作出响应。
并行服务器:可以单独处理没一个连接,且不会产生干扰。并行服务器分为两种:一客户一线程和线程池。
每个新线程都会消耗系统资源:创建一个线程将占用CPU周期,而且每个线程都自己的数据结构(如,栈)也要消耗系统内存。另外,当一个线程阻塞(block)时,JVM将保存其状态,选择另外一个线程运行,并在上下文转换(context switch)时恢复阻塞线程的状态。随着线程数的增加,线程将消耗越来越多的系统资源。这将最终导致系统花费更多的时间来处理上下文转换和线程管理,更少的时间来对连接进行服务。那种情况下,加入一个额外的线程实际上可能增加客户端总服务时间。
我们可以通过限制总线程数并重复使用线程来避免这个问题。与为每个连接创建一个新的线程不同,服务器在启动时创建一个由固定数量线程组成的线程池(thread pool)。当一个新的客户端连接请求传入服务器,它将交给线程池中的一个线程处理。当该线程处理完这个客户端后,又返回线程池,并为下一次请求处理做好准备。如果连接请求到达服务器时,线程池中的所有线程都已经被占用,它们则在一个队列中等待,直到有空闲的线程可用。
2.4写一个简单的chat程序,并能互传文件,编程语言不限。
传输层的代码完全运行在用户的机器上,但是网络层主要运行在由承运商控制的路由器上。用户在网络层上并没有真正的控制权,可行的方法就是在网络层之上的另一层中提高服务质量。
在源机器和目标机器之间提供可靠的、性价比合理的数据传输服务,并且与当前使用的物理网络完全独立。
数据传送,不关心数据含义,进程间通信。弥补高层(上3层)要求与网络层(基于下3层)数据传送服务质量间的差异(差错率、差错恢复能力、吞吐率、延时、费用等),对高层屏蔽网络层的服务的差异,提供稳定和一致的界面。
传输层将数据分段,并进行必要的控制,以便将这些片段重组成各种通信流。在此过程中,传输层主要负责:
① 跟踪源主机和目的主机上应用程序间的每次通信;
② 将数据分段,并管理每个片段;
③ 将分段数据重组为应用程序数据流;
④ 标识不同的应用程序。
网络层(IP层)提供点到点的连接即提供主机之间的逻辑通信,传输层提供端到端的连接——提供进程之间的逻辑通信。
TCP/ IP 传输层的两个主要协议都是因特网的重要标准,传输控制协议TCP(Transmission Control Protocol)[RFC 768]、用户数据报协议UDP(User Datagram Protocol)[RFC 793]。
在一个计算机中,每个进程都有唯一的PID标记着。那么不同计算机运行在应用层的进程有无数个,用PID识别不可取,所以用统一的方法对TCP/IP体系的应用进程进行标志,这个方法就是在传输层使用协议端口号port。虽然通信的终点是应用进程,但我们可以把端口想象是通信的终点,因为我们只要把要传送的报文交到目的主机的某一个合适的目的端口,剩下的工作(即最后交付目的进程)就由 TCP来完成。
在TCP协议中,当客户端退出程序或断开连接时,TCP协议的recv函数会立即返回不再阻塞,因为服务端自己知道客户端已经退出或断开连接,证明它是面向连接的;
而在UDP协议中,recvfrom这个接收函数将会始终保持阻塞,因为服务端自己不知道客户端已经退出或断开连接,证明它是面向无连接的)。
面向字节流:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。
面向报文:面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会是IP太小。(UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送),即一次发送一个报文)。
对于TCP协议,客户端连续发送数据,只要服务端的这个函数的缓冲区足够大,会一次性接收过来,即客户端是分好几次发过来,是有边界的,而服务端却一次性接收过来,所以证明是无边界的;
而对于UDP协议,客户端连续发送数据,即使服务端的这个函数的缓冲区足够大,也只会一次一次的接收,发送多少次接收多少次,即客户端分几次发送过来,服务端就必须按几次接收,从而证明,这种UDP的通讯模式是有边界的。
A->B发100
C->B发200
B recv(1000)
A与C是一个tcp连接,B与C又是另一个tcp连接,所以不同socket,所以分开处理。每个socket有自己的接收缓冲和发送缓冲。
应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元( MTU)的限制)。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
所谓socket 通常也称作”套接字“,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。
以J2SDK-1.3为例,Socket和ServerSocket类库位于Java.NET包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
1.1服务器绑定监听端口
ServerSocket serverSocket = new ServerSocket(80);
注意如果参数设为0,表示由操作系统来为服务器分配一个任意可用的端口,这种端口称作匿名端口,对于多数服务器还是用明确的端口更好,因为客户程序需要事先知道服务器的端口,才能方便地访问服务器。
1.2设定客户连接请求队列的长度
Socket socket = new Socket("服务器IP", 80);
当一个客户进程执行以上代码意味着服务器监可能听到了一个客户的请求连接,这就说明连接请求的数量不定,需要操作系统管理一个队列,一般最大长度为50, 当队列中的连接请求达到了队列的最大容量时, 服务器进程所在的主机会拒绝新的连接请求. 只有当服务器进程通过 ServerSocket 的 accept() 方法从队列中取出连接请求, 使队列腾出空位时, 队列才能继续加入新的连接请求。
对于客户进程, 如果它发出的连接请求被加入到服务器的请求连接队列中, 就意味着客户与服务器的连接建立成功, 客户进程从 Socket 构造方法中正常返回. 如果客户进程发出的连接请求被服务器拒绝, Socket 构造方法就会抛出 ConnectionException.
创建绑定端口的服务器进程后, 当客户进程的 Socket构造方法返回成功, 表示客户进程的连接请求被加入到服务器进程的请求连接队列中. 虽然客户端成功返回 Socket对象, 但是还没跟服务器进程形成一条通信线路. 必须在服务器进程通过 ServerSocket 的 accept() 方法从请求连接队列中取出连接请求, 并返回一个Socket 对象后, 服务器进程这个Socket 对象才与客户端的 Socket 对象形成一条通信线路.
ServerSocket 构造方法的 backlog 参数用来显式设置连接请求队列的长度, 它将覆盖操作系统限定的队列的最大长度. 值得注意的是, 在以下几种情况中, 仍然会采用操作系统限定的队列的最大长度:
backlog 参数的值大于操作系统限定的队列的最大长度;
backlog 参数的值小于或等于0;
在ServerSocket 构造方法中没有设置 backlog 参数.
服务器代码:
package fuwuqi;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private int port = 5209;
private ServerSocket serverSocket;
public Server() throws Exception{
serverSocket = new ServerSocket(port,3);
System.out.println("服务器启动!");
}
public void service(){
while(true){
Socket socket = null;
try {
socket = serverSocket.accept();
System.out.println("New connection accepted "+
socket.getInetAddress()+":"+socket.getPort());
} catch (IOException e) {
e.printStackTrace();
}finally{
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws Exception{
Server server = new Server();
Thread.sleep(60000*20);
//new一个类后,构造函数里已经启动了服务器的监听,便可以接受socket请求加入队列
server.service();
}
}
客户机代码:
package fuwuqi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
public class SocketClient {
// 搭建客户端
public static void main(String[] args) throws IOException {
try {
// 1、创建客户端Socket,指定服务器地址和端口
//下面是你要传输到另一台电脑的IP地址和端口
Socket socket = new Socket("127.0.0.1", 5209);
System.out.println("客户端启动成功");
// 2、获取输出流,向服务器端发送信息
// 向本机的52000端口发出客户请求
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 由系统标准输入设备构造BufferedReader对象
PrintWriter write = new PrintWriter(socket.getOutputStream());
// 由Socket对象得到输出流,并构造PrintWriter对象
//3、获取输入流,并读取服务器端的响应信息
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 由Socket对象得到输入流,并构造相应的BufferedReader对象
String readline;
readline = br.readLine(); // 从系统标准输入读入一字符串
while (!readline.equals("end")) {
// 若从标准输入读入的字符串为 "end"则停止循环
write.println(readline);
// 将从系统标准输入读入的字符串输出到Server
write.flush();
// 刷新输出流,使Server马上收到该字符串
System.out.println("客户:" + readline);
// 在系统标准输出上打印读入的字符串
System.out.println("服务:" + in.readLine());
// 从Server读入一字符串,并打印到标准输出上
readline = br.readLine(); // 从系统标准输入读入一字符串
} // 继续循环
//4、关闭资源
write.close(); // 关闭Socket输出流
in.close(); // 关闭Socket输入流
socket.close(); // 关闭Socket
} catch (Exception e) {
System.out.println("can not listen to:" + e);// 出错,打印出错信息
}
}
}
如果服务器有多个网卡,可以选择绑定某个网卡(IP),比如一个网卡连着局域网,一个网卡连着Internet,希望被局域网访问就可以选择第一个网卡。
ServerSocket serverSocket = new ServerSocket(5209, 10, InetAddress.getByName(“119.23.13.192”));//适用于多个IP地址的主机
默认构造的作用:
可以在绑定端口前修改一些选项,一旦绑定了端口则不能再修改选项
可修改选项的版本:
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true); //设置 ServerSocket 的选项
serverSocket.bind(new InetSocketAddress(5209)); //与8000端口绑定
不可修改的版本:
ServerSocket serverSocket = new ServerSocket(8000);
serverSocket.setReuseAddress(true);//设置 ServerSocket 的选项
##简单的发送消息TCP(BufferedReader & PrintWriter)
客户端:
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer=new PrintWriter(socket.getOutputStream());
以上是获取socket的输入输出,即服务器的输入输出
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
以上是获取客户端的输入(输出无太大意义)。
line=br.readLine();
这个函数比较函数,比如br是客户端的输入流,那么当客户端输入东西,这个readline才会有返回值,然后才能往下运行。
服务器端(同上)
###具体代码(服务器和客户端)
package fuwuqi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketService {
//搭建服务器端
public static void main(String[] args) throws IOException{
SocketService socketService = new SocketService();
//1、a)创建一个服务器端Socket,即SocketService
socketService.oneServer();
}
public void oneServer(){
try{
ServerSocket server=null;
try{
//下面是端口,端口可以和客户端代码里面的端口一样
server=new ServerSocket(5209,3);
//b)指定绑定的端口,并监听此端口。
System.out.println("服务器启动成功");
//创建一个ServerSocket在端口5209监听客户请求
}catch(Exception e) {
System.out.println("队列已满:"+e);
//出错,打印出错信息
}
Socket socket=null;
try{
socket=server.accept();
//2、调用accept()方法开始监听,等待客户端的连接
//使用accept()阻塞等待客户请求,有客户
//请求到来则产生一个Socket对象,并继续执行
System.out.println("+1");
}catch(Exception e) {
System.out.println("Error."+e);
//出错,打印出错信息
}
//3、获取输入流,并读取客户端信息
String line;
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("获取了socket输入");
//由Socket对象得到输入流,并构造相应的BufferedReader对象
PrintWriter writer=new PrintWriter(socket.getOutputStream());
//由Socket对象得到输出流,并构造PrintWriter对象
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
System.out.println("获取了服务系统输入");
//由系统标准输入设备构造BufferedReader对象
System.out.println("Client:"+in.readLine());
//在标准输出上打印从客户端读入的字符串
line=br.readLine();
//从标准输入读入一字符串
//4、获取输出流,响应客户端的请求
while(!line.equals("end")){
//如果该字符串为 "bye",则停止循环
writer.println(line);
//向客户端输出该字符串
writer.flush();
//刷新输出流,使Client马上收到该字符串
System.out.println("服务:"+line);
//在系统标准输出上打印读入的字符串
System.out.println("客户:"+in.readLine());
//从Client读入一字符串,并打印到标准输出上
line=br.readLine();
//从系统标准输入读入一字符串
} //继续循环
//5、关闭资源
writer.close(); //关闭Socket输出流
in.close(); //关闭Socket输入流
socket.close(); //关闭Socket
server.close(); //关闭ServerSocket
}catch(Exception e) {//出错,打印出错信息
System.out.println("Error."+e);
}
}
}
package fuwuqi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
public class SocketClient {
// 搭建客户端
public static void main(String[] args) throws IOException {
try {
// 1、创建客户端Socket,指定服务器地址和端口
//下面是你要传输到另一台电脑的IP地址和端口
Socket socket = new Socket("127.0.0.1", 5209);
System.out.println("客户端启动成功");
// 2、获取输出流,向服务器端发送信息
// 向本机的52000端口发出客户请求
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("获取了系统输入");
// 由系统标准输入设备构造BufferedReader对象
PrintWriter write = new PrintWriter(socket.getOutputStream());
System.out.println("获取了socket输出");
// 由Socket对象得到输出流,并构造PrintWriter对象
//3、获取输入流,并读取服务器端的响应信息
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("获取了socket输入");
// 由Socket对象得到输入流,并构造相应的BufferedReader对象
String readline;
readline = br.readLine(); // 从系统标准输入读入一字符串
System.out.println("从系统读了一行字符串");
while (!readline.equals("end")) {
// 若从标准输入读入的字符串为 "end"则停止循环
write.println(readline);
// 将从系统标准输入读入的字符串输出到Server
write.flush();
// 刷新输出流,使Server马上收到该字符串
System.out.println("客户:" + readline);
// 在系统标准输出上打印读入的字符串
System.out.println("服务:" + in.readLine());
// 从Server读入一字符串,并打印到标准输出上
readline = br.readLine(); // 从系统标准输入读入一字符串
} // 继续循环
//4、关闭资源
write.close(); // 关闭Socket输出流
in.close(); // 关闭Socket输入流
socket.close(); // 关闭Socket
} catch (Exception e) {
System.out.println("can not listen to:" + e);// 出错,打印出错信息
}
}
● DatagramPacket对象封装UDP数据包(含接送端的IP地址和端口号、发送内容等)
● DatagramSocket发送(接收)数据包(系统不保证一定能送达,也不确定什么时候送达)
● 关闭DatagramSocket
发送端:
Scanner sc = new Scanner(System.in);
String msg = sc.next();
byte [] bytes = msg.getBytes();//发送的内容转化为字节数组
/*InetAddress ip = InetAddress.getByName("localhost");
//接受内容的Ip地址 */
以上把输入流获取并且把目标主机的IP获得
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,ip,8080);//输入流,输入流的长度,目标IP,目标端口
接受端:
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
//用套接字接受数据包
receviceSocket.receive(datagramPacket);
//得到发送端的ip地址对象
InetAddress ip = datagramPacket.getAddress();
//将接受到的消息转换为字符串
String rec = new String(datagramPacket.getData()) ;
package fuwuqi;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Send implements Runnable{
@Override
public void run() {
//创建一个发送消息的套接字
DatagramSocket sendSocket = null;
try {
sendSocket = new DatagramSocket();
} catch (SocketException e) {
e.printStackTrace();
}
while (true) {
try {
System.out.println("发送端发送消息");
Scanner sc = new Scanner(System.in);
String msg = sc.next();
byte [] bytes = msg.getBytes();//发送的内容转化为字节数组
InetAddress ip = InetAddress.getByName("localhost");//接受内容的Ip地址
//创建要发送的数据包,然后用套接字发送
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,ip,8080);
//用套接字发送数据包
sendSocket.send(datagramPacket);
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
Send s = new Send();
Thread thread = new Thread(s);
thread.start();
}
}
package fuwuqi;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class Receive implements Runnable {
@Override
public void run() {
System.out.println("接收端");
//创建接受消息的套接字
DatagramSocket receviceSocket = null;
try {
receviceSocket = new DatagramSocket(8080);
} catch (SocketException e) {
e.printStackTrace();
}
while (true) {
try {
byte [] bytes = new byte[2048];
//创建一个数据包来接受消息
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
//用套接字接受数据包
receviceSocket.receive(datagramPacket);
//得到发送端的ip地址对象
InetAddress ip = datagramPacket.getAddress();
//将接受到的消息转换为字符串
String rec = new String(datagramPacket.getData()) ;
System.out.println(ip.getHostAddress()+"发送的消息为:"+rec);
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Receive r = new Receive();
Thread thread = new Thread(r);
thread.start();
}
}
多线程:每处理一个socket请求都会新建一个线程
线程池:提前创建好多个线程,然后需要就从线程池里拿,用完换回去即可
(1)定义一个类实现Runnable接口,重写接口中的run()方法。在run()方法中加入具体的任务代码或处理逻辑。
public class ThreadServer implements Runnable
(2)创建Runnable接口实现类的对象。
ThreadServer s=new ThreadServer(socket);
(3)创建一个Thread类的对象,需要封装前面Runnable接口实现类的对象。(接口可以实现多继承)
Thread thread=new Thread(s);
(4)调用Thread对象的start()方法,启动线程
thread.start();
(1)首先定义一个类去继承Thread父类,重写父类中的run()方法。在run()方法中加入具体的任务代码或处理逻辑。
(2)直接创建一个ThreadDemo2类的对象,也可以利用多态性,变量声明为父类的类型。
(3)调用start方法,线程t启动,隐含的调用run()方法。
package fuwuqi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class ThreadServer implements Runnable {
private Socket socket;
public ThreadServer(Socket socket){
this.socket=socket;
}
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(8888);
while (true) {
Socket socket = server.accept();
ThreadServer s=new ThreadServer(socket);
//一个任务单元
Thread thread=new Thread(s);
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
System.out.println("成功进入run 线程号为"+Thread.currentThread().getName());
Scanner sc = new Scanner(System.in);
while(true){
try {
// String str1 = sc.next();
InputStream in=socket.getInputStream();
DataInputStream dis=new DataInputStream(in);
String str=dis.readUTF();
System.out.println("客户端 "+Thread.currentThread().getName()+" :"+str);
OutputStream out=socket.getOutputStream();
DataOutputStream dos=new DataOutputStream(out);
dos.writeUTF("收到");
dos.flush();
if(str.equals("bye")){
in.close();
out.close();
socket.close();
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"主机: 对话结束");
}
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 8888);
Scanner sc = new Scanner(System.in);
while (true) {
String str = sc.next();
OutputStream out = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(out);
dos.writeUTF(str);
dos.flush();
InputStream in = socket.getInputStream();
DataInputStream dis = new DataInputStream(in);
String strs = dis.readUTF();
System.out.println("服务器:"+strs);
if (strs.equals("bye")||str.equals("bye")) {
in.close();
out.close();
socket.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
简单的为每一个socket都开一个线程,将socket作为参数传进去即可。
存在的问题:
优点:合理利用CPU资源。并且可以提高程序的运行效率
缺点:
1、如果有大量的线程运行,会消耗大部分内存,会影响性能(可能会死机),CPU需要他们之间的切换
2、线程运行可能会出现死锁、线程安全问题(修改操作时你要考虑使用synchronized、lock(接口)、volatile)
package fuwuqi;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
*
* @auth hnurxn
* @date 2015年10月10日
*/
public class SmallThread extends Thread {
private Socket socket;
public SmallThread(Socket socket){
this.socket = socket;
}
public void run() {
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
try {
// 接受客户端请求
dataInputStream = new DataInputStream(socket.getInputStream());
String request = dataInputStream.readUTF();
System.out.println("from client..." + request+" 当前线程"+Thread.currentThread().getName()+": "+request);
// 响应客户端
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String response = "收到";
dataOutputStream.writeUTF(response);
while(true) {
String line;
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
// System.out.println("获取了socket输入");
//由Socket对象得到输入流,并构造相应的BufferedReader对象
PrintWriter writer=new PrintWriter(socket.getOutputStream());
//由Socket对象得到输出流,并构造PrintWriter对象
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
// System.out.println("获取了服务系统输入");
//由系统标准输入设备构造BufferedReader对象
System.out.println("客户"+Thread.currentThread().getName()+": "+in.readLine());
//在标准输出上打印从客户端读入的字符串
line=br.readLine();
//从标准输入读入一字符串
//4、获取输出流,响应客户端的请求
while(!line.equals("end")){
//如果该字符串为 "bye",则停止循环
writer.println(line);
//向客户端输出该字符串
writer.flush();
//刷新输出流,使Client马上收到该字符串
System.out.println("服务:"+line);
//在系统标准输出上打印读入的字符串
System.out.println("客户"+Thread.currentThread().getName()+": "+in.readLine());
//从Client读入一字符串,并打印到标准输出上
line=br.readLine();
//从系统标准输入读入一字符串
} //继续循环
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) {
dataInputStream.close();
}
if (dataOutputStream != null) {
dataOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package fuwuqi;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
*
* @auth hucc
* @date 2015年10月10日
*/
public class ThreadPool {
private static final int PORT = 8888;
public static void main(String[] args) throws IOException {
ServerSocket server = null;
Socket socket = null;
server = new ServerSocket(PORT);
System.out.println("服务端已经开启,监听端口:" + PORT);
//FixedThreadPool最多开启3(参数)个线程,多余的线程会存储在队列中,等线程处理完了
//再从队列中获取线程继续处理
Executor executor = Executors.newFixedThreadPool(3);
while(true){
socket = server.accept();
executor.execute(new SmallThread(socket));
}
}
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
*
* @auth hnurxn
* @date 2015年10月10日
*/
public class Mul {
private static final String HOST = "127.0.0.1";
private static final int PORT = 8888;
public static void main(String[] args) {
Socket socket = null;
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
try {
socket = new Socket(HOST, PORT);
//给服务端发送请求
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String request = "我是客户端";
dataOutputStream.writeUTF(request);
dataInputStream = new DataInputStream(socket.getInputStream());
String response = dataInputStream.readUTF();
System.out.println("服务器:"+response);
while(true){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("获取了系统输入");
// 由系统标准输入设备构造BufferedReader对象
PrintWriter write = new PrintWriter(socket.getOutputStream());
System.out.println("获取了socket输出");
// 由Socket对象得到输出流,并构造PrintWriter对象
//3、获取输入流,并读取服务器端的响应信息
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("获取了socket输入");
// 由Socket对象得到输入流,并构造相应的BufferedReader对象
String readline;
readline = br.readLine(); // 从系统标准输入读入一字符串
System.out.println("从系统读了一行字符串");
while (!readline.equals("end")) {
// 若从标准输入读入的字符串为 "end"则停止循环
write.println(readline);
// 将从系统标准输入读入的字符串输出到Server
write.flush();
// 刷新输出流,使Server马上收到该字符串
System.out.println("客户:" + readline);
// 在系统标准输出上打印读入的字符串
System.out.println("服务:" + in.readLine());
// 从Server读入一字符串,并打印到标准输出上
readline = br.readLine(); // 从系统标准输入读入一字符串
} // 继续循环
//4、关闭资源
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(dataInputStream != null){
dataInputStream.close();
}
if(dataOutputStream != null){
dataOutputStream.close();
}
if(socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端
package fuwuqi;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
public class ChatServer {
/**
* 工程main方法
* @param args
*/
public static void main(String[] args) {
try {
//下面的端口可以和客户端一致
final ServerSocket server = new ServerSocket(5209);
Thread th = new Thread(new Runnable() {
public void run() {
while (true) {
try {
System.out.println("开始监听...");
/*
* 如果没有访问它会自动等待
*/
Socket socket = server.accept();
System.out.println("有链接");
receiveFile(socket);
} catch (Exception e) {
System.out.println("服务器异常");
e.printStackTrace();
}
}
}
});
th.run(); //启动线程运行
} catch (Exception e) {
e.printStackTrace();
}
}
public void run() {
}
/**
* 接收文件方法
* @param socket
* @throws IOException
*/
public static void receiveFile(Socket socket) throws IOException {
byte[] inputByte = null;
int length = 0;
DataInputStream dis = null;
FileOutputStream fos = null;
//这里是给你的文件的名字定义
String filePath = "G:/"+GetDate.getDate()+"-RXN"+new Random().nextInt(10000)+".txt";
try {
try {
dis = new DataInputStream(socket.getInputStream());
//文件储存位置就是下面
File f = new File("G:/");
if(!f.exists()){
f.mkdir();
}
/*
* 文件存储位置
*/
fos = new FileOutputStream(new File(filePath)); //文件输出流
inputByte = new byte[1024];
System.out.println("开始接收数据...");
while ((length = dis.read(inputByte, 0, inputByte.length)) > 0) {
fos.write(inputByte, 0, length);
fos.flush();
}
System.out.println("完成接收:"+filePath);
} finally {
if (fos != null)
fos.close();
if (dis != null)
dis.close();
if (socket != null)
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端:
package fuwuqi;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
public class ChatClient {
/**
* 程序main方法
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
int length = 0;
double sumL = 0 ;
byte[] sendBytes = null;
Socket socket = null;
DataOutputStream dos = null;
FileInputStream fis = null;
boolean bool = false;
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String readline = br.readLine(); // 从系统标准输入读入一字符串
//File file = new File("E:/beijing.txt"); //你要传输的文件路径
File file = new File(readline); //你要传输的文件路径
long l = file.length();
socket = new Socket();
//下面是对方电脑的IP地址和端口,你必须和对方的PC在同一个网内
socket.connect(new InetSocketAddress("127.0.0.1",5209));
dos = new DataOutputStream(socket.getOutputStream());
fis = new FileInputStream(file);
sendBytes = new byte[1024];
while ((length = fis.read(sendBytes, 0, sendBytes.length)) > 0) {
sumL += length;
System.out.println("已传输:"+((sumL/l)*100)+"%");
dos.write(sendBytes, 0, length);
dos.flush();
}
//虽然数据类型不同,但JAVA会自动转换成相同数据类型后在做比较
if(sumL==l){
bool = true;
}
}catch (Exception e) {
System.out.println("客户端文件传输异常");
bool = false;
e.printStackTrace();
} finally{
if (dos != null)
dos.close();
if (fis != null)
fis.close();
if (socket != null)
socket.close();
}
System.out.println(bool?"成功":"失败");
}
}
参考文章如下:
https://blog.csdn.net/qq_36009027/article/details/75500790