Socket编程
1基础知识
协议 端口号(辨别不同应用)
TCP/IP协议
是目前世界上应用最广泛的协议
是以TCP为基础的不同层次上多个协议的集合
也称:TCP/IP协议族 或 TCP/IP协议栈
TCP:Transmission Control Protocol 传输控制协议
IP: Internet Protocol 互联网协议
IP地址
为实现网络中不同计算机之间的通信,每台机器都必须有唯一的标识—-IP地址
IP地址格式:数字型:192.168.1.10
端口
1、用来区分不同应用程序,每个应用都会有个端口号
2、端口的范围065535,其中01023为系统保留
3、IP地址和端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础。
4、常见协议端口号 http:80 ftp:21 telnet:23
JAVA中的网络支持
针对网络通信的不同层次,java提供的网络功能有四大类
1、InetAddress:用于标识网络上的硬件资源
2、Socket:使用TCP协议实现网络通信的Socket相关的类
3、Datagram:使用UDP协议,将数据保留在数据报中,通过网络进行通信
InetAddress类
1、InetAddress类用于标识网络上的硬件资源,表示互联网协议(IP)地址
public void inetAddressTest() throws Exception{ //获取本机的InetAddressTest实例 InetAddress localHost = InetAddress.getLocalHost(); System.out.println("计算机名:"+localHost.getHostName()); System.out.println("ip地址:"+localHost.getHostAddress()); //获取字节数组形式的IP地址 System.out.println(Arrays.toString(localHost.getAddress())); System.out.println(localHost); //根据机器名 获取InetAddress实例 InetAddress byName = InetAddress.getByName("SONGPC"); System.out.println(byName); //根据ip地址获取实例信息 InetAddress address = InetAddress.getByName("192.168.43.52"); System.out.println(address); }
Socket编程
TCP协议是面向连接、可靠的、有序的,以字节流的方式发送数据
基于TCP协议实现网络通信的类
>客户端的Socket类 > >服务器端的ServerSocket类
Socket通讯的模型
Socket通信实现步骤
1.创建ServerSocket和Socket
2.打开连接到Socket的输入/输出流
3.按照协议对Socket进行读/写操作
4.关闭输入输出流、关闭Socket
服务器端:
1.创建ServerSocket对象,绑定监听端口
2.通过accept()方法监听客户端请求
3.连接通过后,通过输入流读取客户端发送的请求信息
4.通过输出流向客户端发送相应信息
5.关闭相关资源
//1.创建服务器端的Socket,指定绑定的端口,并监听此端口 ServerSocket serverSocket=new ServerSocket(8000); //2.调用accept()监听,等待客户端的连接 System.out.println("服务器:启动,等待客户端的连接***"); //连接成功后创建socket Socket socket = serverSocket.accept(); System.out.println("服务器:客户端已经连接成功!"); //3.获取输入流,用来读取客户端发送信息 InputStream is = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); //包装成高效字符流 BufferedReader br=new BufferedReader(new InputStreamReader(is)); String len=""; while((len=br.readLine())!=null){ System.out.println("服务端:"+len); } (用完一个关闭一个 不然回阻塞) socket.shutdownInput(); //4.获取输出流相应客户端请求 PrintWriter pw=new PrintWriter(outputStream);//包装成打印了 pw.write("欢迎你。。。"); pw.flush(); socket.shutdownOutput(); //关闭资源 pw.close(); outputStream.close(); br.close(); is.close(); socket.close(); serverSocket.close();
客户端:
1.创建Socket对象,指定需要连接的服务器的地址和端口号
2.连接建立后,通过输出流向服务器端发送请求信息
3.通过输入流获取服务器端响应的信息
4.关闭相关资源
//1.创建客户端Socket,指定服务器端的地址和端口号 Socket socket=new Socket("localhost", 8000); //2.获取输出流,用来向服务器端发送信息 OutputStream outputStream = socket.getOutputStream(); PrintWriter pw=new PrintWriter(outputStream); //3.获取输入流获取服务器端的相应 InputStream inputStream = socket.getInputStream(); pw.write("用户名:admin;密码:123"); pw.flush(); //关闭输出流(用完一个关闭一个 不然回阻塞) socket.shutdownOutput(); BufferedReader br=new BufferedReader(new InputStreamReader(inputStream)); String len=""; while((len=br.readLine())!=null){ System.out.println("客户端:"+len); } socket.shutdownInput(); //关闭资源 br.close(); pw.close(); outputStream.close(); socket.close();
多线程服务器
应用多线程来实现服务器与多客户端之间的通信
基本步骤
1.服务器创建ServerSocket,循环调用accept()等待客户端连接
2.客户端创建一个socket并请求和服务器端连接
3.服务器端接受客户端请求,创建socket与该客户端建立专线连接
4.建立连接两个socket在一个单独的线程上对话
5.服务器端继续等待新的连接
服务器线程类ServerThread
public class ServerThread implements Runnable { private Socket socket; public ServerThread(Socket socket) { this.socket = socket; } @Override public void run() { while (true) { InputStream is = null; try { is = socket.getInputStream(); } catch (IOException e) { e.printStackTrace(); } OutputStream os = null; try { os = socket.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } DataInputStream dataInputStream = new DataInputStream(is); DataOutputStream dataOutputStream = new DataOutputStream(os); String string = null; try { string = dataInputStream.readUTF(); } catch (IOException e) { e.printStackTrace(); } System.out.println(string); try { dataOutputStream.writeUTF(string); if(string.equals("byebye")) { System.out.println("客户端下线了,我也退出了"); break; } } catch (IOException e) { e.printStackTrace(); } } } }
服务器端
public class SimpleSocketServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(6666); System.out.println("服务器启动"); while (true) { Socket socket = serverSocket.accept();//监听,等待客户端连接上来 InetAddress inetAddress= socket.getInetAddress();//得到客户端的地址信息 System.out.println("客户端:"+ inetAddress.getHostAddress() + "连接上来了"); ServerThread serverThread = new ServerThread(socket); new Thread(serverThread).start(); } } }
客户端线程
public class ClientThread implements Runnable{ private Socket socket; public ClientThread(Socket socket) { this.socket = socket; } @Override public void run() { while (true) { try { InputStream inputStream = socket.getInputStream(); DataInputStream dataInputStream = new DataInputStream(inputStream); String response = dataInputStream.readUTF();//阻塞 System.out.println("服务器回应:" + response); if(response.equals("byebye")) { System.out.println("退出"); break; } } catch (IOException e) { e.printStackTrace(); } } } }
客户端
public class TcpClient { // public static boolean isDead = false; public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1",6666); ClientThread clientThread = new ClientThread(socket); Thread child = new Thread(clientThread); child.start(); OutputStream os = socket.getOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(os); Scanner scanner = new Scanner(System.in); System.out.println("请输入要发给服务器的信息:byebye 退出"); while (scanner.hasNext())//阻塞在此处,等待用户在控制台的输入 { String string = scanner.nextLine(); dataOutputStream.writeUTF(string); if(string.equals("byebye")) { break; } System.out.println("请输入要发给服务器的信息:byebye 退出"); } } }
6.UDP编程
UDP协议(用户数据报协议)是无连接、不可靠、无序的
UDP协议以数据报作为数据传输的载体
进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明数据要达到的Socket(主机地址和端口号),然后再将数据报发送出去
相关操作类
DatagramPacket:表示数据报包
DatagramSocket:进行端到端通信的类
服务器端实现步骤
1.创建DatagramSocket,指定端口号
2.创建DatagramPacket
3.接受客户端发送的数据信息
4.读取数据
package com.andgo.udp通信; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; /** * UDP服务端 * @author Administrator */ public class UDPServer { /** * 1.创建DatagramSocket,指定端口号 2.创建DatagramPacket 3.接受客户端发送的数据信息 4.读取数据 * @param args * @throws UnknownHostException * @throws SocketException */ public static void main(String[] args) throws Exception { /* *实现了接收数据 */ //0.创建本机的address //InetAddress address = InetAddress.getLocalHost(); // 1.创建DatagramSocket,指定端口号 DatagramSocket socket=new DatagramSocket(8888); //2.创建DatagramPacket,用于接收客户端发送来的数据 byte[] data=new byte[1024]; DatagramPacket packet=new DatagramPacket(data, data.length); //3.接收客户端发送的数 System.out.println("服务器端已经启动,等待接收数据"); socket.receive(packet);//此方法在接收到数据报之前会一直阻塞 //4.读取数据 String info=new String(data, 0, packet.getLength()); System.out.println("服务器接收到:"+info); /* *向客户端响应数据 */ //1.定义客户端的地址、端口号、数据 InetAddress address=packet.getAddress(); int port=packet.getPort(); byte[] bs="欢迎。。。。".getBytes(); //2.创建数据报,包含响应的数据信息 DatagramPacket packet2=new DatagramPacket(bs, bs.length, address, port); //3.向客户端发送数据 socket.send(packet2); //4.关闭资源 socket.close(); } }
客户端实现步骤
1.定义发送数据
2.创建DatagramPacket,包含将要发送的信息
3.创建DatagramSocket
4.发送数据
package com.andgo.udp通信; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; /** * UDP客户端 * @author Administrator * */ public class UDPClient { /** * 1.定义发送数据 2.创建DatagramPacket,包含将要发送的信息 3.创建DatagramSocket 4.发送数据 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { /* *向服务器端发送数据 */ //1.定义服务器的地址,端口号、数据 InetAddress address=InetAddress.getByName("localhost"); int port=8888; byte[] data="用户名:admin;密码:123".getBytes(); //2.创建数据报,包含发送的数据 DatagramPacket packet=new DatagramPacket(data, data.length, address, port); //3.创建DatagramSocket DatagramSocket socket=new DatagramSocket(); //4.向服务器端发送数据 socket.send(packet); /* *接收服务器端响应的数据 */ //1.创建DatagramPacket,用于接收服务器端响应的数据 byte[] bs=new byte[1024]; DatagramPacket packet2=new DatagramPacket(bs, bs.length); //2.接收服务器端响应的数据 socket.receive(packet2); //3.读取数据 String reply=new String(bs, 0, packet2.getLength()); System.out.println("客户端:"+reply); //4.关闭资源 socket.close(); } }
多线程UDP服务器
UDPServerThread
package com.andgo.udp通信多线程实现; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; public class UDPServerThread extends Thread{ private byte[] data; private DatagramPacket packet; private DatagramSocket socket; public UDPServerThread(byte[] data, DatagramPacket packet, DatagramSocket socket) { this.data = data; this.packet = packet; this.socket = socket; } @Override public void run() { //4.读取数据 String info=new String(data, 0, packet.getLength()); System.out.println("服务器接收到:"+info); /* *向客户端响应数据 */ //1.定义客户端的地址、端口号、数据 InetAddress address=packet.getAddress(); int port=packet.getPort(); byte[] bs="欢迎。。。。".getBytes(); //2.创建数据报,包含响应的数据信息 DatagramPacket packet2=new DatagramPacket(bs, bs.length, address, port); //3.向客户端发送数据 try { socket.send(packet2); } catch (IOException e) { System.out.println("数据发送异常"); e.printStackTrace(); } //4.关闭资源 //socket.close(); //UDP中用同一个socket发送和接受数据 ,datagramPaket包含了数据、端口、地址信息(用于区分和发送到哪里) } }
UDPServer
package com.andgo.udp通信多线程实现; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; /** * UDP服务端 * @author Administrator * */ public class UDPServer { /** * 1.创建DatagramSocket,指定端口号 2.创建DatagramPacket 3.接受客户端发送的数据信息 4.读取数据 * @param args * @throws UnknownHostException * @throws SocketException */ public static void main(String[] args) throws Exception { /* *实现了接收数据 */ //0.创建本机的address //InetAddress address = InetAddress.getLocalHost(); // 1.创建DatagramSocket,指定端口号 DatagramSocket socket=new DatagramSocket(8888); //2.创建DatagramPacket,用于接收客户端发送来的数据 byte[] data=new byte[1024]; DatagramPacket packet=new DatagramPacket(data, data.length); //3.接收客户端发送的数 System.out.println("服务器端已经启动,等待接收数据"); while(true){ socket.receive(packet);//此方法在接收到数据报之前会一直阻塞 UDPServerThread thread=new UDPServerThread(data, packet, socket); thread.run(); } } }
UDPClient
package com.andgo.udp通信多线程实现; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; /** * UDP客户端 * @author Administrator * */ public class UDPClient { /** * 1.定义发送数据 2.创建DatagramPacket,包含将要发送的信息 3.创建DatagramSocket 4.发送数据 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { /* *向服务器端发送数据 */ //1.定义服务器的地址,端口号、数据 InetAddress address=InetAddress.getByName("localhost"); int port=8888; byte[] data="用户名:admissn;密码:123sssss".getBytes(); //2.创建数据报,包含发送的数据 DatagramPacket packet=new DatagramPacket(data, data.length, address, port); //3.创建DatagramSocket DatagramSocket socket=new DatagramSocket(); //4.向服务器端发送数据 socket.send(packet); /* *接收服务器端响应的数据 */ //1.创建DatagramPacket,用于接收服务器端响应的数据 byte[] bs=new byte[1024]; DatagramPacket packet2=new DatagramPacket(bs, bs.length); //2.接收服务器端响应的数据 socket.receive(packet2); //3.读取数据 String reply=new String(bs, 0, packet2.getLength()); System.out.println("客户端:"+reply); //4.关闭资源 socket.close() } }
基于socket公共聊天室的实现
1.客户端
TcpClient.java package com.company; import java.io.*; import java.net.Socket; import java.util.Scanner; /** * Created by ttc on 2018/6/12. */ public class TcpClient { public static void helpList() { System.out.println("提示:进入聊天室,默认公聊!!"); System.out.println("/B 用户在线列表,用户#信息 私聊,/C 查看聊天记录,/0 退出系统"); } // public static boolean isDead = false; public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); System.out.println("请输入服务器ip地址"); String strIP = scanner.nextLine(); Socket socket = new Socket(strIP,6666); System.out.println("请输入你的名字"); String username = scanner.nextLine(); ClientThread clientThread = new ClientThread(socket,username); Thread child = new Thread(clientThread); child.start(); OutputStream os = socket.getOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(os); dataOutputStream.writeUTF(username); System.out.println("欢迎进入聊天室,需要帮助请输入/A"); while (scanner.hasNext())//阻塞在此处,等待用户在控制台的输入 { String string = scanner.nextLine(); if(string.equalsIgnoreCase("/A")) { helpList(); } else if(string.equalsIgnoreCase("/C")) { //读取聊天记录 File file = new File(username+".txt"); FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader); String msg = bufferedReader.readLine(); while (msg != null) { System.out.println(msg); msg = bufferedReader.readLine(); } fileReader.close(); bufferedReader.close(); } else if(string.equalsIgnoreCase("/0")) { System.exit(0); } else { dataOutputStream.writeUTF(string);//将信息发送给服务器 } } } }
ClientThread.java
package com.company; import java.io.*; import java.net.Socket; import java.net.SocketException; import java.util.Date; /** * Created by ttc on 2018/6/12. */ public class ClientThread implements Runnable{ private Socket socket; private String username; public ClientThread(Socket socket,String username) { this.username = username; this.socket = socket; } private void writeToFile(String msg) { File file = new File(username + ".txt"); try { FileWriter fileWriter = new FileWriter(file,true); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); Date date = new Date(); bufferedWriter.newLine(); bufferedWriter.write(date.toLocaleString() + ":" + msg); bufferedWriter.flush(); fileWriter.close(); bufferedWriter.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { while (true) { try { InputStream inputStream = socket.getInputStream(); DataInputStream dataInputStream = new DataInputStream(inputStream); String response = dataInputStream.readUTF();//阻塞 System.out.println(response); writeToFile(response); } catch (SocketException e) { //1.关闭socket try { socket.close(); } catch (IOException e1) { e1.printStackTrace(); } //2.退出 System.out.println("服务断开~~~~~~~"); System.exit(0); } catch (IOException e) { e.printStackTrace(); } } } }
2.服务端
SimpleSocketServer.java
package com.company; import java.io.*; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Created by ttc on 2018/6/12. */ public class SimpleSocketServer { static Map<String,Socket> name2Client = Collections.synchronizedMap(new HashMap<>()); public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(6666); System.out.println("服务器启动"); while (true) { Socket socket = serverSocket.accept();//监听,等待客户端连接上来 InetAddress inetAddress= socket.getInetAddress();//得到客户端的地址信息 System.out.println("客户端:"+ inetAddress.getHostAddress() + "连接上来了"); //读取客户端的用户名 InputStream inputStream = socket.getInputStream(); DataInputStream dataInputStream = new DataInputStream(inputStream); String client_name = dataInputStream.readUTF();//阻塞 name2Client.put(client_name,socket); ServerThread serverThread = new ServerThread(socket,client_name); new Thread(serverThread).start(); } } }
ServerThread.java
package com.company; import java.io.*; import java.net.Socket; import java.net.SocketException; import java.util.Map; /** * Created by ttc on 2018/6/12. */ public class ServerThread implements Runnable { private String client_name; private Socket socket; public ServerThread(Socket socket,String client_name) { this.client_name = client_name; this.socket = socket; } //广播消息给所有在线用户 public void send_msg_to_all_clients(String message) throws IOException { for(Map.Entry<String,Socket> entry : SimpleSocketServer.name2Client.entrySet()) { Socket socket = entry.getValue(); OutputStream outputStream = socket.getOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(outputStream); dataOutputStream.writeUTF(message); } } @Override public void run() { try { send_msg_to_all_clients(client_name + "上线了"); } catch (IOException e) { e.printStackTrace(); } //不断的读取,服务的客户端发过来的信息 while (true) { InputStream is = null; try { is = socket.getInputStream(); } catch (IOException e) { e.printStackTrace(); } DataInputStream dataInputStream = new DataInputStream(is); String string = null; try { string = dataInputStream.readUTF(); String[] info = string.split("#"); if(info.length == 2)//私聊 { String target_user = info[0]; String msg = info[1]; if(target_user.equals(client_name)) { OutputStream outputStream2 = socket.getOutputStream(); DataOutputStream dataOutputStream2 = new DataOutputStream(outputStream2); dataOutputStream2.writeUTF("不能自己和自己聊天"); } else if(SimpleSocketServer.name2Client.containsKey(target_user))//是当前在线用户的话 { //把消息发送给该用户 Socket targer_socket = SimpleSocketServer.name2Client.get(target_user); OutputStream outputStream = targer_socket.getOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(outputStream); dataOutputStream.writeUTF(client_name + "对你说:"+ msg); //同时回应发信息的用户 OutputStream outputStream2 = socket.getOutputStream(); DataOutputStream dataOutputStream2 = new DataOutputStream(outputStream2); dataOutputStream2.writeUTF("你对"+target_user+"说:"+msg); } else { //回应用户,用户名有误 //同时回应发信息的用户 OutputStream outputStream2 = socket.getOutputStream(); DataOutputStream dataOutputStream2 = new DataOutputStream(outputStream2); dataOutputStream2.writeUTF("用户名有误。"); } } else if(string.equalsIgnoreCase("/B"))//获取在线用户列表 { OutputStream outputStream = socket.getOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(outputStream); dataOutputStream.writeUTF("当前在线用户列表:"); for (Map.Entry<String, Socket> entry : SimpleSocketServer.name2Client.entrySet()) { dataOutputStream.writeUTF(entry.getKey()); } } else { send_msg_to_all_clients(client_name + "说:" + string); } } catch (SocketException e) { //1.关闭对应的socket try { socket.close(); } catch (IOException e1) { e1.printStackTrace(); } //2.把客户端从map中移除 SimpleSocketServer.name2Client.remove(client_name); System.out.println(client_name + "退出了"); //3.本线程结束 return; } catch (IOException e) { e.printStackTrace(); } } } }