Socket编程主要涉及到客户端和服务端两个方面,首先是在服务器端创建一个服务器套接字(ServerSocket),并把它附加到一个端口上,服务器从这个端口监听连接。端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。
客户端请求与服务器进行连接的时候,根据服务器的域名或者IP地址,加上端口号,打开一个套接字。当服务器接受连接后,服务器和客户端之间的通信就像输入输出流一样进行操作。
getInputStream()方法可以得到一个输入流,客户端的Socket对象上的getInputStream方法得到输入流其实就是从服务器端发回的数据。
getOutputStream()方法得到的是一个输出流,客户端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给服务器端的数据。
getInputStream()方法得到的是一个输入流,服务端的Socket对象上的getInputStream方法得到的输入流其实就是从客户端发送给服务器端的数据流。
getOutputStream()方法得到的是一个输出流,服务端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给客户端的数据。
ServerSocket的accept()方法是侦听并接受到此套接字的连接,就是一直等待连接,此方法在连接传入之前一直阻塞。直到接受到有socket的连接,然后创建并返回新的Socket对象。
从Socket上读取对端发过来的数据一般有两种方法:
1)按照字节流读取
1 BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 2 int len = 0; 3 4 while ((len = in.read()) != -1) { 5 6 }
2)按照字符流读取
1 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 2 String s; 3 while ((s = in.readLine()) != null) { 4 System.out.println("Reveived: " + s); 5 }
这两个方法read()和readLine()都会读取对端发送过来的数据,如果无数据可读,就会阻塞直到有数据可读。或者到达流的末尾,这个时候分别返回-1和null。
这个特性使得编程非常方便也很高效。
但是这样也有一个问题,就是如何让程序从这两个方法的阻塞调用中返回。
1、发送完后调用Socket的shutdownOutput()方法关闭输出流,这样对端的输入流上的read操作就会返回-1。 注意不能调用socket.getInputStream().close()。这样会导致socket被关闭。
当然如果不需要继续在socket上进行读操作,也可以直接关闭socket。 但是这个方法不能用于通信双方需要多次交互的情况。
2、发送数据时,约定数据的首部固定字节数为数据长度。这样读取到这个长度的数据后,就不继续调用read方法。
3、为了防止read操作造成程序永久挂起,还可以给socket设置超时。 如果read()方法在设置时间内没有读取到数据,就会抛出一个java.net.SocketTimeoutException异常。
例如下面的方法设定超时3秒。 socket.setSoTimeout(3000);
1 public class TCPClient { 2 public static void main(String[] args)throws IOException { 3 Socket socket = new Socket("127.0.0.1",5000); 4 OutputStream out = socket.getOutputStream(); 5 //创建文件对象 如果文件路径写错 Client报找不到文件异常,Server报Connection Reset异常 6 File file = new File("E:\\Pictures\\010.jpg"); 7 //字节输入流,读取本地文件到java程序中 8 FileInputStream fis = new FileInputStream(file); 9 byte[] data = new byte[1024]; //数组,增强传输效率 10 int len = 0; 11 while((len = fis.read(data)) != -1) //read方法返回数组的有效字符个数 12 out.write(data, 0, len); 13 socket.shutdownOutput(); //数据传输完毕,关闭socket输出流,避免服务器端read方法阻塞 14 15 InputStream in = socket.getInputStream(); //字节输入流,读取服务器返回的数据 16 len = in.read(data); 17 System.out.println(new String(data,0,len)); 18 19 socket.close(); 20 fis.close(); 21 } 22 23 } 24 25 --------------------------------------------------------------------------------- 26 public class TCPServer { 27 public static void main(String[] args)throws IOException { 28 ServerSocket server = new ServerSocket(5000);//打开服务器制定端口,等待客户端连接 29 //获得与服务器相连的套接字对象 套接字:绑定ip地址和端口号的网络对象 30 Socket socket = server.accept(); 31 //查看该地址文件夹是否存在,如果不存在,创建一个 32 File file = new File("E:\\TestFolder\\upload"); 33 if(!file.exists()){ 34 boolean b = file.mkdirs(); 35 System.out.println(b); 36 } 37 InputStream in = socket.getInputStream(); //套接字的字节输入流,读取客户端传过来的数据 38 39 String name = System.currentTimeMillis()+""+new Random().nextInt(9999); //随机文件名 40 FileOutputStream fos = new FileOutputStream(file+File.separator+name+".jpg"); //File.separator表示分隔符,windows是\,linux是/ 41 byte[] data = new byte[1024]; 42 int len = 0; 43 //如果客户端没有关闭socket输出流,这里的read方法会一直读取,对于socket流没有流末尾之说,不可能返回-1 44 while((len = in.read(data)) != -1) 45 fos.write(data, 0, len); 46 data = "上传成功!".getBytes(); //字符串转化为字节数组 47 OutputStream out = socket.getOutputStream(); //创建字节输出流 48 out.write(data); //写入上传成功 ,反馈给客户端 49 server.close(); 50 fos.close(); 51 } 52 53 }
参考文章:
https://blog.csdn.net/anthony_ju/article/details/82192135
https://www.cnblogs.com/swordfall/p/10781281.html
socket通信模型、socket中accept阻塞的与read()阻塞
原文:https://www.cnblogs.com/FengZeng666/p/12488827.html