Socket(套接字):
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
传输层连接的端点叫做套接字
Socket=(IP地址:端口号)
TCP:提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达
UDP:尽最大努力交付,即不保证可靠交付
(UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。)
每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
tcp/dup
tcp/dup
HTTP:是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应
socket相当于接口 (连接双方的接口)
tcp/udp相当于管道(定义连接传输的方式)
http相当于石油(真正传输的东西)
java.net.Socket套接字
封装了TCP协议的通讯细节,与远端计算机建立TCP连接后可以以流的读写形式与远端计算机的数据交互
功能描述:
* 〈Socket常用构造方法:
* Socket(String host,int port)
* 通过该构造方法实例化Socket的过程就是连接服务端的过程,其中参数:
* host:指的是远端计算机的地址信息(ip信息)
* port:指的是远端计算机开启的服务端口
* 通过地址可以找到网络上的服务端计算机通过端口可以连接到该机上运行的服务端应用程序〉
socket提供的方法:
OutputStream getOutputStream()
通过该方法获取一个输出流,向该输出流写出数据会通过网络发送给对方
通过Socket的方法:
InputStream getInputStream()获取的字节输入流读取的是远端计算机
运行在服务端的ServerSocket主要有两个作用:
1.向系统申请服务端口,客户端就是通过这个端口与服务端程序建立连接的
2.监听端口,一旦一个客户端通过该端口建立连接,此时服务端就会自动创建一个Socket实例
通过他就可以与连接的客户端进行交互了
ServerSocket提供了接收客户端连接的方法
Socket accept()
该方法是一个阻塞方法,调用后会卡住,此时服务端等待客户端的连接,一旦一个客户端实例化
Socket与服务器端建立连接,那么该方法会like返回一个Socket,通过他就可以与该客户端交互了〉
多次调用accept方法可以接收多个客户端的连接
聊天室简单案例
/**
* Copyright (C), 2019, 学生
* FileName: Client
* Author: 秋水浮萍
* Date: 2019/8/30 15:22
* Description:
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package socket;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* ipconfig
* 〈一句话功能简述〉<br>
* 〈聊天室客户端〉
*
* @author 秋水浮萍
* @create 2019/8/30
* @since 1.0.0
*/
public class Client {
/**
* 功能描述:
* 〈java.net.Socket套接字
* 封装了TCP协议的通讯细节,与远端计算机建立TCP连接后可以以流的读写形式王城与远端计算机的数据交互〉
*/
private Socket socket;
//初始化构造方法
public Client() {
try {
/**
* 功能描述:
* 〈Socket常用构造方法:
* Socket(String host,int port)
* 通过该构造方法实例化Socket的过程就是连接服务端的过程,其中参数:
* host:指的是远端计算机的地址信息(ip信息)
* port:指的是远端计算机开启的服务端口
* 通过地址可以找到网络上的服务端计算机通过端口可以连接到该机上运行的服务端应用程序〉
*
*/
System.out.println("正在连接服务端...");
socket = new Socket("176.14.14.172", 8088);
System.out.println("与服务器建立连接!");
} catch (Exception e) {
e.printStackTrace();
}
}
//用于启动的方法
public void start() {
try {
//将读取服务端消息的线程跑起来
ServerHandler handler = new ServerHandler();
new Thread(handler).start();
/*
socket提供的方法:
OutputStream getOutputStream()
通过该方法获取一个输出流,向该输出流写出数据会通过网络发送给对方
*/
Scanner scanner = new Scanner(System.in);
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out, "utf-8");
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw, true);
System.out.println("请输入信息:");
while (true) {
String input = scanner.nextLine();
pw.println(input);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Client c = new Client();
c.start();
}
/**
* 功能描述:
* 〈该线程负责读取服务端发送过来的消息〉
*/
private class ServerHandler implements Runnable {
@Override
public void run() {
try {
//接收信息
InputStream is = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
InputStreamReader isr = new InputStreamReader(bis, "utf-8");
BufferedReader br = new BufferedReader(isr);
String message = null;
InetAddress local = null;
local = InetAddress.getLocalHost();
local.getHostAddress();
String hosts = local.getHostAddress();
System.out.println(hosts + "...这是本机地址");
//循环读取服务端发送的消息
while ((message = br.readLine()) != null) {
//读取服务端发送过来的一行字符串
int end = message.indexOf("说");
if (!hosts.equals(message.substring(0, end))) {
System.out.println("收到信息:" + message);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* Copyright (C), 2019, 学生
* FileName: Server
* Author: 秋水浮萍
* Date: 2019/8/30 15:22
* Description:
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
/**
* 〈一句话功能简述〉<br>
* 〈聊天室服务端〉
*
* @author 秋水浮萍
* @create 2019/8/30
* @since 1.0.0
*/
public class Server {
/*
运行在服务端的ServerSocket主要有两个作用:
1.向系统申请服务端口,客户端就是通过这个端口与服务端程序建立连接的
2.监听端口,一旦一个客户端通过该端口建立连接,此时服务端就会自动创建一个Socket实例
通过他就可以与连接的客户端进行交互了
*/
private ServerSocket server;
/*
该数组用于存放所有客户端输出流
ClientHandler是Server的内部类,因此所有ClientHandler的都可以看到外部类的属性
因此在这里定义一个数组所有ClientHandler就可以将要共享的输出流放在这里
*/
// private PrintWriter[] allOut = {};
private Collection<PrintWriter> allOut=new ArrayList<>();
public Server() {
try {
/**
* 功能描述:
* 〈ServerSocket实例化时通常会传入端口号
* 客户端就是通过这个端口号与服务端建立连接的
* 该端口号不能与系统其他运行到程序申请相同的,否则会抛出地址被占用异常:
* SocketException:address already in use〉
*
*/
System.out.println("正在启动服务端...");
server = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (Exception e) {
e.printStackTrace();
}
}
public void start() {
try {
/**
* 功能描述:
* 〈ServerSocket提供了接收客户端连接的方法
* Socket accept()
* 该方法是一个阻塞方法,调用后会卡住,此时服务端等待客户端的连接,一旦一个客户端实例化
* Socket与服务器端建立连接,那么该方法会like返回一个Socket,通过他就可以与该客户端交互了〉
*多次调用accept方法可以接收多个客户端的连接
*/
while (true) {
System.out.println("等待客户端连接...");
Socket socket = server.accept();
System.out.println("客户端来连接成功");
ClientHandler handler = new ClientHandler(socket);
Thread t = new Thread(handler);
t.start();
for (PrintWriter p : allOut
) {
System.out.println(p);
}
/*
创建一个线程来处理给客户端的交互,并与主线程并发运行
*/
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
/**
* 功能描述:
* 〈该线程负责与指定客户端交互〉
*/
private class ClientHandler implements Runnable {
private Socket socket;
private String host;//记录远端计算机地址信息(客户端)
public ClientHandler(Socket socket) {
this.socket = socket;
//通过Socket获取远端计算机地址的字符串格式
// getInetAddress获取远端地址信息,getHostAddress把地址装换为字符串形式
host = socket.getInetAddress().getHostAddress();
}
@Override
public void run() {
PrintWriter pw = null;
try {
/**
* 功能描述:
* 〈通过Socket的方法:
* InputStream getInputStream()获取的字节输入流读取的是远端计算机
* 发送过来的数据〉
*
*/
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
//通过Socket获取输出流,给客户端发送消息
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
OutputStreamWriter osw = new OutputStreamWriter(bos, "utf-8");
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw, true);
synchronized (allOut) {
//将输出流存入allOut,便于共享
//对数组进行扩容
// allOut = Arrays.copyOf(allOut, allOut.length + 1);
// //将输出流放入数组最后一个位置
// allOut[allOut.length - 1] = pw;
//
allOut.add(pw);
}
System.out.println(host + "上线了,当前在线人数:" + allOut.size());
/**
* 功能描述:
* 〈读取客户端发送过来消息这里,当客户端断开连接是由于客户端系统不同,服务端这里体现的效果:
* linux客户端断开时,服务端这边readline方法会返回null值
* windows客户端断开时,服务端readline方法通常会直接抛出SocketException〉
*
*/
String message = null;
while ((message = br.readLine()) != null) {
System.out.println(host + "说:" + message);
/*
遍历数组发送消息
*/
synchronized (allOut) {
for (PrintWriter p : allOut
) {
p.println(host + "说:" + message);
}
// for (int i = 0; i < allOut.size(); i++) {
// allOut[i].println(host + "说:" + message);
// }
// System.out.println(allOut.size());
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//处理客户端断开连接的操作
//将pw从allOut中删除
// for (int i = 0; i < allOut.size(); i++) {
// if (allOut[i] == pw) {
// allOut[i] = allOut[allOut.length - 1];
// allOut = Arrays.copyOf(allOut, allOut.length - 1);
// break;
// }
//
// }
allOut.remove(pw);
System.out.println(host + "下线了,当前在线人数:" + allOut.size());
//通过Socket的close关闭释放资源
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}