1 通过Socket实现TCP编程
TCP协议是面向连接、可靠的、有序的,以字节流的方式发送数据。
基于TCP协议实现网络通信的类:
- 客户端的Socket类
- 服务器端的ServerSocket类
1.1 Socket通信模型
1.2 Socket通信实现步骤
- 分别在服务器端和客户端创建ServerSocket和Socket(这也是网络通信的基础)
- 打开连接到Socket的输入/输出流
- 按照协议对Socket进行读/写操作
- 关闭:关闭输入输出流、关闭Socket
1.3 ServerSocket和Socket常用方法介绍
- ServerSocket构造方法:
- ServerSocket(int port):创建绑定到特定端口的服务器套接字
- ServerSocket其他方法:
- Socket accept():侦听并接受到此套接字的连接,调用该方法时会阻塞当前的侦听,等待客户端的连接,客户端连接成功后会创建返回一个Socket实例用来与当前的客户端进行通信重点内容
- void close():关闭此套接字
- getInetAddress:返回此服务器套接字的本地地址
- getLocalPort():返回此套接字在其上侦听的端口
- Socket构造方法:
- Socket(InetAddress address , int port):创建一个流套接字并将其连接到指定的IP地址和端口号
- Socket(String host , int port):创建一个流套接字并将其连接到指定主机上的指定端口号
- Socket其他方法
- getInputStream() 返回此套接字的输入流
- getOutputStream()返回此套接字的输出流
- shutdownInput()关闭当前Socket的输入流
- shutdownOutput()关闭当前Socket的输出流
备注:
1. 源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
1.4 编程实现TCP的Socket通信
1.4.1 服务器端的通信步骤
建立连接
- 创建ServerSocket对象,绑定监听端口
- 通过accept()方法监听客户端请求,等待客户端的连接
ServerSocket serversocket = new ServerSocket(8888); Socket socket = serversocket.accept();
进行通信
连接建立后,通过输入流读取客户端发送的请求信息
InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br =new BufferedReader(isr); String info = null; while((info = br.readLine()) != null){ System.out.println("来自客户端的信息:"+info); }
通过输出流向客户端发送相应信息
OutputStream os = serversocket.getOutputStream(); PrintWriter pw = new PrintWriter(os); pw.wrtier("登录成功,欢迎使用"); pw.flush();
关闭相关资源
读取完客户端信息后紧接着要关闭输入流
socket.shutdownInput();
给客户端发送完信息后紧接着要关闭输出流
socket.shutdownOuput();
最后关闭其他资源,务必在所有操作结束后:
br.close(); isr.close(); is.close(); pw.close(); os.close(); socket.close(); serversocket.close();
1.4.2 客户端的通信步骤
建立连接
- 创建Socket对象,指定要连接的服务器的地址和端口号
Socket socket = new Socket("localhost",8888);//"localhost"表示本机名
进行通信
连接建立后,通过输出流向服务器端发送请求信息
OutputStream os = socket.getOutputStream(); //将输出流包装为打印流 PrintWrtier pw = new PrintWriter(os); //OutputStreamWriter osw = new OutputStreamWriter(os); //BufferedWriter bw = new BufferedWriter(osw); //向服务器端发送信息 pw.write("username : admin ; key : 12300"); pw.flush();
通过输入流获取服务器端响应的信息
InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String info = null; while((info = br.readLine()) != null){ System.out.println("来自服务器端的信息:"+info); }
关闭相关资源
给服务器端发送完信息后紧接着关闭输出流
socket.shutdownOutput();
接收完服务器端发送的信息后紧接着要关闭输入流
socket.shutdownInput();
最后关闭其他资源,务必在所有操作结束后:
pw.close(); os.close(); br.close(); is.close();
备注:
1. 以上在一个主机上进行测试;
2. 必须先启动服务端后启动客户端。
3. 对于同一个socket,如果关闭了输出(输入)流,这与该流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可
4. 使用TCP通信时更多的是以对象的形式,可以用ObjectOutputStream和ObjectInputStream
1.4.3 使用多线程实现多客户端的通信
- 服务器端创建ServerSocket,循环调用accept()等待客户端连接
- 客户端创建一个socket并请求和服务器端连接
- 服务器端接受客户端请求,创建socket与该客户端建立专线连接
- 建立连接的两个socket在一个单独的线程上对话
服务器端继续等待新的连接
服务器端线程处理类代码:
public class ServerThread extends Thread { /** *和本线程相关的Socket */ private Socket socket = null; public ServerThread(Socket socket){ this.socket = socket; } /** * 执行线程操作,响应客户端的请求 */ public void run(){ InputStream is = null; InputStreamReader isr = null; BufferedReader br = null; OutputStream os = null; PrintWriter pw = null; try { //1. 获取输入流,并读取客户端信息 is = socket.getInputStream(); isr = new InputStreamReader(is); br = new BufferedReader(isr); String info = null; while((info = br.readLine())!=null){ System.out.println("来自客户端的信息:"+info); } //1.1 关闭输入流 socket.shutdownInput(); //2. 获取输出流,响应客户端 os = socket.getOutputStream(); pw = new PrintWriter(os); pw.write("登录成功,欢迎使用"); pw.flush(); //2.1 关闭输出流 socket.shutdownOutput(); //获取客户端的IP地址 InetAddress address = socket.getInetAddress(); System.out.println("客户端的IP地址:"+address.getHostAddress()); } catch (IOException e) { e.printStackTrace(); }finally { //3.关闭资源 try { br.close(); isr.close(); is.close(); os.close(); pw.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
服务器端代码:
public class ServerwithTread { public static void main(String[] args) { try { //1.创建一个服务器端Socket,指定绑定的端口,并监听此端口 ServerSocket serversocket = new ServerSocket(8888); //2. 调用accept()方法开始监听,等待客户端的连接 System.out.println( "**服务器即将启动,等待客户端的连接**" ); int count = 0; while(true){ Socket socket = serversocket.accept(); System.out.println("\n**第"+(++count)+"个客户端已连接**\n"); ServerThread serverthread = new ServerThread(socket); serverthread.start(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
客户端代码:
public class Client { static Scanner scanner = new Scanner(System.in); public static void main(String[] args) { try { //1. 创建socket对象 Socket socket = new Socket("localhost",8888); //2. 获取输出流,向服务器发送请求 OutputStream os = socket.getOutputStream(); PrintWriter pw = new PrintWriter(os); StringBuffer sb = new StringBuffer(); System.out.println("请输入用户名:");sb.append("username:"); String name = scanner.next();sb.append(name); System.out.println("请输入密码:");sb.append(";key:"); String key = scanner.next();sb.append(key); pw.write(sb.toString()); pw.flush(); // OutputStreamWriter osw = new OutputStreamWriter(os); // BufferedWriter bw = new BufferedWriter(osw); // bw.write("username : admin ; key : 12300"); // bw.flush(); //2.1 关闭socket输出流 socket.shutdownOutput(); //3. 获取socket输入流,得到服务器端的响应信息 InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String info = null; while((info = br.readLine()) != null){ System.out.println("来自服务器的信息:"+info); } //3.1 关闭输入流 socket.shutdownInput(); //4. 关闭资源 pw.close(); // bw.close(); // osw.close(); os.close(); br.close(); is.close(); socket.close(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
2 通过Socket实现UDP编程
- UDP协议(用户数据报协议)是无连接、不可靠、无序的,但速度相对较快
- UDP协议是以数据报为数据传输的载体,进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据中指明数据所要达到的Socket(主机地址和端口号),然后再将数据报发送出去
- 相关类:DatagramPacket(表示数据报包)和DatagramSocket(进行端到端通信的类,实现UDP通信)
2.1数据报Datagram
构造方法:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包
2.2 DatagramSocket
表示用来发送和接收数据报包的套接字
- 构造方法
- DatagramSocket()
创建数据报套接字,将其绑定到本地主机上任何可用的端口 - DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口 - DatagramSocket(int port, InetAddress laddr)
创建数据报套接字,将其绑定到指定的本地地址
- DatagramSocket()
- 其他方法
- close()
关闭此数据报套接字 - getInetAddress()
返回此套接字连接的地址 - getPort()
返回此套接字的端口 - send(DatagramPacket p)
从此套接字发送数据报包 - receive(DatagramPacket p)
从此套接字接收数据报包
- close()
2.3 服务器端实现步骤
2.3.1 服务器端接收客户端信息
创建DatagramSocket,指定端口号
DatagramSocket soket = new DatagramSocket(7788);
创建DatagramPacket
byte[] data = new byte[1024]; DatagramPacket packet = new DatagramPacket(data,data.length);
接收客户端发送的数据信息
socket.receive(packet);
当调用receive(packet)方法时,在接收到数据报之前会一直处于阻塞状态
读取数据,数据已经被保存在了数据报的字节数组中
String info = new String(packet.getData(),0,packet.getlength()); System.out.println("来自客户端的信息:"+info);
2.3.2 服务器端响应客户端
定义客户端的地址、端口号、数据(getAddress()返回某台机器的 IP 地址,调用该方法的数据报将要发往该机器或者是从该机器接收到的;getPort()返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的)。
InetAddress address = packet.getAddress(); int port = packet.getPort(); byte[] datar = "欢迎使用!".toBytes();
创建数据报包,包含响应的数据信息
DatagramPacket packet2 = new DatagramPacket(datar,datar.lenght,address,port);
发送数据报包
socket.send(packet2);
2.4 客户端实现步骤
2.4.1 客户端向服务器端发送信息
定义发送的信息,比如要发送的服务器的地址、端口号和要发送的数据
InetAddress address = InetAddress.getByName("localhost"); int port = 7788; byte[] data = "username:admin";key:12300".getBytes();
创建DatagramPacket,包含将要发送的信息
DatagramPacket packet = new DatagramPacket(data,data.length(),address,port);
创建DatagramSocket对象,实现数据的发送
DatagramSocket socket = new DatagramSocket();
发送数据
socket.send(packet);
2.4.2 接收服务器的响应数据
创建数据报,用于接收服务器响应数据
byte[] data2 = new byte[1024]; DatagramPacket packet2 = new DatagramPacket(data2,data2.length);
接收服务器响应的数据
socket.receive(packet2);
读取数据
String info = new String(packet2.getData(),packet2.getLength());
备注:
1. 最后注意关闭资源:socket.colse();