socket 是java中的一插口,要实现网络通信的话,需要连接插口和插口,而数据的传输使用了流的思想,读数据操作运用了输入流,而写数据运用了输出流,
聊天对话的实现
一个类作为服务端对象,用于接受客户端写出的输出流,
一个类作为客户端对象,用于输到的服务端的进行读取的输入流.
client客户端的类
私有一个成员变量 Socket插口
构造方法会优先比普通方法先调用,这边可以先定义一下客户端新建一个实例对象初始化的行为
public class Client {/*java.net.Socket 套接字Socket封装了TCP协议的通讯细节,使用它可以和远端计算机建立网络链接,并基于两条流(一条输入,一条输出)的读写与对方进行数据交换。*/private Socket socket;/*** 构造器,用于初始化客户端*/public Client(){try {System.out.println("正在链接服务端...");/*Socket实例化时就是与服务端建立链接的过程,此时需要传入两个参数参数1:服务端的IP地址,用于找到服务端的计算机参数2:服务端口,用于找到服务端程序如何查看IP地址:windows:窗口键+R 打开控制台输入ipconfig查看以太网适配器-以太网,找到ipv4查看自己的IP地址mac:打开[终端]程序输入/sbin/ifconfig查看自己的IP地址*/
// socket = new Socket("127.0.0.1",8088);//127.0.0.1和localhost都是表示本机socket = new Socket("localhost",8088);System.out.println("与服务端成功链接!");} catch (IOException e) {e.printStackTrace();}}}
主要点 Socket 需要传递两个参数 ip地址(服务端) 服务端占用的端口
ip地址 查看
如何查看IP地址:
windows:窗口键+R 打开控制台
输入ipconfig
查看以太网适配器-以太网,找到ipv4查看自己的IP地址mac:打开[终端]程序
输入/sbin/ifconfig查看自己的IP地址
*/
socket端口查看
Windows系统:
- 按住Windows键+R,唤出运行对话框。
- 在对话框里输入“cmd”,然后点确定,就会出现cmd窗口。
- 在cmd窗口中输入命令“netstat -an”,然后回车,就能看见当前连接的列表,其中冒号后面跟着的数字就是端口号
这边定义一个运行方法
public void start(){/*Socket提供的方法:OutputStream getOutputStream()通过Socket获取一个字节输出流,通过向该流写出字节,就可以发送给远端链接的计算机的Socket了*/try {OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw, true);pw.write(12);}catch (IOException e) {e.printStackTrace();} finally {try {//socket的close方法会进行四次挥手//并且也会关闭通过socket获取的输入流和输出流socket.close();} catch (IOException e) {e.printStackTrace();}}}
finally是无论程序执行过程中是否发生异常都会执行,并且在强制退出方法也会执行,使用return中断下方的代码,强制退出该方法也会执行finally,
选择拔插口是不选择关闭文件流是为了告诉服务端 ,客户端想要中断输出,是为了保证传输的可靠性
三次握手主要用于建立TCP连接。这个过程包括三个步骤:
- 客户端发送一个带有SYN(synchronize)标志的数据包给服务端,并进入SYN_SEND状态,等待服务器确认。
- 服务端收到SYN包后,确认客户的SYN(ACK=客户端序列号+1),同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态。
- 客户端收到服务器的SYN+ACK包后,向服务器发送确认包ACK(ACK=服务器序列号+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。完成三次握手后,客户端与服务器端开始传送数据。
四次挥手则用于断开TCP连接。这个过程同样包含四个步骤:
- 客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT1状态。
- 服务端收到连接释放报文,发出确认报文,ACK=1,ack=序号+1,并且带上自己的序列号,服务端就进入了CLOSE_WAIT状态。此时TCP连接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接受。
- 若服务端也没有数据要发送了,就通知客户端,发送一个FIN,即释放数据报文,之后服务端进入LAST_ACK状态,等待客户端的确认。
- 客户端收到服务端的释放数据报文后,必须发出确认,ACK=1,ack=序号+1,而自己的序列号是seq=序号+1,此时,客户端就进入了TIME_WAIT状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
四次挥手中,服务器发送完ACK之后,释放了服务器端到客户端的数据传送,但是客户端到服务器端的这个方向数据传送还没有释放,所以客户端最后还要发送一次确认。
总的来说,三次握手和四次挥手确保了TCP连接的建立和断开过程中的数据同步和可靠性,是TCP/IP协议中非常重要的部分。
最后运行
public static void main(String[] args) {//实际开发中不会在main方法中写业务逻辑,main方法是静态方法会有很多不便Client client = new Client();//调用构造器初始化客户端client.start();//调用start方法使客户端开始工作 }
服务端的类
服务端构造方法定义一个启动服务端使用初始化行为,私有化的服务端插口
public class Server {/*java.net.ServerSocket运行在服务端的ServerSocket相当于时客户中心的"总机",上面有若干的插座(Socket)客户端的插座就是与总机建立链接,然后总机这边分配一个插座与之建立链接,来保持双方通讯的。ServerSocket有两个主要工作1:创建时向系统申请服务端口,以便客户端可以通过端口找到2:监听该端口,一旦一个客户端链接,便创建一个Socket,通过它与客户端通讯*/private ServerSocket serverSocket;public Server(){try {System.out.println("正在启动服务端");/*实例化ServerSocket时需要指定向系统申请的服务端口,如果该端口已经被系统的其他应用程序占据,则这里会抛出异常java.net.BindException: Address already in use: bind()*/serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕");} catch (IOException e) {throw new RuntimeException(e);}}
ServerSocket 的参数是一个服务端占用的端口
服务端运行的方法
public void start(){try {System.out.println("等待客户端链接...");/*ServerSocket的重要方法:Socket accept()该方法是一个阻塞方法,调用该方法后程序会"卡住",直到一个客户端使用Socket与服务端建立链接为止,此时accept方法会立即返回一个Socket通过返回的Socket就可以与链接的客户端双向通讯了。*/Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isr);String message;while ((message = br.readLine()) != null) {//张三[192.168.2.5]说:XXXXSystem.out.println(nickname+"[" + ip + "]说:" + message);} catch (IOException e) {e.printStackTrace();}}
服务端运行程序
public static void main(String[] args) {Server server = new Server();server.start(); }
整理
客户端
ublic class Client {/*java.net.Socket 套接字Socket封装了TCP协议的通讯细节,使用它可以和远端计算机建立网络链接,并基于两条流(一条输入,一条输出)的读写与对方进行数据交换。*/private Socket socket;/*** 构造器,用于初始化客户端*/public Client(){try {System.out.println("正在链接服务端...");/*Socket实例化时就是与服务端建立链接的过程,此时需要传入两个参数参数1:服务端的IP地址,用于找到服务端的计算机参数2:服务端口,用于找到服务端程序如何查看IP地址:windows:窗口键+R 打开控制台输入ipconfig查看以太网适配器-以太网,找到ipv4查看自己的IP地址mac:打开[终端]程序输入/sbin/ifconfig查看自己的IP地址*/
// socket = new Socket("127.0.0.1",8088);//127.0.0.1和localhost都是表示本机socket = new Socket("localhost",8088);System.out.println("与服务端成功链接!");} catch (IOException e) {e.printStackTrace();}}/*** 用于客户端开始工作的方法*/public void start(){/*Socket提供的方法:OutputStream getOutputStream()通过Socket获取一个字节输出流,通过向该流写出字节,就可以发送给远端链接的计算机的Socket了*/try {OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw, true);pw.write(01)} catch (IOException e) {e.printStackTrace();} finally {try {//socket的close方法会进行四次挥手//并且也会关闭通过socket获取的输入流和输出流socket.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) {//实际开发中不会在main方法中写业务逻辑,main方法是静态方法会有很多不便Client client = new Client();//调用构造器初始化客户端client.start();//调用start方法使客户端开始工作}
服务端
public class Server {/*java.net.ServerSocket运行在服务端的ServerSocket相当于时客户中心的"总机",上面有若干的插座(Socket)客户端的插座就是与总机建立链接,然后总机这边分配一个插座与之建立链接,来保持双方通讯的。ServerSocket有两个主要工作1:创建时向系统申请服务端口,以便客户端可以通过端口找到2:监听该端口,一旦一个客户端链接,便创建一个Socket,通过它与客户端通讯*/private ServerSocket serverSocket;public Server(){try {System.out.println("正在启动服务端");/*实例化ServerSocket时需要指定向系统申请的服务端口,如果该端口已经被系统的其他应用程序占据,则这里会抛出异常java.net.BindException: Address already in use: bind()*/serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕");} catch (IOException e) {throw new RuntimeException(e);}}public void start(){try {System.out.println("等待客户端链接...");/*ServerSocket的重要方法:Socket accept()该方法是一个阻塞方法,调用该方法后程序会"卡住",直到一个客户端使用Socket与服务端建立链接为止,此时accept方法会立即返回一个Socket通过返回的Socket就可以与链接的客户端双向通讯了。*/Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isr);String message;while ((message = br.readLine()) != null) {//张三[192.168.2.5]说:XXXXSystem.out.println(nickname+"[" + ip + "]说:" + message);} catch (IOException e) {System.out.println(e);}}
问题 分析
问题 这个客户端只能固定写死输出的数据,不能根据用户输入的内容传递到服务端,使服务端读取数据
这个问题,需要解决的话客户端需要用的Scanner,替换之前单调输出 pw.write()
Scanner scanner = new Scanner(System.in);while(true) {String line = scanner.nextLine();if("exit".equalsIgnoreCase(line)){break;}pw.println(line);}
需要提醒的是,Scanner中 nextline 是根据换行符鉴定用户是否输入一行内容,而PrintWriter是可以根据换行符刷新更新数据的,PrintWriter pw = new PrintWriter(bw, true);这边写了true是表示字符串输出流可以根据鉴定用户是否换行来刷新数据,记得 一定要使用printIn 或者 print(+"\n").
问题 假如多个客户端需要连接一个服务端怎么办
可以循环新建一个接受用户的插口
public void start(){while(true){try {System.out.println("等待客户端链接...");/*ServerSocket的重要方法:Socket accept()该方法是一个阻塞方法,调用该方法后程序会"卡住",直到一个客户端使用Socket与服务端建立链接为止,此时accept方法会立即返回一个Socket通过返回的Socket就可以与链接的客户端双向通讯了。*/Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isr);String message;while ((message = br.readLine()) != null) {//张三[192.168.2.5]说:XXXXSystem.out.println(nickname+"[" + ip + "]说:" + message);}} catch (IOException e) {e.printStackTrace();}}
问题 多个客户端连接一个服务器,同时不同客户端给服务端发送消息,服务端只能接受第一个人发生来的消息
原因分析 第一 服务端start方法使用两个循环,内存循环除非执行完,才能执行外层循环,而内层循环的Scanner是获取用户的输入,而内层需要结束需要根据用户的输入和字符串比较才能得到的结果.
我们需要引入一个新的概念 线程 线程是将我们程序用一条线的方式按照顺序依次执行.
这个我打算整理完,再说,我也是一个刚学的新手😂😂