java基础--网络编程之TCP
Tcp传输应用
应用一、实现TCP传输的客户端和服务端的简单互访
需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息。
客户端:
1,建立socket服务。指定要连接主机和端口。
2,获取socket流中的输出流。将数据写到该流中。通过网络发送给服务端。
3,获取socket流中的输入流,将服务端反馈的数据获取到,并打印。
4,关闭客户端资源。
关键在于通过getOutputStream()和getInputStream()获取读写流
package cn.xushuai.Test; import java.io.*; import java.net.*; class TcpClient2 { public static void main(String[] args)throws Exception { Socket s = new Socket("127.0.0.1",10007); OutputStream out = s.getOutputStream(); out.write("服务端,你好".getBytes()); InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); s.close(); } }
服务端:
1,建立服务端的socket服务。ServerSocket();
并监听一个端口。
2,获取连接过来的客户端对象。
通过ServerSokcet的 accept方法。没有连接就会等,所以这个方法阻塞式的。
3,客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据。
并打印在控制台。
4,关闭服务端。(可选)
class TcpServer2{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10007); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"....connected"); InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); OutputStream out = s.getOutputStream(); //Thread.sleep(10000); out.write("哥们收到,你也好".getBytes()); s.close(); ss.close(); } }
应用二、编写一个文本转换服务器
分析:
既然是操作设备上的数据,那么就可以使用io技术,并按照io的操作规律来思考。
都是文本数据,可以使用字符流进行操作,同时提高效率,加入缓冲。
客户端给服务端发送文本,服务单会将文本转成大写在返回给客户端。
而且客户度可以不断的进行文本转换。当客户端输入over时,转换结束。
该例子出现的问题:
现象:客户端和服务端都在莫名的等待。
原因:客户端和服务端都有阻塞式方法readLine(),这些方法没有读到结束标记,那么就一直等,而导致两端,都在等待。
为了书写简化,可以使用打印流,自动刷新与换行。
客户端:
源:键盘录入。 目的:网络设备,网络输出流。
而且操作的是文本数据,可以选择字符流。
步骤
1,建立服务。
2,获取键盘录入。
3,将数据发给服务端。
4,获取服务端返回的大写数据。
5,结束,关资源。
import java.io.*; import java.net.*; class TransClient{ public static void main(String[] args) throws Exception{ Socket s = new Socket("127.0.0.1",10010); //定义读取键盘数据的流对象。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //定义目的,将数据写入到socket输出流。发给服务端。 //BufferedWriter bufOut = //new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(),true); //定义一个socket读取流,读取服务端返回的大写信息。 BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); String line = null; while((line=bufr.readLine())!=null){ if("over".equals(line)) break; out.println(line); //bufOut.write(line); //将键盘录入写给服务端 //bufOut.newLine(); //写入换行符,让服务端readLine识别,否则服务端一直阻塞 //bufOut.flush(); String str =bufIn.readLine(); //读取服务端反馈的信息 System.out.println("server:"+str); } bufr.close(); s.close(); //会在socket流中加入结束标记,服务端会识别,所以在客户端结束//后,服务端也就结束了 } }
服务端:
源:socket读取流。目的:socket输出流。
都是文本,装饰。
class TransServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10010); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"....connected"); //读取socket读取流中的数据。 BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); //目的:socket输出流,将大写数据写入到socket输出流,并发送给客户端。 //BufferedWriter bufOut = //new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(),true); String line = null; while((line=bufIn.readLine())!=null) {//只有读到换行符才进行下次读取,否则一直阻塞 System.out.println(line); out.println(line.toUpperCase()); // bufOut.write(line.toUpperCase()); // bufOut.newLine(); //写入换行符,让客户端的readLine识别 // bufOut.flush(); //必须刷新缓冲区 } s.close(); ss.close(); } }
网络编程需注意的问题:
1、 读写流是否能读取到结束标记
读写流中的阻塞式方法read和write要刷新缓冲区和加入结束标记,readLine要进行换行操作,以便加入结束标记。
可以使用printReaader 和printWriter 来替代,有自动刷新和换行,这样简化了书写。
2、服务端是否能够接收到客户端结束标记
客户端上传完文件之后,需要向服务端提供一个结束标记。否则虽然客户端的循环结束,还会向下执行,又开始读取服务端反馈的信息,
但是,服务端还没有反馈时,客户端就向下执行了,以至于两端都阻塞。
3、自定义标签可能出现在文本中,所以导致文本文件还读取完就结束,所以使用通用的解决方案:shutDownOutput( )
服务端的服务线程:使用多线程实现并发访问,只需将共享的代码放在run方法中即可。
应用三、TCP上传文件
客户端:上传文本,并等待服务端的反馈信息
import java.io.*; import java.net.*; //客户端上传文本,并等待服务端的反馈信息 class TextClient{ public static void main(String[] args) throws Exception{ Socket s = new Socket("192.168.1.254",10006); //生成一个缓冲读取流,同时关联一个文件 BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java")); //使用打印流,实现自动刷新和换行 PrintWriter out = new PrintWriter(s.getOutputStream(),true); //循环向服务端发送数据 String line = null; while((line=bufr.readLine())!=null){ out.println(line); } //自定义结束标记 //缺陷:文件中可能出现自定义标记符 //out.println("over"); s.shutdownOutput();//关闭客户端的输出流,相当于,给流中加入一个结束标记-1. //服务端readLine()到-1时就结束读取,接着执行下面的代码 //得到一个缓冲读取流,用于获取服务端的反馈信息 BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); String str = bufIn.readLine(); System.out.println(str); bufr.close(); s.close(); } }
服务端:收到上传的文件,保存到本地server.txt中,并反馈给客户端上传成功的信息
class TextServer{
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(10006);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
//得到一个缓冲读取流对象,用于读取客户端传过来的数据
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//得到一个打印流,用于写入读取的文件
PrintWriter out = new PrintWriter(new FileWriter("server.txt"),true);
String line = null;
//读取数据,直到读到结束标记
while((line=bufIn.readLine())!=null){
//if("over".equals(line))
//break;
out.println(line);
}
//获取一个写入流,向客户端反馈上传结果信息
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
pw.println("上传成功");
out.close();
s.close();
ss.close();
}
}
应用四、客户端并发上传图片
客户端
1,服务端点。
2,读取客户端已有的图片数据。
3,通过socket 输出流将数据发给服务端。
4,读取服务端反馈信息。
5,关闭。
import java.io.*; import java.net.*; class PicClient{ public static void main(String[] args)throws Exception { //只能传入一个文件名 if(args.length!=1){ System.out.println("请选择一个jpg格式的图片"); return ; } //验证文件是否存在并是一个文件 File file = new File(args[0]); if(!(file.exists() && file.isFile())){ System.out.println("该文件有问题,要么补存在,要么不是文件"); return ; } //限制图片为jpg格式 if(!file.getName().endsWith(".jpg")){ System.out.println("图片格式错误,请重新选择"); return ; } //限定文件的大小 if(file.length()>1024*1024*5){ System.out.println("文件过大,没安好心"); return ; } Socket s = new Socket("192.168.1.254",10007); FileInputStream fis = new FileInputStream(file); OutputStream out = s.getOutputStream(); byte[] buf = new byte[1024]; int len = 0; while((len=fis.read(buf))!=-1){ out.write(buf,0,len); } //告诉服务端数据已写完 s.shutdownOutput(); InputStream in = s.getInputStream(); byte[] bufIn = new byte[1024]; int num = in.read(bufIn); System.out.println(new String(bufIn,0,num)); fis.close(); s.close(); } }
服务端提供文件上传的线程代码:
class PicThread implements Runnable{ private Socket s; PicThread(Socket s){ this.s = s; } public void run(){ int count = 1;//定义为局部变量,让每个客户都拥有一个变量,而不是共享(成员变量) String ip = s.getInetAddress().getHostAddress(); try{ System.out.println(ip+"....connected"); InputStream in = s.getInputStream(); File dir = new File("d:\\pic"); File file = new File(dir,ip+"("+(count)+")"+".jpg"); //循环判断文件是否已经存在 while(file.exists()) file = new File(dir,ip+"("+(count++)+")"+".jpg"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[1024]; int len = 0; while((len=in.read(buf))!=-1){ fos.write(buf,0,len); } OutputStream out = s.getOutputStream(); out.write("上传成功".getBytes()); fos.close(); s.close(); } catch (Exception e){ throw new RuntimeException(ip+"上传失败"); } }
服务端:为每个要上传文件的客户new一个线程提供服务
class PicServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10007); while(true){ Socket s = ss.accept(); new Thread(new PicThread(s)).start(); } //ss.close(); } }
应用五、登陆服务
需求:客户端向服务端发送用户请求登陆,服务端通过验证,返回”欢迎光临“,未通过”用户不存在“
客户端通过键盘录入用户名。
服务端对这个用户名进行校验。
如果该用户存在,在服务端显示xxx,已登陆,并在客户端显示 xxx,欢迎光临。
如果该用户存在,在服务端显示xxx,尝试登陆,并在客户端显示 xxx,该用户不存在。
最多就登录三次。
客户端登陆代码:
import java.io.*;
import java.net.*;
class LoginClient{
public static void main(String[] args) throws Exception{
Socket s = new Socket("192.168.1.254",10008);
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
for(int x=0; x<3; x++){
String line = bufr.readLine();
if(line==null)
break;
out.println(line);
String info = bufIn.readLine();
System.out.println("info:"+info);
if(info.contains("欢迎"))
break;
}
bufr.close();
s.close();
}
}
服务端提供登陆服务的线程
class UserThreadimplements Runnable{
private Socket s;
UserThread(Socket s){
this.s = s;
}
public void run(){
String ip =s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
try{
for(int x=0; x<3;x++){
BufferedReaderbufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String name= bufIn.readLine();
//避免按Ctrl+C 结束时,readLine读取为null,不满足,而进行3次循环的判断,所以这里对null进行判断
if(name==null)
break;
BufferedReaderbufr = new BufferedReader(new FileReader("user.txt"));
PrintWriterout = new PrintWriter(s.getOutputStream(),true);
String line= null;
booleanflag = false; //定义标记,判断是否获取到用户
while((line=bufr.readLine())!=null) {//循环读取判断,用户是否存在
if(line.equals(name)){
flag= true;
break;
}
}
if(flag){
System.out.println(name+",已登录");
out.println(name+",欢迎光临");
break;
}
else{
System.out.println(name+",尝试登录");
out.println(name+",用户名不存在");
}
}
s.close();
}
catch (Exception e){
throw newRuntimeException(ip+"校验失败");
}
}
}
服务端:不断循环以产生新的线程为客户端提供服务
class LoginServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10008); //通过循环,不断提供服务 while(true){ Socket s = ss.accept(); new Thread(new UserThread(s)).start(); } } }