Socket
所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端
利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实 上的标准。
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标 识符套接字。
通信的两端都要有Socket,是两台机器间通信的端点。
网络通信其实就是Socket间的通信。
Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
Socket的分类
流套接字(stream socket): 使用TCP提供可依赖的字节流服务
数据报套接字(datagram socket): 使用UDP提供“尽力而为”的数据报服务
Socket的操作
Socket类的常用构造器: public Socket(InetAddress address,int port)创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 public Socket(String host,int port)创建一个流套接字并将其连接到指定主机上的指定端口号。 Socket类的常用方法: public InputStream getInputStream()返回此套接字的输入流。可以用于接收网络消息 public OutputStream getOutputStream()返回此套接字的输出流。可以用于发送网络消息 public InetAddress getInetAddress()此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。 public InetAddress getLocalAddress()获取套接字绑定的本地地址。 即本端的IP地址 public int getPort()此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。 public int getLocalPort()返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的 端口号。 public void close()关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接 或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。 public void shutdownInput()如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将 返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。 public void shutdownOutput()禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发 送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流, 则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
基于Socket的Tcp编程
Java语言的基于套接字编程分为服务端编程和客户端编程,其通信模 型如图所示:
客户端Socket的工作过程包含以下四个基本的步骤:
创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端 响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。 打开连接到 Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流,进行数据传输 按照一定的协议对Socket 进行读/写操作:通过输入流读取服务器放入线路的信息 (但不能读取自己放入线路的信息),通过输出流将信息写入线程。 关闭 Socket:断开客户端到服务器的连接,释放线路
客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连 接。Socket的构造器是: Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是 host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。 Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的 IP地址以及端口号port发起连接。 客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求 Socket s = new Socket(“192.168.40.165”,9999); OutputStream out = s.getOutputStream(); out.write(" hello".getBytes()); s.close();
指定服务端ip和端口的时候可以使用new Socket(ip,端口)也可以借助InetAddress来完成
/** * 实现TCP的网络编程 * * 例子1:客户端发送信息给服务端,服务端将数据显示在控制台上 * @param args */ public static void main(String[] args) { Socket socket = null; // 使用socket建立服务端 OutputStream os = null; try { //1 创建Socket对象指明 服务端ip和端口 InetAddress inetAddress = InetAddress.getByName("192.168.1.10"); // 指定服务端的ip地址 socket = new Socket(inetAddress,8899); // 2 : socket获取一个输出流 用于输出数据 os = socket.getOutputStream(); // 3 :写出数据 os.write("老王我是客户端来请求你".getBytes()); } catch (IOException e) { e.printStackTrace(); //4 :资源的关闭, 关闭流和socket } finally { if(os != null){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
服务端Socket的工作过程包含以下四个基本的步骤:
调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口 上。用于监听客户端的请求。 调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信 套接字对象。 调用 该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出 流和输入流,开始网络数据的发送和接收。 关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字。
服务器建立 ServerSocket 对象
ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口 中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字 连接的ServerSocket对象。 所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象 ServerSocket ss = new ServerSocket(9999); Socket s = ss.accept (); InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int num = in.read(buf); String str = new String(buf,0,num); System.out.println(s.getInetAddress().toString()+”:”+str); s.close(); ss.close();
server端取数据尽量使用ByteArrayOutputStream因为它会在内部建立一个缓冲区先将数据存储到缓冲区,等数据获取完毕一次输出,不会因为外部建的数组不够大导致数据乱码
public class TcpTestOneServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; InputStream is = null; ByteArrayOutputStream bo = null; try { // 1.创建服务器端的ServerSocket,指明自己的端口号 serverSocket = new ServerSocket(8899); // 2.调用accept()表示接收来自于客户端的socket socket = serverSocket.accept(); // 获取输入流 is = socket.getInputStream(); /* 这样写有可能乱码 : 输出结果 老王我是客户��来请求你 byte [] bytes = new byte[20]; int len; while ((len = is.read(bytes)) != -1){ String str = new String(bytes,0,len); System.out.print(str); } */ bo = new ByteArrayOutputStream(); byte [] buffer = new byte[5]; int len; while ((len = is.read(buffer)) != -1){ bo.write(buffer,0,len); } System.out.println(bo.toString()); System.out.println("/接收到了 来自"+socket.getInetAddress().getHostAddress()+"的数据"); } catch (IOException e) { e.printStackTrace(); } finally { if(serverSocket != null){ try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(is != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if(bo != null){ try { bo.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
Socket通信的理解
socket通信好比你去邮局寄快递,socket就是邮局,个人是client端,server是你的邮寄对象(your feriends),
client在邮寄的时候先要获取到server的地址,也就是ip和端口就好比你朋友的小区和门牌号,
这样才能精准的找到目标,然后将要写的数据通过邮局,也就是输出流邮局socket将你要写的数据通过OutputStream信封把数据存储起来,然后发送,你要发送的数据都存储在邮局的信封内:Socket.OutputStream()中.
Server你的朋友在收取数据的时候要先在家等着才能收到信封,所以将朋友在家的状态设为ServerSocket,朋友除了在家还要同意接受信封而不是拒收,所以朋友同意的状态是基于在家的前提那就是
serverSocket.accept();那么就可以将信封给你了拿到信封 Socket.InputStream(),然后就是拆信封ByteArrayOutputStream()读取数据
在传输数据的时候 服务端在接收数据使用read()的时候,会进行阻塞,因为client端没有传递给一个确切的断开连接的信息,所以client在使用socket传输数据的时候不仅仅要关闭socket还要告诉
server端什么时候数据传输完毕要关闭连接 使用socket.shutdownOutput()关闭输出,不然server端还在等着你的传输不会进行下面的步骤server会卡住
Tcp-Practices
客户端发送文件给服务端,服务端将文件保存在本地。
client
public static void main(String[] args) { Socket socket = null; OutputStream osSix = null; FileInputStream fileInputStream = null; try { socket = new Socket("192.168.1.10",1005); osSix = socket.getOutputStream(); fileInputStream = new FileInputStream(new File("beauty.jpg")); // 操作文件用到字节流来 byte [] bytes = new byte[1024]; int len; while ((len = fileInputStream.read(bytes)) != -1){ osSix.write(bytes,0,len); } socket.shutdownOutput(); } catch (IOException e) { e.printStackTrace(); } finally { if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(osSix != null){ try { osSix.close(); } catch (IOException e) { e.printStackTrace(); } } if(fileInputStream != null){ try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }