Socket编程
1. Socket简介
Socket通常称作“套接字”,用于描述IP和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提各种服务。每种服务都打开一个socket,并绑定到一个端口上,不同的端口对应于不同的服务。
2. Socket编程
网络编程就是直接或间接的通过网络协议与其他计算机进行通信。
其中最重要的两点:
1.如何准确定位到网络上的另外一台主机
2.找到主机之后如何可靠高效的传输数据
2.1 IP、端口
IP地址标识了Internet上的计算机,端口号标识正在运行的进程。
IP和端口号组合则可以得出一个网络套接字。
端口号为一个16位(即2byte)的整数,范围为0~65535。其中0~1023被预定义的通信服务占用。为了避免冲突,我们自己的程序应该使用1024~65535之间的。
2.2 InetAddress
Internet上的主机有两种方式表示其地址
1). 域名: www.ztdjt.com
2). IP:27.151.0.99
java.net.InetAddress常用方法:
getHostName() //得到域名
getHostAddress() //得到IP地址
//得到本地地址 InetAddress localAddress = socket.getLocalAddress(); //得到本地的端口 int localPort = socket.getLocalPort(); //得到远端的地址 InetAddress remoteAddress = socket.getInetAddress(); //得到远端的端口 int remotePort = socket.getPort(); |
3. TCP
TCP(Transmission Control Protocol)传输控制协议,是面向连接的运输层协议。应用程序在使用TCP协议通信之前,必须建立起TCP连接,在传输完毕后,释放已建立连接。其中通信的两个应用一个是服务器进程,另一个是客户端进程。
3.1 TCP通信过程
1). 第一次握手:建立连接,客户端发送到syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
2). 第二次握手:服务器接收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
3). 第三次握手:客户端接收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=ack+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态。
4). 完成三次握手后,客户端和服务端就可以开始传送数据了。
3.2 TCP网络编程
1. Server端监听ServerSocket
java.net.ServerScoket是运行于服务端的应用程序。
//创建ServerSocket并申请服务端口为8888 ServerSocket server = new ServerSocket(8888); //侦听客户端连接,这个方法会阻塞,直到某个socket连接 Socket socket = server.accept(); |
2.Client端连接server端
创建Socket的同时就发起连接,若连接异常就会抛出异常,需要传入服务端的的地址和端口号
//参数1:服务端的IP地址,参数2:服务端的服务端口 Socket socket = new Socket("127.0.0.1", 8888); |
3.获取网路输入流、网络输出流
通过socket获取输入流和输入流,这两个方法时使用Socket通讯的关键方法。封装了TCP协议的Socket是基于流进行通讯的,所以在创建了双方连接后,只需要获取相应的输入与输出流即可实现通讯。
//得到套接字的输入流 InputStream in = socket.getInputStream(); //得到套接字的输出流 OutputStream out = socket.getOutputStream(); |
4. 关闭socket
通讯完毕后,要关闭Socket以释放系统资源。当关闭了该套接字后也会同时关闭由此获取的输入和输出流。
socket.close() |
5. 编程分为服务端和客户端
1). 服务端创建ServerSocket,绑定到指定端口
2). 服务端调用accept(),监听端口,等待客户端来连接
3). 客户端创建Socket并指定服务端的IP地址及端口,与服务端建立连接
4). 服务端accept发现客户端连接后,获取对应该客户端的Socket
5). 调用Socket类getOutputStream和getInputStream获取网络输出流和输入流,开始发送和接收数据
6). 通讯结束后,关闭连接
服务端程序:
/** * Server端应用程序 * @author jmcfeng */ public class Server { public static void main(String[] args){ ServerSocket server = null; try { //创建ServerSocket并申请服务端口为8888 server = new ServerSocket(8888); System.out.println("服务端启动..."); //侦听客户端连接 Socket socket = server.accept(); //客户端连接后,通过该Socket与客户端交互 //得到一个输入流,用于读取客户端发送过来的数据 InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); //读取客户端发送的消息 String msg = br.readLine(); System.out.println("客户端说:" + msg); //得到一个输出流,用于向该客户端发送数据 OutputStream os = socket.getOutputStream(); //是否自动刷新 PrintWriter printer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"), true); //向客户端发送数据 printer.println("你好客户端!"); } catch (IOException e) { e.printStackTrace(); } finally{ try { server.close(); } catch (IOException e) { e.printStackTrace(); } } } } |
客户端:
/** * Client端应用程序 * @author jmcfeng */ public class Client { public static void main(String[] args) { Socket socket = null; try { //和服务端建立一个连接 socket = new Socket("127.0.0.1", 8888); //得到一个输出流,向服务端发送数据 OutputStream out = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8"); PrintWriter printer = new PrintWriter(osw, true); printer.println("你好服务端"); //得到一个输入流,用于读取来自服务端的数据 InputStream in = socket.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); String str = reader.readLine(); System.out.println("服务端说:" + str); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //关闭连接 if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } } |
4. UDP
UDP(User Datagram Protocol)用户传输控制协议,是面向无连接的运输层协议。在使用UDP协议通信之前,不需要建立连接。
4.1 UDP网络编程
UDP数据包通过数据报套接字java.net.DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
4.1.1 DatagramPacket
java.net.DatagramPacket:UDP数据包基于IP、端口建立的。数据包中字节数限制为65536 – 8byte(头信息需要占8字节)。
1. 创建接收包
构造函数:
DatagramPacket(byte buf[], int length) |
将数据包中Length长的数据装进Buf数组
DatagramPacket(byte buf[], int offset, int length) |
将数据包中从offest开始、length长的数据装进buf数组
2. 创建发送包
构造函数:
DatagramPacket(byte buf[], int length, InetAddress address, int port) |
从buf数组中取出length长的数据创建数据包对象,目标是地址address端口port
DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port) |
从buf数组中取出offset开始的、length长的数据包创建数据包对象,目标是地址adderss端口port
4.1.2 DatagramSocket
java.net.DatagramSocket:用于接收和发送UDP的Socket实例
1. 服务端接收
DatagramSocket(int port) |
创建实例,并固定监听Port端口的报文。
void receive(DatagramPacket p) |
接收数据报文到p中,receive方法产生”阻塞”。会一直等待知道有数据被读取到。
2. 客户端发送
DatagramSocket() |
无参构造方法用于客户端编程,它并没有特定监听的端口,仅仅使用一个临时的。程序会让操作系统分配一个可用端口。
void send(DatagramPacket p) |
发送报文到目的地
Server端:
/** * UDP Server端程序 * @author jmcfeng */ public class Server { public static void main(String[] args) { DatagramSocket socket = null; try { //监听8888端口 socket = new DatagramSocket(8888); byte[] data = new byte[1024]; //创建接收包 DatagramPacket packet = new DatagramPacket(data, data.length); //读取发送过来的数据,产生阻塞 socket.receive(packet); //将包中的数据 String str = new String(packet.getData(), 0, packet.getLength()); System.out.println("接收到数据包:" + str); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ //关闭以释放资源 socket.close(); } } } |
Client端:
/** * UDP Client端 * @author jmcfeng */ public class Client { public static void main(String[] args) { DatagramSocket socket = null; try { //创建socket socket = new DatagramSocket(); byte[] data = "你好服务器,你有freestyle么。".getBytes(); //创建发送包 DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 8888); //发送数据 socket.send(packet); } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //关闭以释放资源 socket.close(); } } } |