1. TCP协议
1.1. TCP通信
因为我们上面说过,TCP是明确区分客户端与服务端的,所以我们编写代码的过程中必须要编写两个类分别代表客户端与服务端.
我们先编写客户端的代码:
public static void main(String[] args) throws IOException { Socket socket=null; OutputStream os=null; try { //首先先创建一个InetAddress对象,表明我们客户端过会是要和哪台服务器进行连接 InetAddress serverIP=InetAddress.getByName("127.0.0.1"); int port=9999; //之后我们创建一个连接连接到该服务器上 socket=new Socket(serverIP,port); //因为我们是客户端所以我们需要给服务器发消息,所以我们再创建一个文件流,帮助我们写入数据 os=socket.getOutputStream(); //之后就是通过文件流来写入数据 os.write("hi,you are here too".getBytes()); } catch (IOException e){ e.printStackTrace(); } //这里最好就是关闭好连接,否则可能会出现下面的错误 finally { os.close(); socket.close(); } }
服务端代码:
public static void main(String[] args) throws IOException { ServerSocket serverSocket=null; Socket socket=null; InputStream is=null; ByteArrayOutputStream baos=null; try { //创建客户端需要连接的服务端 serverSocket=new ServerSocket(9999); while (true){ //等待客户端连接过来 socket=serverSocket.accept(); //读取客户端的消息 is=socket.getInputStream(); baos=new ByteArrayOutputStream(); byte[] buffer=new byte[1024]; int len; // do { // len=is.read(buffer); // baos.write(buffer,0,len); // } while (is.available() != 0); while ((len=is.read(buffer))!=-1){ baos.write(buffer,0,len); } System.out.println(baos.toString()); } } catch (IOException e){ e.printStackTrace(); } //这里最好就是关闭好连接,否则可能会出现下面的错误 finally { baos.close(); is.close(); socket.close(); serverSocket.close(); } }
所以最好还是将整个连接关闭好,否则可能会出现上述错误;
这种错误主要就是下面这个原因:
客户端发送完数据之后,并没有关闭socket连接,那么服务端在读取缓冲区的数据是就有可能会出现这样的错误,主要就是 读写操作出现冲突
效果如下:
1.2. TCP文件上传
其实本质上这里和我们上面写的TCP通信其实是差不多的,只不过这里因为我们需要上传的是文件所以我们必须使用文件专属的流,所以需要使用 FileInputStream 与 FileOutputStream ,还有一个就是 关闭连接方面 ,这个关闭连接和上面的关闭连接不太一样,具体我们在下面的代码中具体讲.
其他的操作本质上就是一致的.
客户端:
public static void main(String[] args) throws IOException { //创建一个Socket连接 Socket socket=new Socket(InetAddress.getByName("127.0.0.1"),9000); //读取一个输出流 OutputStream os=socket.getOutputStream(); //读取文件 FileInputStream fis=new FileInputStream(new File("新垣结衣.jpg")); //写出文件 byte[] buffer = new byte[1024]; int len; while ((len=fis.read(buffer))!=-1){ os.write(buffer,0,len); } //确定服务器接收完毕后,才能断开连接 InputStream inputStream1= socket.getInputStream(); ByteArrayOutputStream baos=new ByteArrayOutputStream(); byte[] buffer2=new byte[2048]; int len2; while ((len2=inputStream1.read(buffer2))!=-1){ baos.write(buffer2,0,len2); } System.out.println(baos.toString()); //关闭连接 baos.close(); inputStream1.close(); fis.close();; os.close(); socket.close(); }
服务端:
public static void main(String[] args) throws IOException { //我有一个地址 ServerSocket serverSocket=new ServerSocket(9000); //监听客户端的链接 Socket socket=serverSocket.accept();//阻塞式监听,会一直等待客户端连接 //获取输入流 InputStream is=socket.getInputStream(); //获取输出流 FileOutputStream fos=new FileOutputStream(new File("receive.jpg")); byte[]buffer= new byte [1024]; int len; while ((len=is.read(buffer))!=-1){ fos.write(buffer,0,len); } //通知客户端我接收完毕了 OutputStream os=socket.getOutputStream(); os.write("我接受好了,拜拜了您嘞".getBytes()); //关闭连接 fos.close(); is.close(); socket.close(); serverSocket.close(); }
如果我们只是这样这样写完的话,我们会发现一个奇怪的现象如下:
之前我们的客户端都是发完消息之后就直接关闭了,并且服务端也是接收到消息之后也不做其他的处理,所以整个的通信是能够正常结束的.
但是现在因为我们需要服务器在接收完数据之后告诉客户端我已经成功接收到数据了,你可以关闭连接了.所以客户端这边也是在等待接收服务端数据的状态所以这样就形成了思死锁的状态.就如下图所示:
所以我们在客户端发送完数据之后需要断开socket的输出连接,否则服务端还是无法接收到客户端的信息的.
在客户端发送数据的代码后面添加这一段代码:
// 通知服务器,我已经结束了 socket.shutdownOutput();//我已经传输完了
这样之后我们的文件上传就实现了,效果如下: