前言
1. 基本知识
通俗一点就是CS就是要下载应用来访问的,BS是在浏览器上面访问的,不用下载
1.1 IP
IP地址就是你电脑的主机号,一台设备都有唯一的IP,端口就是程序的唯一标识
这上面就是我们的ip地址了,有IPv4和IPv6两种
因为IPv4不够用了,所以才用IPv6的
IP域名和IP就是一个东西
只要有网还有IP地址存在,那么ping就会成功,
127.0.0.1就是我们这个电脑的IP地址,默认是
每个电脑的127.0.0.1都是自己的IP
//1.获取本机IP地址对象InetAddress ip1=InetAddress.getLocalHost();System.out.println(ip1.getHostName());//获取本机IP地址对象的名字System.out.println(ip1.getHostAddress());//获取本机IP地址对象的IP
我这个就叫hahaha
//2.获取指定IP或者域名的IP地址对象InetAddress ip2=InetAddress.getByName("www.baidu.com");System.out.println(ip2.getHostName());System.out.println(ip2.getHostAddress());
然后我直接在网址输入这个IP
就真的变成百度了
System.out.println(ip2.isReachable(6000));
这个就是看6秒内,我们本机能否和百度IP建立联系,当然有网就可以了
1.2 端口与通信协议
因为视频语音的话,少了几个包最多只是声音变小,卡了一下而已
三次握手的意思就是,第一次确认了客户端可以发消息,第二次确认了服务器端可以收消息,和发消息,第三次就是确认客户端可以接受消息,这样两边都可以收发消息了,就没有问题了
2. UDP通信
2.1 一发一收
public class Server {public static void main(String[] args) throws Exception {//创建一个服务器对象(创建一个接菜的人),注册端口DatagramSocket socket=new DatagramSocket(6666);//这个对象也就是这个进程的端口是6666,可以指定端口的,不然就是随机的//2.创建一个数据包对象,用于接收数据,就是创建一个盘子byte[] buffer=new byte[1024*64];//因为一个数据包最大发送的内容就是这么大DatagramPacket packet=new DatagramPacket(buffer,buffer.length);//3.开始正式使用数据包来接收客户端发来的数据socket.receive(packet);//只管接收就可以了,只要把IP和端口告诉客户端,只管输入就可以了,这边只管接收//接收到的内容在buffer中,然后就是接收到了多少就倒出多少,不可能倒64kb吧int len=packet.getLength();String rs=new String(buffer,0,len);System.out.println(rs);}
}
这个是服务端
public class Client {public static void main(String[] args) throws Exception {//1.创建客户端对象(发菜的人)DatagramSocket socket=new DatagramSocket();//包装数据包对象分装要发出去的数据(创建盘子)byte[] bytes="我是快乐的客户端,我爱你abc".getBytes();
// public DatagramPacket(byte buf[], int length,
// InetAddress address, int port) {
// this(buf, 0, length, address, port);
// }//参数一:分装要发出去的数据,参数二:发送出去的数据大小(字节个数),参数三:服务端的IP地址,参数四:服务端程序的端口DatagramPacket packet=new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),6666);//包装菜和盘子socket.send(packet);//发出去System.out.println("发送完毕");socket.close();}
}
这个是客户端
应该先启动服务端,因为如果先启动客户端的话,直接就发送了,也不会管你发送成功了没有,还没等服务端接受就没了
只要服务端启动了,那么就会停在那个receive那里,等待接受
然后就是服务端接受到了的话,也可以得到客户端的IP和端口,还有就是资源不要忘了关闭,关闭客户端,服务端就可以了
System.out.println(packet.getAddress().getHostAddress());System.out.println(packet.getPort());socket.close();
然后就是其实客户端可以创建一个指定端口的对象
//1.创建客户端对象(发菜的人)DatagramSocket socket=new DatagramSocket(7777);
2.2 多发多收
下面我们来实现多个客户端发送的场景
用个while循环就可以了
public class Client {public static void main(String[] args) throws Exception {DatagramSocket socket=new DatagramSocket(7777);Scanner sc=new Scanner(System.in);while (true) {System.out.println("请说");String msg=sc.nextLine();if("exit".equals(msg)){System.out.println("退出成功");socket.close();break;}byte[] bytes=msg.getBytes();DatagramPacket packet=new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),6666);//包装菜和盘子socket.send(packet);//发出去}}
}
这样就可以了,那么服务端也要循环来接收
public class Server {public static void main(String[] args) throws Exception {//创建一个服务器对象(创建一个接菜的人),注册端口DatagramSocket socket=new DatagramSocket(6666);byte[] buffer=new byte[1024*64];DatagramPacket packet=new DatagramPacket(buffer,buffer.length);while (true) {socket.receive(packet);int len=packet.getLength();String rs=new String(buffer,0,len);System.out.println(rs);System.out.println("--------------------");}}
}
因为服务端一般是不会关闭的,所以不用close
现在我们就可以客户端一直输入,服务端一直接受了
但是如何启动多个客户端呢,我们的编译器默认的是只能启动一个,但是可以改
点编辑配置
点允许多个实例
但是还是会报错,为什么呢,因为我们的客户端的端口指定了为7777
,不可能两个程序用一个端口吧,所以我们还是让系统随机分配端口吧
DatagramSocket socket=new DatagramSocket();
这样就可以了
3. TCP通信
3.1 一发一收
public class Client {public static void main(String[] args) throws Exception {//创建socket对象,并请求与服务端连接Socket socket=new Socket("127.0.0.1",8888);//连接的就是IP为127.0.0.1,端口为8888的服务端//2.从socket通信管道中得到一个字节输出流,用来发数据给服务端程序OutputStream os=socket.getOutputStream();//3.把这个低级的字节输出流包装成数据输出流DataOutputStream dos=new DataOutputStream(os);//4.写数据出去dos.writeUTF("在一起毫秒");dos.close();socket.close();//要释放两个资源,一个网络的,一个IO流的}
}
public class Server {public static void main(String[] args) throws Exception {//创建一个ServerSocket的对象,同时为服务端注册端口ServerSocket serverSocket=new ServerSocket(8888);//使用serverSocket对象调用一个accept方法,等待客户端的连接请求,返回一个socket,与客户端对应,在服务端但是Socket socket=serverSocket.accept();//从socket通信管道中得到一个字节输入流InputStream is=socket.getInputStream();//包装成高级的IO流DataInputStream dis=new DataInputStream(is);//使用数据输入流读取客户端发送过来的消息String rs=dis.readUTF();System.out.println(rs);//打印客户端的IPSystem.out.println(socket.getRemoteSocketAddress());dis.close();socket.close();}
}
这个服务端会先在accept那里等着连接客户端,成功后,就会在readUTF等待读
3.2 多发多收
public class Client {public static void main(String[] args) throws Exception {Socket socket=new Socket("127.0.0.1",8888);OutputStream os=socket.getOutputStream();DataOutputStream dos=new DataOutputStream(os);Scanner sc=new Scanner(System.in);while(true){System.out.println("请说:");String msg=sc.nextLine();if("exit".equals(msg)){System.out.println("退出");dos.close();socket.close();break;}dos.writeUTF(msg);dos.flush();//刷新过去,防止在缓冲区中}}
}
public class Server {public static void main(String[] args) throws Exception {ServerSocket serverSocket=new ServerSocket(8888);Socket socket=serverSocket.accept();InputStream is=socket.getInputStream();DataInputStream dis=new DataInputStream(is);while(true){String rs=dis.readUTF();System.out.println(rs);}}
}
但是在我们客户端推出的时候,服务端就会异常了,因为一端断开了,read不了了,但是还在等,就会出错,所以要捕获
public class Server {public static void main(String[] args) throws Exception {ServerSocket serverSocket=new ServerSocket(8888);Socket socket=serverSocket.accept();InputStream is=socket.getInputStream();DataInputStream dis=new DataInputStream(is);while(true){try {String rs=dis.readUTF();System.out.println(rs);} catch (Exception e) {System.out.println(socket.getRemoteSocketAddress()+"离线了");dis.close();socket.close();break;}}}
}
但是我们这个不能实现服务端与多个用户通信,因为socket始终只有一个,而且又不确定用户端有多少个
所以我们就要用多线程的处理思想了
3.3 与多个用户通信
我们可以这样,用主线程来接收客户端的连接,连接了一个的话,就把这个socket交给一个子线程来处理
public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket=socket;}@Overridepublic void run() {}
}
先自定义一个线程,因为要把socket交给线程,所以线程的定义要有socket
public class Server {public static void main(String[] args) throws Exception {ServerSocket serverSocket=new ServerSocket(8888);while(true){Socket socket=serverSocket.accept();new ServerReaderThread(socket).start();//交给一个新的线程来处理}}
}
public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket=socket;}@Overridepublic void run() {try {InputStream is=socket.getInputStream();DataInputStream dis=new DataInputStream(is);while (true) {try {String rs = dis.readUTF();System.out.println(rs);} catch (IOException e) {System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程dis.close();socket.close();break;}}} catch (Exception e) {e.printStackTrace();}}
}
这样的话,主线程就会在accept那里等着,子线程的话,就会在read那里等着
这样我们就可以开启多个客户端了
3.4 用户与用户之间通信
其实实现逻辑就是客户端发给服务端,服务端发给其他客户端
就是一个客户端发送消息,其他客户端都能看到
socket用集合存着,这个集合只有一个
public class Server {public static List<Socket> onLineSockets=new ArrayList<>();public static void main(String[] args) throws Exception {ServerSocket serverSocket=new ServerSocket(8888);while(true){Socket socket=serverSocket.accept();onLineSockets.add(socket);System.out.println("有人上线了"+socket.getRemoteSocketAddress());new ServerReaderThread(socket).start();}}
}
接收到一个,就增加一个
对应还有删除,
System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程Server.onLineSockets.remove(socket);//
还要把这个socket发给其他用户
分发给全部客户端就可以了
String rs = dis.readUTF();System.out.println(rs);sendRsToAll(rs);
private void sendRsToAll(String rs) throws Exception {for (Socket onLineSocket : Server.onLineSockets) {OutputStream os=onLineSocket.getOutputStream();DataOutputStream dos=new DataOutputStream(os);dos.writeUTF(rs);dos.flush();}}
然后对应每个客户端都要接受来自对应服务端的数据
因为socket是对应的,所以一定会对应回去的
每个客户端都要一直读,所以这也是一个死循环,而且还要一直死循环写,所以又是多线程的逻辑
又要建立一个新的线程
这个的逻辑和服务端的读的线程逻辑是差不多的
public class ClientReaderThread extends Thread{Socket socket;public ClientReaderThread(Socket socket){this.socket=socket;}@Overridepublic void run() {try {InputStream is=socket.getInputStream();DataInputStream dis=new DataInputStream(is);while (true) {try {String rs = dis.readUTF();System.out.println(socket.getRemoteSocketAddress()+"说"+rs);} catch (IOException e) {Server.onLineSockets.remove(socket);//dis.close();socket.close();break;}}} catch (Exception e) {e.printStackTrace();}}
}
public class Client {public static void main(String[] args) throws Exception {Socket socket=new Socket("127.0.0.1",8888);new ClientReaderThread(socket).start();//创建一个线程来专门接受读的OutputStream os=socket.getOutputStream();DataOutputStream dos=new DataOutputStream(os);Scanner sc=new Scanner(System.in);while(true){System.out.println("请说:");String msg=sc.nextLine();if("exit".equals(msg)){System.out.println("退出");dos.close();socket.close();break;}dos.writeUTF(msg);dos.flush();//刷新过去,防止在缓冲区中}}
}
public class Server {public static List<Socket> onLineSockets=new ArrayList<>();public static void main(String[] args) throws Exception {ServerSocket serverSocket=new ServerSocket(8888);while(true){Socket socket=serverSocket.accept();onLineSockets.add(socket);System.out.println("有人上线了"+socket.getRemoteSocketAddress());new ServerReaderThread(socket).start();}}
}
public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket=socket;}@Overridepublic void run() {try {InputStream is=socket.getInputStream();DataInputStream dis=new DataInputStream(is);while (true) {try {String rs = dis.readUTF();System.out.println(rs);sendRsToAll(rs);} catch (IOException e) {System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程Server.onLineSockets.remove(socket);//dis.close();socket.close();break;}}} catch (Exception e) {e.printStackTrace();}}private void sendRsToAll(String rs) throws Exception {for (Socket onLineSocket : Server.onLineSockets) {OutputStream os=onLineSocket.getOutputStream();DataOutputStream dos=new DataOutputStream(os);dos.writeUTF(rs);dos.flush();}}
}
3.5 实现一个简易版的BS架构
我们的服务端只有一个,不同浏览器打开我们的服务端的时候,就会创建不同的子线程
public class Server {public static List<Socket> onLineSockets=new ArrayList<>();public static void main(String[] args) throws Exception {ServerSocket serverSocket=new ServerSocket(8888);while(true){Socket socket=serverSocket.accept();onLineSockets.add(socket);System.out.println("有人上线了"+socket.getRemoteSocketAddress());new ServerReaderThread(socket).start();}}
}
public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket=socket;}@Overridepublic void run() {//连接到了,就要返回一个‘黑马程序员给浏览器’try {OutputStream os=socket.getOutputStream();DataOutputStream dos=new DataOutputStream(os);dos.writeUTF("黑马程序员");dos.close();socket.close();} catch (Exception e) {e.printStackTrace();}}
}
然后我们就可以在浏览器上连接我们的服务端了
但是这样不行呢,为什么呢,因为服务器必须给浏览器响应http协议规定的数据格式,不然浏览器不识别返回的数据。
而且浏览器会疯狂去试探,所以有很多上线了,但是没有一个能认识那些数据,所以会出错
因为换行数据流不好操作,所以我们用打印流
我们返回的数据就要是上面的格式,不然浏览器无法识别,这个就是http协议
public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket=socket;}@Overridepublic void run() {//连接到了,就要返回一个‘黑马程序员给浏览器’try {OutputStream os=socket.getOutputStream();PrintStream ps=new PrintStream(os);ps.println("HTTP/1.1 200 OK");ps.println("Content-Type:text/html;charset=UTF-8");ps.println();//必须换行、、、..后面的就是我们要输入的内容ps.println("黑马程序员");socket.close();} catch (Exception e) {e.printStackTrace();}}
}
可以美化一下
ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员");
换个浏览器也可以
拓展一下,我们可以使用线程池
//创建一个线程池,,因为这个是IO型任务,所以核心线程数量=CPU核数*2ThreadPoolExecutor pool=new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS,new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
因为为线程池,所以传入的也都是任务对象,就不能是线程对象了
public class ServerReaderRunnable implements Runnable{
public class Server {public static List<Socket> onLineSockets=new ArrayList<>();public static void main(String[] args) throws Exception {ServerSocket serverSocket=new ServerSocket(8888);//创建一个线程池,,因为这个是IO型任务,所以核心线程数量=CPU核数*2ThreadPoolExecutor pool=new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS,new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());while(true){Socket socket=serverSocket.accept();pool.execute(new ServerReaderRunnable(socket));}}
}
public class ServerReaderRunnable implements Runnable{private Socket socket;public ServerReaderRunnable(Socket socket){this.socket=socket;}@Overridepublic void run() {//连接到了,就要返回一个‘黑马程序员给浏览器’try {OutputStream os=socket.getOutputStream();PrintStream ps=new PrintStream(os);ps.println("HTTP/1.1 200 OK");ps.println("Content-Type:text/html;charset=UTF-8");ps.println();//必须换行、、、..后面的就是我们要输入的内容ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员");ps.close();socket.close();} catch (Exception e) {e.printStackTrace();}}
}
这样就可以了
总结
后面还是接着讲Java