(1)建立连接,形成传输的数据通道 (2)在连接中进行大数据量传输 (3)通过三次握手完成连接,是可靠的协议 (4)必须连接,效率稍低 |
2、TCP分客户端和服务端
(1)客户端对应的对象:Socket (2)服务端对应的对象:ServerSocket |
2.1、客户端
通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机,因为TCP时面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功,形成通路后,在该通道进行数据传输。
思路:
(1)创建Socket服务,并制定要连接的主机和端口号 一旦Socket对象创建,就形成Socket流,包含输入流和输出流,Socket有以下方法: InputStream getInputStream():返回次套接字的输入流 OutputStream getOutputStream():返回套接字的输出流 (2)为了发送数据,应获取Socket流中俄输出流 (3)关闭资源 |
例:
package com.heima.net; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; public class TCPDemo1 { public static void main(String[] args)throws Exception { //创建Socket服务 Socket s=new Socket(InetAddress.getByName("localhost"),10001); //发送数据,应获取Socket流中输出流 OutputStream out=s.getOutputStream(); //写数据 out.write("hello heima".getBytes()); //关闭资源 s.close(); } } |
2.2、服务端ServerSocket
思路
(1)建立服务端Socket服务,ServerSocket,并监听一个端口 (2)获取连接过来的客户端对象,通过ServerSocket的accept方法,没有连接就会等,是阻塞式的 (3)客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据 (4)关闭流 |
代码如下:
package com.heima.net; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class TCPServer { public static void main(String[] args)throws Exception { //创建ServerSocket服务 ServerSocket ss=new ServerSocket(10001); //通过accept方法获取连接过来的客户端对象 Socket s=ss.accept(); //获取ip String ip=s.getInetAddress().getHostAddress(); //获取客户端发过来的数据 InputStream in=s.getInputStream(); //读取数据到控制台 byte[]buf=new byte[1024]; int len=0; while((len=in.read(buf))!=-1){ System.out.println(new String(buf,0,len)); } //关闭资源 s.close(); } } |
应用一:建立一个文本转换服务器
客户端给服务器发送文本,服务端会将文本转换成大写在返回该客户端,而且客户端能够不断的进行文本转换。
package com.heima.net; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; public class TCPDemo1 { public static void main(String[] args)throws Exception { } } //客户端 class Send{ public static void main(String[] args)throws Exception { //创建一个Socket服务 Socket s=new Socket(InetAddress.getByName("localhost"),10009); //创建一个键盘输入流 BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in)); //创建一个Socket输出流 BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //创建一个Socket输入流 BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream())); //创建一个字符串 String line=null; while((line=bufr.readLine())!=null){ //把从键盘读取的数据写到Socket输出流中 bufOut.write(line); //换行 bufOut.newLine(); //刷新 bufOut.flush(); //同时需要接受服务端反馈回来的信息 String str=bufIn.readLine(); //打印出来 System.out.println("server说:"+str); } //关闭资源 bufr.close(); s.close(); } } //服务端 class Receive{ public static void main(String[] args)throws Exception { //创建一个ServerSocket服务 ServerSocket ss=new ServerSocket(10009); //获取客户端的Socket服务 Socket s=ss.accept(); //创建一个Socket接受流 BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream())); //创建一个Socket输出流 BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //定义一个字符串 String line=null; while((line=bufIn.readLine())!=null){ //把读取的数据转换成大些写出去 bufOut.write(line.toUpperCase()); //换行 bufOut.newLine(); //刷新 bufOut.flush(); } //关闭资源 s.close(); } } |
注:如果不进行换行和刷新,客户端会停留等待,因为readLine(),只有回车符时才结束,二newLine表示一行的结束。
应用二:复制文件
package com.heima.net; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; public class TCPDemo2 { } //客户端 class Send1{ public static void main(String[] args)throws Exception { //创建Socket服务 Socket s=new Socket(InetAddress.getByName("localhost"),10007); //创建输入流 BufferedReader bufr=new BufferedReader( new FileReader(new File("d:"+File.separator+"clint.txt"))); //创建Socket输出流 PrintWriter out=new PrintWriter(s.getOutputStream(),true); //创建一个字符串 String line=null; while((line=bufr.readLine())!=null){ //把从文件中读取的数据写到Socket输出流中 out.println(line); } //关闭客户端的输出流,相当于给流种加入一个结束标记 s.shutdownOutput(); //读取服务端反馈回来的数据 BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream())); //读取服务端反馈回来的数据 String str=in.readLine(); System.out.println("服务器说:"+str); //关闭资源 bufr.close(); s.close(); } } //服务端 class Receive1{ public static void main(String[] args)throws Exception { //创建ServerSocket服务 ServerSocket ss=new ServerSocket(10007); //创建一个客户端的Socket服务 Socket s=ss.accept(); //读取Socket中数据 BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream())); //把读取的数据写到文件中 PrintWriter pw=new PrintWriter(new FileWriter(new File("d:"+File.separator+"server.txt")),true); //定义一个字符串 String line=null; while((line=bufIn.readLine())!=null){ //把数据读取到指定的文件中 pw.println(line); } //向客户端反馈一些信息 PrintWriter out=new PrintWriter(s.getOutputStream(),true); out.println("上传成功"); //关闭资源 s.close(); pw.close(); } } |
应用三:TCP客户端并发上传图片
问题:怎样让多个客户端同时并发访问服务端?
最好就是将每个客户端封装到一个单独的线程中,这样就可以同时处理多个客户端请求。
如何定义线程呢?
只要明确了每个客户端要在服务端执行的代码即可,将该代码存入run方法中。
定义的线程部分
//定义一个线程 class PicThread implements Runnable{ //此线程需要接受一个客户端 private Socket s; public PicThread(Socket s){ this.s=s; } public void run() { //获取IP String ip=s.getInetAddress().getHostAddress(); FileOutputStream fis=null; try{ System.out.println(ip+"连接成功"); //从Socket服务中读取客户端传过来的数据,因为时图片,所有用字节流 InputStream in=s.getInputStream(); //创建一个文件对象 File file=new File(System.currentTimeMillis()+".jpg"); //把读取到得图片数据写到指定的地方 fis=new FileOutputStream(file); //定义一个字节数组 byte[]buf=new byte[1024]; //定义一个整型 int len=0; while((len=in.read(buf))!=-1){ //把读取的数据写到指定的文件中 fis.write(buf,0,len); } //当上传成功后给客户端反馈一些信息 OutputStream out=s.getOutputStream(); //写一些数据 out.write("上传成功".getBytes()); }catch(Exception e){ throw new RuntimeException(ip+"上传失败"); }finally{ try{ if(fis!=null) fis.close(); if(s!=null) s.close(); }catch(Exception e){ e.printStackTrace(); } } } } |
客户端:
//定义一个客户端 class Client{ 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("只能上传格式为jpg的文件"); return; } //判断文件的大小 if(file.length()>1024*1024*3){ System.out.println("文件超出了最大的上传大小"); return; } //如果上面的步骤都通过了,可以上传文件了,首先建立Socket服务 Socket s=new Socket(InetAddress.getByName("localhost"),10009); //创建一个读取流 FileInputStream fis=new FileInputStream(file); //创建一个Socket输出流,把读取到得数据输出到服务端 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 Server{ public static void main(String[] args)throws Exception { //创建一个ServerSocket服务 ServerSocket ss=new ServerSocket(10009); //不断的接受客户端 while(true){ //接受Socket客户端 Socket s=ss.accept(); //创建一个线程 new Thread(new PicThread(s)).start(); } } } |
应用四:客户端的并发登录
因为同时可能有很多人登录,所以用多线程
//定义一个线程 class UserThread implements Runnable{ //接收一个Socket服务 private Socket s=null; public UserThread(Socket s){ this.s=s; } @Override public void run() { //获取ip地址 String ip=s.getInetAddress().getHostAddress(); try { for(int i=0;i<3;i++){ //读取从客户端读取的数据 BufferedReader bufrIn=new BufferedReader( new InputStreamReader(s.getInputStream())); //创建一个字符串用于存储读取的数据 String name=bufrIn.readLine(); //从文件中获取数据 BufferedReader bufr=new BufferedReader(new FileReader( new File("d:"+File.separator+"user.txt"))); //定义一个字符串 String line=null; //创建一个输出流,向客户端反馈信息 PrintWriter out=new PrintWriter(s.getOutputStream(),true); //创建一个标记 boolean flag=false; while((line=bufr.readLine())!=null){ //判断从文件中读取的数据是否包含客户端传过来的 if(line.equals(name)){//包含则把标记改为true 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) { e.printStackTrace(); throw new RuntimeException(ip+"校验失败"); } } } |
客户端:登录三次,当三次失败是退出程序
//定义一个客户端 class LoginClient{ public static void main(String[] args)throws Exception { //创建一个Socket服务 Socket s=new Socket(InetAddress.getByName("localhost"),10008); //从键盘输入数据 BufferedReader bufr=new BufferedReader( new InputStreamReader(System.in)); //把从键盘接收的数据输出到服务端 PrintWriter out=new PrintWriter(s.getOutputStream(),true); //接收服务端反馈回来的信息 BufferedReader in=new BufferedReader( new InputStreamReader(s.getInputStream())); //定义一个字符串 for(int i=0;i<3;i++){ String line=bufr.readLine(); if(line==null) break; out.println(line); String str=in.readLine(); //打印接收的服务端反馈 System.out.println(str); if(str.contains("欢迎")) break; } //关闭资源 bufr.close(); s.close(); } } |
服务端:
//定义一个服务端 class LoginServer{ public static void main(String[] args)throws Exception { //创建ServerSocket服务 ServerSocket ss=new ServerSocket(10008); //循环接收客户端 while(true){ //接收一个客户端Socket服务 Socket s=ss.accept(); //开启一个线程 new Thread(new UserThread(s)).start(); } } |