1.1 网络编程简介
其实,所谓的网络编程,就是编写程序,实现让同一个网络中的机器实现数据的传递,实现通信。
Java是 Internet 的语言,它从语言级上提供了对网络应用程序的支持。
Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制,并且Java 实现了一个跨平台的网络库,因此程序员面对的是一个统一的网络编程环境,很容易开发常见的网络应用程序。
1.2 网络编程需要具备的条件
如果要实现两台机器之间实现通信,必须要满足几点要求:
1、需要知道对方的 IP 地址。
2 、需要知道对方的哪一个端口号来做数据的接受。
3 、通信的双方,需要遵循相同的通信协议。
1.3 IP地址和端口号的简介
1.3.1 IP地址
IP是 Internet Protocol (网络互连协议),在计算机中,使用IP地址来描述一个上网终端的唯一的地址编 号。分为 IPv4 和 IPv6。
IPv4 : 使用4个字节来描述一个IP地址,由四部分组成,每一部分一个字节。
IPv6 : 使用6个字节来描述一个IP地址,由六部分组成,每一部分一个字节。
IP 地址的分类:
A类: 1.0.0.1 ~ 126.255.255.254 :保留给政府机构
B类: 128.0.0.1 ~ 191.255.255.254 :分配给大中型企业
C类: 192.0.0.1 ~ 223.255.255.254 :分配给任何有需要的个人
D类: 224.0.0.1 ~ 239.255.255.254 :用于组播
E类: 240.0.0.1 ~ 255.255.255.254 :用于实验
1.3.2 端口号的简介
端口是设备与外界进行通信的数据的出口。端口号的范围: [0,65535]。
常见的端口占用:
3306: MySQL
1521 :Oracle
8080 :Tomcat
1.4 通信协议
常用的通信协议有:TCP 、UDP 、HTTP;而OSI模型过于理想化,未能在因特网上进行广泛推广,在这里章节,我们着重讲解一下TCP协议。
1.4.1 TCP协议
是一个传输层的通信协议,是一个安全的,面向连接的通信协议。所谓的安全,指的是在数据传递的过程中,不会出现数据丢失的问题。 特点:安全的、面向连接。
TCP/IP 因其传输控制协议(TCP)和网络互联协议(IP)而得名。实际上是一组协议,其包含多个具有不同功能且互为关联的协议。
TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即网络接口层、IP层、传输层和应用层。下图表示了TCP/IP的分层结构和与OSI参考模型的对应关系:
。
1.4.2 TCP的三次握手
使用TCP实现数据在两个计算机之间进行传递,需要先在两个计算机之间建立一个安全的连接,然后再 通过这个连接,进行数据的传递。因此, TCP是一个安全的通信协议。
在使用TCP进行数据传递的时候,存在两个角色,客户端和服务端。
在TCP中,连接的建立过程会经历如下几个步骤:
-
第一次握手:
建立连接时,由客户端向服务端发送一个 syn 包, syn包中是同步序列编号, 并进入SYN_SENT 状态,并等待服务器确认。
-
第二次握手:
服务器收到了 syn 包,服务器必须确认客户端的syn,同时自己也发送一个syn包给客户端,即 SYN_ACK 包,并进入到 SYN_RECV 状态。
-
第三次握手:
客户端收到了服务端 SYN_ACK 包,并向服务端回复一个 ACK 包,此时客户端和服务端会进 入到TCP连接成功状态。 ESTABLISHED。
1.4.3 TCP与UDP的区别
运输层协议中有两个非常重要的协议:
-
传输控制协议TCP(Transmission Control Protocol)
-
用户数据报协议UDP(User Datagram Protocol)
TCP是面向连接的运输层协议。即应用进程(或程序)在使用TCP协议之前,必须先建立TCP连接,在传输完毕后,释放已经建立的连接。利用TCP协议进行通信的两个应用进程,一个是服务器进程。另一个是客户进程。
UDP是面向无连接的运输层协议。即应用进程(或程序)在使用UDP协议之前,不必先建立连接。自然,发送数据结束时也没有连接需要释放。因此,减少了开销和发送数据之前的时延。
二 TCP/IP程序编程
2.1 TCP/IP编程简介
2.1.1 C/S架构模型
CS架构由客户端和服务器两个部分组成,通常采用C/S模式进行通信。其中,客户端负责向用户提供界面和交互功能,而服务器则负责存储和处理数据。在这种架构中,客户端和服务器之间通过网络进行通信,客户端向服务器发出请求,服务器响应并返回相应的结果。
目前,CS架构已被广泛应用于各种场景中,如Web应用程序、电子邮件、数据库管理等,并且具有可扩展性、可靠性和安全性等优点。
TCP利用Socket(套接字)接口来实现C/S模型的网络程序开发,其早已被广泛的采用。通常,主动发起通信的应用程序属于客户端。而服务器则是等待通信请求,当服务器收到客户端的请求,执行需要的运算然后向客户端返回结果。
如我们使用QQ软件时,我们电脑上安装的就是一个客户端软件,而腾讯必需运行一个服务器。
2.1.2 Socket套接字介绍
socket被称为套接字,用于描述IP地址和端口号。主机上一般运行了多个服务软件,同时提供多种服务,每种服务都会打开一个Socket,并绑定到一个端口上,不同的端口对应不同的服务。
Socket和ServerSocket类位于java.net包中,ServerSocket用于服务端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,两个Socket构成一个通信管道。操作这个实例,完成所需要的通信。
套接字的基本操作有七个基本操作:
-
连接到远程主机
-
绑定到端口
-
接收从远程机器来的连接请求
-
监听到达的数据
-
发送数据
-
接收数据
-
关闭连接。
2.1.3 Socket编程步骤
1)服务端编程步骤如下:
-
调用 ServerSocket(int port) 创建一个服务器端套接字,并绑定到指定端口上。
-
调用 accept(),监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字。
-
调用 Socket类的 getOutputStream 和 getInputStream 获取输出流和输入流,开始网络数据的发送和接收。
-
最后关闭通信套接字。
2)客户段编程步骤如下:
-
创建 Socket。根据指定的 IP和port构造 Socket 类对象,并发送连接请求。如服务器端响应,则建立客户端到服务器的通信线路。
-
通过Socket获取与服务器的通信流。 使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流。
-
按照一定的协议对 Socket进行读/写操作。通过输入流读取服务端响应的信息,通过输出流将信息发送给服务端。
-
关闭 Socket。断开客户端到服务器的连接,释放线路
3)流连接
-
客户端和服务器端的套接字对象诞生以后,必须进行输入、输出流的连接。
-
套接字调用 close()可以关闭双方的套接字连接,只要一方关闭连接,就会导致对方发生IOException异常。
2.2 三个常用类的API
2.2.1 InetAddress
InetAddress,是一个用来描述IP地址的类;常用的两个子类,分别是Inet4Address 、Inet6Address 。
InetAddress的实例对象由一个IP地址和可能与之对应的主机名(域名)组成
互联网中的主机地址有两种表现形式: 域名: www.baidu.com IP地址: 110.242.68.4 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。
常用API
static InetAddress getLocalHost() 返回本地主机的地址。 static InetAddress getByName(String host) 确定主机名称的IP地址。 String getHostAddress() 返回文本显示中的IP地址字符串。 String getHostName() 获取此IP地址的主机名。
try { // 1、获取本机 InetAddress address1 = InetAddress.getLocalHost(); // 2、通过主机名字,获取一个主机 InetAddress address2 = InetAddress.getByName("懿范 "); // 3、通过IP地址字节数组,获取一个主机(需要注意字节数组中的每一个元素是否溢出) InetAddress address3 = InetAddress.getByAddress(new byte[] {10, 0, (byte)153, 27}); // 4、通过IP地址字符串获取一个主机 InetAddress address4 = InetAddress.getByName("10.0.153.27"); // 5、通过域名获取主机 InetAddress address5 = InetAddress.getByName("www.taobao.com"); // 6、获取一个域名对应的所有的主机 InetAddress[] addresses = InetAddress.getAllByName("www.taobao.com"); for (InetAddress address : addresses) { System.out.println(address); } // 通过 InetAddress 对象 // 1、获取主机名字 System.out.println(address1.getHostName()); // 2、获取主机地址 System.out.println(address1.getHostAddress()); // 3、获取一个IP地址字节数组 byte[] addr = address1.getAddress(); for (byte b : addr) { System.out.println(b); } } catch (UnknownHostException e) { e.printStackTrace(); }
2.1.2 Socket
是一个用来描述TCP中的客户端的类
-
常用构造器:
-
Socket(InetAddress address, int port)
-
Socket(String host, int port)
-
-
常用方法
-
InetAddress getLocalAddress()
返回对方Socket中的IP的InetAddress对象
-
int getLocalPort()
返回本地Socket中的端口号
-
InetAddress getInetAddress()
返回对方Socket中IP地址
-
int getPort()
返回对方Socket中的端口号
-
void close() throws IOException
关闭Socket,释放资源
-
InputStream getInputStream() throws IOException
获取与Socket相关联的字节输入流,用于从Socket中读数据。
-
OutputStream getOutputStream() throws IOException
获取与Socket相关联的字节输出流,用于向Socket中写数据。
-
2.1.3 ServerSocket
是一个用来描述TCP中的服务端的类
-
常用构造器
-
ServerSocket(int port)
-
-
常用方法
-
Socket accept() throws IOException
等待客户端的连接请求,返回与该客户端进行通信用的Socket对象
-
void close()throws IOException
关闭监听Socket
-
InetAddress getInetAddress()
返回此服务器套接字的本地地址
-
int getLocalPort()
返回此套接字在其上监听的端口号
-
SocketAddress getLocalSocketAddress()
返回此套接字绑定的端点的地址
-
void setSoTimeout(int timeout) throws SocketException
设置accept()方法等待连接的时间为timeout毫秒。若时间已到,还没有客户端连接,则抛出InterruptedIOException异常,accept()方法不再阻塞,该倾听Socket可继续使用。若timeout值为0,则表示accept()永远等待。该方法必须在倾听Socket创建后,在accept()之前调用才有效。
-
三 聊天小项目
3.1 第一个小版本
客户端向服务端发送消息,服务端打印消息
3.1.1 服务端代码:
package com.se.day12.chat; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * 聊天服务器 */ public class ChatServer { //定义一个TCP通信协议的服务端属性 private ServerSocket server; public ChatServer(int port) { try { server = new ServerSocket(port); } catch (IOException e) { System.out.println("---服务器启动失败---"); } } /** * 聊天的主方法 */ public void start() { try { System.out.println("---等待客户端连接---"); Socket socket = server.accept(); System.out.println("---一个客户端连接上了---"); //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据 InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(inputStream,"utf-8")); String info = br.readLine(); System.out.println("客户端发送过来的信息:"+info); } catch (IOException e) { System.out.println("---一个客户端离线了---"); } } public static void main(String[] args) { //创建一个具体的服务器对象 ChatServer chatServer = new ChatServer(8888); //调用聊天的主方法 chatServer.start(); } }
3.1.2 客户端代码:
package com.se.day12.chat; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; /** * 聊天客户端 */ public class ChatClient { //定义一个TCP通信协议的客户端属性 private Socket socket; public ChatClient() { //获取Socket对象,同时向服务端发送连接请求, 连接失败:Connection reset try { socket = new Socket("localhost",8888); } catch (IOException e) { System.out.println("---服务器崩溃中---"); } } // 聊天的主方法 public void start(){ PrintWriter out = null; try { //获取向服务器发送信息的输出流对象 OutputStream outputStream = socket.getOutputStream(); //封装成字符缓存流,可以按行输出 out = new PrintWriter( new OutputStreamWriter(outputStream,"UTF-8"), true); out.println("我是客户端"); } catch (IOException e) { e.printStackTrace(); } finally { out.close(); } } public static void main(String[] args) { //创建一个具体的聊天客户端对象 ChatClient client = new ChatClient(); //调用客户端的主方法 client.start(); } }
3.2 第二个小版本
客户端循环向服务端发送信息,服务端负责打印
3.2.1 服务端的代码:
package com.se.day12.chat; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * 聊天服务器 */ public class ChatServer { //定义一个TCP通信协议的服务端属性 private ServerSocket server; public ChatServer(int port) { try { server = new ServerSocket(port); } catch (IOException e) { System.out.println("---服务器启动失败---"); } } /** * 聊天的主方法 */ public void start() { try { System.out.println("---等待客户端连接---"); Socket socket = server.accept(); System.out.println("---一个客户端连接上了---"); //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据 InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(inputStream,"utf-8")); //循环打印客户端发送过来的信息 String info = ""; while ((info = br.readLine()) != null) { System.out.println("客户端发送过来的信息:"+info); } } catch (IOException e) { System.out.println("---一个客户端离线了---"); } } public static void main(String[] args) { //创建一个具体的服务器对象 ChatServer chatServer = new ChatServer(8888); //调用聊天的主方法 chatServer.start(); } }
3.2.2 客户端的代码
package com.se.day12.chat; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; /** * 聊天客户端 */ public class ChatClient { //定义一个TCP通信协议的客户端属性 private Socket socket; public ChatClient() { //获取Socket对象,同时向服务端发送连接请求, 连接失败:Connection reset try { socket = new Socket("localhost",8888); } catch (IOException e) { System.out.println("---服务器崩溃中---"); } } // 聊天的主方法 public void start(){ PrintWriter out = null; try { //获取向服务器发送信息的输出流对象 OutputStream outputStream = socket.getOutputStream(); //封装成字符缓存流,可以按行输出 out = new PrintWriter( new OutputStreamWriter(outputStream,"UTF-8"), true); //使用控制台扫描对象类型,不断的扫描控制台上的文字 Scanner scan = new Scanner(System.in); System.out.println("====开始聊天===="); while(true){ String info = scan.nextLine(); //将扫描的信息发送到服务端 out.println(info); } } catch (IOException e) { e.printStackTrace(); } finally { out.close(); } } public static void main(String[] args) { //创建一个具体的聊天客户端对象 ChatClient client = new ChatClient(); //调用客户端的主方法 client.start(); } }
3.3 第三个小版本
客户端收到服务端转发的消息
3.3.1 服务端代码
package com.se.day12.chat; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * 聊天服务器 */ public class ChatServer { //定义一个TCP通信协议的服务端属性 private ServerSocket server; public ChatServer(int port) { try { server = new ServerSocket(port); } catch (IOException e) { System.out.println("---服务器启动失败---"); } } /** * 聊天的主方法 */ public void start() { try { System.out.println("---等待客户端连接---"); Socket socket = server.accept(); System.out.println("---一个客户端连接上了---"); //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据 InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(inputStream,"utf-8")); //通过服务端的Socket对象,获取输出流,将信息返回给客户端 OutputStream outputStream = socket.getOutputStream(); PrintWriter pw = new PrintWriter( new OutputStreamWriter(outputStream,"utf-8"),true); //循环打印客户端发送过来的信息 String info = ""; while ((info = br.readLine()) != null) { //使用输出流,将其返回即可 pw.println(info); } } catch (IOException e) { System.out.println("---一个客户端离线了---"); } } public static void main(String[] args) { //创建一个具体的服务器对象 ChatServer chatServer = new ChatServer(8888); //调用聊天的主方法 chatServer.start(); } }
3.2.2 客户端代码
package com.se.day12.chat; import java.io.*; import java.net.Socket; import java.util.Scanner; /** * 聊天客户端 */ public class ChatClient { //定义一个TCP通信协议的客户端属性 private Socket socket; public ChatClient() { //获取Socket对象,同时向服务端发送连接请求, 连接失败:Connection reset try { socket = new Socket("localhost",8888); } catch (IOException e) { System.out.println("---服务器崩溃中---"); } } // 聊天的主方法 public void start(){ try { //获取一个获取服务端信息的处理器任务 Runnable task = new GetServerInfoHandler(); Thread thread = new Thread(task); thread.start(); //获取向服务器发送信息的输出流对象 OutputStream outputStream = socket.getOutputStream(); //封装成字符缓存流,可以按行输出 PrintWriter out = new PrintWriter( new OutputStreamWriter(outputStream,"UTF-8"), true); //使用控制台扫描对象类型,不断的扫描控制台上的文字 Scanner scan = new Scanner(System.in); System.out.println("====开始聊天===="); while(true){ String info = scan.nextLine(); //将扫描的信息发送到服务端 out.println(info); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { //创建一个具体的聊天客户端对象 ChatClient client = new ChatClient(); //调用客户端的主方法 client.start(); } /** * 编写一个获取服务端信息的处理器。即一个任务体 */ class GetServerInfoHandler implements Runnable{ public void run(){ try { //获取接受服务端发送过来的数据的输入流对象 InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(inputStream,"utf-8")); String line = ""; while((line = br.readLine()) != null){ System.out.println("服务端返回的数据:"+line); } } catch (IOException e) { e.printStackTrace(); } } } }
3.4 第四个版本
客户端多开,客户端自己和自己聊天
3.4.1 服务端代码(代码有更新)
package com.se.day12.chat; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * 聊天服务器 */ public class ChatServer { //定义一个TCP通信协议的服务端属性 private ServerSocket server; public ChatServer(int port) { try { server = new ServerSocket(port); } catch (IOException e) { System.out.println("---服务器启动失败---"); } } /** * 聊天的主方法 */ public void start() { try { while (true) { System.out.println("---等待客户端连接---"); Socket socket = server.accept(); System.out.println("---一个客户端连接上了---"); //每获取一个客户端的Socket对象,就应该将其放入一个并发线程中 Runnable task = new GetClientInfoHandler(socket); Thread thread = new Thread(task); thread.start(); } } catch (IOException e) { System.out.println("---一个客户端离线了---"); } } public static void main(String[] args) { //创建一个具体的服务器对象 ChatServer chatServer = new ChatServer(8888); //调用聊天的主方法 chatServer.start(); } /** * 定义一个处理客户端信息的处理器,即一个任务体 */ class GetClientInfoHandler implements Runnable { //因为run方法中使用了 start方法中的socket局部变量 //所以可以在该类中添加一个属性,run方法中可以访问该类的属性 private Socket socket; //提供一个构造器,将start方法中的局部变量socket传进来,给属性赋值, 这样,run方法中使用的socket就是 //start方法中的局部变量指向的对象 public GetClientInfoHandler(Socket socket) { this.socket = socket; } @Override public void run() { try { //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据 InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(inputStream,"utf-8")); //通过服务端的Socket对象,获取输出流,将信息返回给客户端 OutputStream outputStream = socket.getOutputStream(); PrintWriter pw = new PrintWriter( new OutputStreamWriter(outputStream,"utf-8"),true); //循环打印客户端发送过来的信息 String info = ""; while ((info = br.readLine()) != null) { //使用输出流,将其返回即可 pw.println(info); } } catch (IOException e) { e.printStackTrace(); } } } }
3.4.2 客户端代码
客户端代码没有更新,参考上一个版本即可
3.5 第五个版本
一个客户端与多个客户端对话,同时处理某一个客户端下线的问题
3.5.1 服务端代码
package com.se.day12.chat; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * 聊天服务器 */ public class ChatServer { //定义一个TCP通信协议的服务端属性 private ServerSocket server; //添加一个Map属性,用于存储多个客户端的标识与输出流对象。 Map集合(散列表) private Map<String,PrintWriter> allOut; public ChatServer(int port) { try { server = new ServerSocket(port); //给map集合赋值 allOut = new HashMap<String,PrintWriter>(); } catch (IOException e) { System.out.println("---服务器启动失败---"); } } /** * 聊天的主方法 */ public void start() { try { while (true) { System.out.println("---等待客户端连接---"); Socket socket = server.accept(); System.out.println("---一个客户端连接上了---"); //每获取一个客户端的Socket对象,就应该将其放入一个并发线程中 Runnable task = new GetClientInfoHandler(socket); Thread thread = new Thread(task); thread.start(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { //创建一个具体的服务器对象 ChatServer chatServer = new ChatServer(8888); //调用聊天的主方法 chatServer.start(); } /** * 定义一个处理客户端信息的处理器,即一个任务体 */ class GetClientInfoHandler implements Runnable { //因为run方法中使用了 start方法中的socket局部变量 //所以可以在该类中添加一个属性,run方法中可以访问该类的属性 private Socket socket; //提供一个构造器,将start方法中的局部变量socket传进来,给属性赋值, 这样,run方法中使用的socket就是 //start方法中的局部变量指向的对象 public GetClientInfoHandler(Socket socket) { this.socket = socket; } @Override public void run() { String nickname = null; try { //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据 InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(inputStream,"utf-8")); //通过服务端的Socket对象,获取输出流,将信息返回给客户端 OutputStream outputStream = socket.getOutputStream(); PrintWriter pw = new PrintWriter( new OutputStreamWriter(outputStream,"utf-8"),true); //先获取客户端的昵称 nickname = br.readLine(); //判断是否已经被占用,如果已经被占用,加后缀 while(allOut.containsKey(nickname)){ nickname = nickname+Math.random(); } //将昵称告诉客户端 pw.println(nickname); //将输出流添加到Map集合中 allOut.put(nickname,pw); System.out.println("-----在线人数:"+allOut.size()+"--------"); //循环打印客户端发送过来的信息 String info = ""; while ((info = br.readLine()) != null) { //将信息info发送到所有的客户端里 sendToAllClient(nickname,info); } } catch (IOException e) { System.out.println("---一个客户端离线了---"); //删除对应的键值对 allOut.remove(nickname); System.out.println("-----在线人数:"+allOut.size()+"--------"); } } } public void sendToAllClient(String nickname, String info) { //遍历Map里的所有输出流,来发送info信息 Set<String> nicks = allOut.keySet(); for (String nick : nicks) { //获取对应的输出流,然后发送信息 PrintWriter writer = allOut.get(nick); writer.println(nickname+"说:"+info); } } }
3.5.2 客户端代码
package com.se.day12.chat; import java.io.*; import java.net.Socket; import java.util.Scanner; /** * 聊天客户端 */ public class ChatClient { //定义一个TCP通信协议的客户端属性 private Socket socket; public ChatClient() { //获取Socket对象,同时向服务端发送连接请求, 连接失败:Connection reset try { socket = new Socket("localhost",8888); } catch (IOException e) { System.out.println("---服务器崩溃中---"); } } // 聊天的主方法 public void start(){ try { //获取一个获取服务端信息的处理器任务 Runnable task = new GetServerInfoHandler(); Thread thread = new Thread(task); thread.start(); //获取向服务器发送信息的输出流对象 OutputStream outputStream = socket.getOutputStream(); //封装成字符缓存流,可以按行输出 PrintWriter out = new PrintWriter( new OutputStreamWriter(outputStream,"UTF-8"), true); //使用控制台扫描对象类型,不断的扫描控制台上的文字 Scanner scan = new Scanner(System.in); //先设置昵称 System.out.println("请输入您的昵称:"); String nickname = scan.nextLine(); //将昵称发送给服务器,让服务器校验昵称是否已经被占用 out.println(nickname); System.out.println("====开始聊天===="); while(true){ String info = scan.nextLine(); //将扫描的信息发送到服务端 out.println(info); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { //创建一个具体的聊天客户端对象 ChatClient client = new ChatClient(); //调用客户端的主方法 client.start(); } /** * 编写一个获取服务端信息的处理器。即一个任务体 */ class GetServerInfoHandler implements Runnable{ public void run(){ try { //获取接受服务端发送过来的数据的输入流对象 InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(inputStream,"utf-8")); //获取自己的昵称: String nickname = br.readLine(); System.out.println("您的昵称:"+nickname); String line = ""; while((line = br.readLine()) != null){ System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } }