介绍了Scoekt的概念,并且提供了基于TCP和UDP协议的Java Socket API编写的简单通信程序,比如简易的聊天室。

此前我们简单的学了各种协议,我们知道大部分的应用层协议,比如HTTP、FTP、SMTP、POP3等,它们都依赖于下层传输层的TCP/UDP协议进行数据传输,因此实际上我们可以直接使用TCP/UDP协议进行网络通信,并且大部分语言都已经提供了现成的一套TCP/UDP编程API,那就是Scoket。下面简单的学习可以不依赖于应用层协议进行网络通信的Socket编程。

文章目录

  • 1 Socket概述
  • 2 Socket通信
  • 3 使用UDP协议通信
    • 3.1 相关类
      • 3.1.1 InetAddress ip地址的类
        • 3.1.1.1 获得InetAddress对象
        • 3.1.1.2 获得本机Ip和主机名
      • 3.1.2 DatagramSocket 数据报套接字类
        • 3.1.2.1 构造器
        • 3.1.2.2 API方法
      • 3.1.3 DatagramPacket 数据报包类
      • 1.3.1 构造器
        • 3.1.3.2 API方法
    • 3.2 基本案例
      • 3.2.1 UDP发送端
      • 3.2.2 UDP接收端
    • 3.3 UDP实现简易的聊天室
  • 4 使用TCP协议通信
    • 4.1 相关类
      • 4.1.1 Socket套接字类
        • 4.1.1.1 构造器
        • 4.1.1.2 API方法
      • 4.1.2 ServerSocket 服务器套接字类
    • 4.2 基本案例
      • 4.2.1 TCP服务端
      • 4.2.2 TCP客户端
    • 4.3 文本上传
    • 4.4 图片上传
    • 4.5 TCP实现简易的多人聊天室

1 Socket概述

Socket翻译成中文就是套接字。它是对TCP/IP协议包括下层各种协议的封装,Socket本身并不是协议,而是一个接口(API),它只是提供了一个针对TCP/UDP编程的接口它将复杂的TCP/UDP协议的各种操作隐藏起来,我们只需要遵循Socket的开发规定去编程,写成的程序自然遵循TCP/UDP协议,自然就能够是进行两台计算机相互通信,这类似于设计模式中的门面模式!

即,Socket隐藏了各种TCP/IP协议的交互细节,提供了需要针对TCP/UDP编程的各种高级语言的上层接口,可以接收请求和发送响应,实现不同计算机之间的通信。实际上,底层协议本来就提供了可以进行网络编程的接口,但是太底层了,对于很多程序员不友好,特别是使用Java等上层语言的程序员,因此,出现了Scoket接口以及它的相关API,Scoket对于这些底层协议的接口进行了进一步封装,并且通过高级语言的API开放出来,对于需要进行网络编程的普通程序员来说更加友好。

不同的语言都提供了Socket API实现,Java也有,比如java.net.Socket、java.net.DatagramSocket等类。

Java的Socket套接字有如下分类:

  1. 流式套接字(SOCK_STREAM):流式套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP协议。
  2. 数据报套接字(SOCK_DGRAM):数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据,但是传输效率较高。数据报套接字使用UDP协议进行数据的传输。
  3. 原始套接字:
    1. 流式套接字和数据报套接字这两种套接字工作在传输层,主要为应用层的应用程序提供服务,并且在接收和发送时只能操作数据部分,而不能对IP首部或TCP和UDP首部进行操作,通常把这两种套接字称为标准套接字。
    2. 原始套接字工作在网络层,主要用于一些更底层的协议的开发,可以进行比较底层的操作,比如发送一个自定义的IP包、UDP包、TCP包或ICMP包。它功能强大,但是没有上面介绍的两种套接字使用方便,一般的程序也涉及不到原始套接字。原始套接字可以在链路层收发数据帧。

2 Socket通信

Socket包含了进行网络通信必需的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

Socket是对TCP/IP协议的封装,因此,根据我们前面学习的通信协议,最简单的Socket编程并没有使用上层——应用层的相关协议,比如HTTP、SMTP、POP3等等。因为我们在传输数据时,可以只使用传输层及其以下的协议,而不使用应用层的协议。

如果Socket是使用TCP协议进行通信,那么Socket程序同样会涉及到TCP连接的三次握手和四次挥手,基于TCP的Socket通信流程图如下:

虽然我们可以直接使用Socket进行通信,可以互相传输到数据,但是Socket传输或者接收的数据都是的byte字节数据,如果没有应用层及其相关协议,我们无法识别传递的字节数据对应的原始数据本身的类型、内容,格式等等,这样就无法将byte字节数据还原成原始的数据。

因此,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,它们用于传输不同的数据,为不同的应用服务。我们的Web应用更多的是使用HTTP协议作应用层协议,以封装HTTP报文信息,然后使用TCP/IP做传输层协议将它发到服务器或者客户端。

上层应用程序/协议的通信需要依靠的下层的协议,如果我们需要使用应用层协议进行通讯。那么该怎么办呢?实际上,Socket已经提供了对接上层应用层协议的接口,并且JDK已经为我们提供好了对于上层协议的封装类,比如HttpURLConnection、URL、HttpClient等等基于HTTP协议封装的API,又比如基于FTP协议封装的FtpClient工具类,又比如基于SMTP协议封装的SmtpClient工具类……。它们的底层基本上最终还是调用了JDK的Socket的API进行数据传输,这些应用层协议的工具类,需要做的就是将数据编码通过Socket传输,或者将接收到的数据以自身指定的格式解码。

实际上,就目前而言,几乎所有的语言的Web应用程序的底层都是采用Socket进行通信的。

3 使用UDP协议通信

使用UDP协议进行数据的传输,主要是用到两个类,一个是DatagramSocket,另一个就是DatagramPacket。

3.1 相关类

3.1.1 InetAddress ip地址的类

public class InetAddress
extends Object
implements Serializable

位于java.net包当中,此类表示互联网协议 (IP) 地址的抽象,InetAddress对象封装了ip地址。
IP 地址是IP协议使用的32位或128位无符号数字,IP协议是一种更加低级协议,UDP 和 TCP 协议都是在它的基础上构建的。
无构造方法。

3.1.1.1 获得InetAddress对象

public static InetAddress getLocalHost()

返回本地主机的IP地址。获得InetAddress对象。由主机名/ip地址组成。比如:DESKTOP-8Q842HN/192.168.253.1

public static InetAddress getByName(String host)

在给定主机名的情况下确定主机的 IP 地址。获得InetAddress对象。

主机名可以传递机器名如”DESKTOP-8Q842HN”,返回由 机器名/ip地址组成的InetAddress对象:ESKTOP-8Q842HN/192.168.253.1

也可以是传入IP地址的文本表示形式如”192.168.253.1”,返回由/ip地址组成的InetAddress对象。但是任然可以使用该对象通过方法获得主机名:/192.168.253.1

还可以传入域名,例如”www.baidu.com”。返回由域名/ip地址组成的InetAddress对象:www.baidu.com/119.75.217.109

3.1.1.2 获得本机Ip和主机名

String getHostAddress() 获得String类型的ip地址。返回字符串格式的原始 IP 地址。
String getHostName() 返回此 IP 地址的主机名;如果安全检查不允许操作,则返回 IP 地址的文本表示形式。
String toString() 返回的字符串具有以下形式:主机名/字面值IP地址。

另外,还有一个InetSocketAddress类,它表示IP 套接字地址(IP 地址 + 端口号),它还可以是一个对(主机名 + 端口号),在此情况下,将尝试解析主机名。如果解析失败,则该地址将被视为未解析地址,但是其在某些情形下仍然可以使用,比如通过代理连接。

3.1.2 DatagramSocket 数据报套接字类

public class DatagramSocket
extends Object

位于java.net包。此类表示用来发送和接收数据报包的套接字,又称数据报套接字。数据报套接字是包投递服务的发送或接收点,使用UDP协议发送数据包。

3.1.2.1 构造器

DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
DatagramSocket(int port)
port -表示要使用的端口。
创建数据报套接字并将其绑定到本地主机上的指定端口。

3.1.2.2 API方法

void send(DatagramPacket p) 从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
void receive(DatagramPacket p) 从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区已经填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。此方法在接收到数据报包前一直阻塞。
InetAddress getLocalAddress() 返回套接字绑定的本地地址,如果套接字没有绑定则返回表示任何本地地址的InetAddress。
int getPort() 返回此套接字的端口。如果套接字未连接,则返回 -1。

3.1.3 DatagramPacket 数据报包类

public final class DatagramPacket
extends Object

此类表示数据报包。数据报包用来实现无连接包投递服务。发送的多个数据报包不能保证达到顺序也不能保证完整性。发送次数必须和接收次数一致,否则将会丢失数据。

1.3.1 构造器

DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port);
buf - 包数据。
offset - 包数据偏移量。
length - 包数据长度。
address - 目的地址。
port - 目的端口号。
构造数据报包,用来将长度为length偏移量为offset的包发送到指定主机上的指定端口号。length参数必须小于等于buf.length。
DatagramPacket(byte[] buf,int length)
buf - 保存传入数据报的缓冲区。
len - 要读取的字节数。
构造 DatagramPacket,用来接收长度为length的数据包。length参数必须小于等于buf.length。

3.1.3.2 API方法

InetAddress getAddress() 返回某台机器的IP地址,此数据报将要发往该机器或者是从该机器接收到的。
int getPort() 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
byte[] getData() 返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量 offset 处开始,持续 length 长度。
int getLength() 返回将要发送或接收到的数据的长度。
int getOffset() 返回将要发送或接收到的数据的偏移量。

3.2 基本案例

3.2.1 UDP发送端

public class Sender {public static void main(String[] args) throws IOException, IOException {//1.创建发送端数据报包套接字socket,用来发送数据报包。如果指定一个端口号,指定的是发送端的端口号;如果不指定端口号,系统会默认分配一个。DatagramSocket ds = new DatagramSocket();//2.构造数据报包,包括:发送的数据的字节数组,起始索引,数据长度,指定远程主机的ip地址[InetAddress对象],以及远程主机上的端口号.(这里就发送到本机演示)DatagramPacket dp = new DatagramPacket("你好".getBytes(), 0, "你好".getBytes().length, InetAddress.getLocalHost(), 8888);//3.发送数据报包ds.send(dp);//4.关闭套接字socketds.close();}
}

3.2.2 UDP接收端

public class Receiver {public static void main(String[] args) throws IOException {//创建接收端数据报包套接字socket,必须指定接收端端口号DatagramSocket ds = new DatagramSocket(8888);while (true) {    //循环接收数据//构造空数据报包,用来接收数据:内部使用字节数组作为接收数据的缓冲区,可以指定起始索引和要读取的字节数.//如果发送的数据量大于接收空间的大小,那么数据将会丢失byte[] by = new byte[1024];DatagramPacket dp = new DatagramPacket(by, 0, by.length);//接收数据:将数据存入数据报包中.在接收到数据前,此方法将一直堵塞!ds.receive(dp);//打开数据报包,获得数据缓冲区数组,这里将会获取一次发送的全部数据byte[] data = dp.getData();//dp.getLength(),是指的接收到的数据的长度。System.out.println("data: " + new String(data, 0, dp.getLength()));//获得发送端ip地址String hostName = dp.getAddress().getHostName();System.out.println("hostName: " + hostName);//获得发送端主机名String hostAddress = dp.getAddress().getHostAddress();System.out.println("hostAddress: " + hostAddress);//获得发送端端口号int port = dp.getPort();System.out.println("port: " + port);}//ds.close(); 接收端应该一直开着接收数据}
}

3.3 UDP实现简易的聊天室

接收服务:

public class ReceiveServer implements Runnable {private final DatagramSocket dsReceive;public ReceiveServer(DatagramSocket dsReceive) {this.dsReceive = dsReceive;}@Overridepublic void run() {while (true) {//构造空数据报包,用来接收数据:内部使用字节数组作为接收数据的缓冲区,可以指定起始索引和要读取的字节数.byte[] by = new byte[1024];DatagramPacket dp = new DatagramPacket(by, 0, by.length);//接收数据:将数据存入数据报包中.在接收到数据前,此方法将一直堵塞!try {dsReceive.receive(dp);} catch (IOException e) {e.printStackTrace();}//打开数据报包,获得数据缓冲区数组byte[] byteData = dp.getData();//dp.getLength(),是指的接收到的数据的长度。String data = new String(byteData, 0, dp.getLength());//获得时间String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));//获得发送端主机IPString hostAddress = dp.getAddress().getHostAddress();//获得发送端口号int port = dp.getPort();System.out.println(time + "---" + hostAddress + ": " + port);System.err.println(data);}}
}

发送服务:

public class SendServer implements Runnable {private final DatagramSocket dsSend;private final InetSocketAddress inetSocketAddress;public SendServer(DatagramSocket dsSend, InetSocketAddress inetSocketAddress) {this.dsSend = dsSend;this.inetSocketAddress = inetSocketAddress;}@Overridepublic void run() {try {//接收键盘录入的数据BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String str;while ((str = br.readLine()) != null) {//构造数据报包,包括:发送的数据的字节数组,起始索引,长度,指定远程ip,以及远程ip上的端口号.DatagramPacket dp = new DatagramPacket(str.getBytes(), 0, str.getBytes().length, inetSocketAddress);//发送数据报包dsSend.send(dp);//定义结束语句if (str.equals("886")) {break;}}} catch (IOException e) {e.printStackTrace();} finally {//关闭套接字socketdsSend.close();}}
}

客户端1:

public class ChatClient1 {public static void main(String[] args) throws SocketException, UnknownHostException {//发送服务,发送到指定Ip和端口的接收服务中。这里的ip就是本机,端口为9999String hostName = InetAddress.getLocalHost().getHostName();InetSocketAddress inetSocketAddress = new InetSocketAddress(hostName, 9999);SendServer st = new SendServer(new DatagramSocket(), inetSocketAddress);//接收服务,接收端口号为9999ReceiveServer rt = new ReceiveServer(new DatagramSocket(8888));new Thread(st).start();new Thread(rt).start();}
}

客户端2:

public class ChatClient2 {public static void main(String[] args) throws SocketException, UnknownHostException {//发送服务,发送到指定Ip和端口的接收服务中。这里的ip就是本机,端口为8888String hostName = InetAddress.getLocalHost().getHostName();InetSocketAddress inetSocketAddress = new InetSocketAddress(hostName, 8888);SendServer st = new SendServer(new DatagramSocket(), inetSocketAddress);//接收服务,接收端口号为9999ReceiveServer rt = new ReceiveServer(new DatagramSocket(9999));new Thread(st).start();new Thread(rt).start();}
}

4 使用TCP协议通信

注意:使用TCP传输,一定要先开启服务器,因为传输的数据一定要保证被收到,否则抛出异常。而UDP本来就不保证数据被收到,因此即使先开启了发送端,也不会报错!

使用TCP协议进行数据的传输,主要是用Socket和ServerSocket,以及输入、输出流!

4.1 相关类

4.1.1 Socket套接字类

public class Socket
extends Object

Java中的Socket类,专门用于TCP请求。

套接字是两台机器间通信的端点。此类实现客户端套接字,并且该类套接字是基于TCP协议的,数据是通过流传输的,因此又称流套接字。网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。

通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO流传输。

4.1.1.1 构造器

Socket(String host,int port)
host - 主机名(字符串类型的IP地址),表示服务端字符串IP。
port - (应用程序)端口号。
创建一个流套接字并将其连接到指定主机上的指定(应用程序)端口号。
Socket(InetAddress address,int port)
address – InetAddress类的服务端IP 地址。
port - (应用程序)端口号。
创建一个流套接字并将其连接到指定IP地址的指定端口号。

4.1.1.2 API方法

OutputStream getOutputStream() 返回此套接字的输出流。
InputStream getInputStream() 返回此套接字的输入流。如果未读取到对方发送的数据,此方法将一直阻塞。
InetAddress getInetAddress() 返回套接字连接的地址。
InetAddress getLocalAddress() 获取套接字绑定的本地地址。
int getPort() 返回此套接字连接到的远程端口。
int getLocalPort() 返回此套接字绑定到的本地端口。
void shutdownInput() 此套接字的输入流置于“流的末尾”。发送到套接字的输入流端的任何数据都将被确认然后被静默丢弃。
void shutdownOutput() 禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。
void close() 关闭此套接字。

4.1.2 ServerSocket 服务器套接字类

public class ServerSocket
extends Object

此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

构造器:

ServerSocket(int port) 创建绑定到特定端口的服务器(应用程序)套接字。

API方法:

Socket accept() 侦听并接受到此套接字的连接。此方法在成功被客户端连接并返回之前在一直阻塞,将返回客户端套接字,通过该返回的客户端套接字可以接收客户端发送过来的数据,或者给客户端发送响应信息
void close() 关闭此套接字。

4.2 基本案例

服务器接收到客户端的数据,然后,响应给客户端一个数据。

4.2.1 TCP服务端

public class Server {public static void main(String[] args) throws IOException {//1.创建服务器socket,并绑定端口号ServerSocket ss = new ServerSocket(8888);//2.监听客户端连接,返回对应的socket对象.此方法在成功被客户端连接并返回之前一直阻塞!Socket a = ss.accept();//获得客户端主机名,ip地址,端口InetAddress ia = a.getInetAddress();System.out.println("client :" + ia.getHostAddress() + ": " + a.getPort());//创建输入流,使用read()读取数据,如果未读取到对方发送的数据,此方法将一直阻塞!InputStream is = a.getInputStream();byte[] b = new byte[1024];int read = is.read(b);System.out.println("from client: " + new String(b, 0, read));//给客户端响应,获得输出流,发送数据OutputStream os = a.getOutputStream();os.write("已经收到".getBytes());os.flush();//释放获得的socket资源,服务器socket不应该关闭a.close();}
}

4.2.2 TCP客户端

public class Client {public static void main(String[] args) throws IOException {//1.创建客户端socket,并将其连接到指定 IP 地址的指定端口号。Socket s = new Socket(InetAddress.getLocalHost(), 8888);//等待4秒再发送消息LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(4));//2.获得输出流,写数据OutputStream os = s.getOutputStream();os.write("你好,收到数据了吗?".getBytes());os.flush();//socket.shutdownOutput();   服务端循环接收时,需要用此方法关闭//获得输入流,使用read()读取服务器的响应,在读取到数据之前,此方法一直阻塞!InputStream is = s.getInputStream();byte[] by = new byte[1024];int read;while ((read = is.read(by)) != -1) {System.out.println("from server: " + new String(by, 0, read));}//关闭客户端,释放资源s.close();}
}

4.3 文本上传

客户端:上传一个文本,服务端:保存起来并响应!

客户端:

public class TxtClient {public static void main(String[] args) throws IOException {Socket s = new Socket(InetAddress.getLocalHost(), 8888);//文本读入流,读取文本所在的位置BufferedReader br = new BufferedReader(new FileReader("E:\\Idea\\Java-EE\\WebProgram\\src\\main\\resources\\a.txt"));//客户端输出流OutputStream os = s.getOutputStream();PrintWriter pw = new PrintWriter(os);String str;while ((str = br.readLine()) != null) {//将文本数据写入到客户端输出流中,发送给服务器pw.println(str);pw.flush();}s.shutdownOutput();/** 接收服务器响应*///客户端输入流InputStream is = s.getInputStream();BufferedReader br1 = new BufferedReader(new InputStreamReader(is));String str1;while ((str1 = br1.readLine()) != null) {System.out.println(str1);}s.close();}
}

服务器:

public class TxtServer {public static void main(String[] args) throws IOException {ServerSocket ss = new ServerSocket(8888);Socket a = ss.accept();//文件输出流,指定上传的文件名和路径PrintWriter pw = new PrintWriter("E:\\Idea\\Java-EE\\WebProgram\\src\\main\\resources\\b.txt");//服务端输入流InputStream is = a.getInputStream();//转换为缓冲流BufferedReader br = new BufferedReader(new InputStreamReader(is));String str;System.out.println("文件内容为:");while ((str = br.readLine()) != null) {System.out.println(str);//服务器将数据保存到指定的地方pw.println(str);pw.flush();}/** 给客户端响应*///服务端输出流PrintWriter pw1 = new PrintWriter(a.getOutputStream());pw1.println("-----------");pw1.println("文件已上传");pw1.flush();a.close();}
}

4.4 图片上传

服务端使用多线程技术处理客户端请求,防止同时出现多个客户端的请求时发生阻塞。

客户端:

public class PicClient {public static void main(String[] args) throws IOException {String filePath = "E:\\Idea\\Java-EE\\WebProgram\\src\\main\\resources\\QQ图片20201123093907.png";savePic(filePath);}static void savePic(String filePath) throws IOException {File pictureFile = new File(filePath);if (!pictureFile.exists()) {System.out.println("你上传的文件不存在");return;}if (!pictureFile.isFile()) {System.out.println("你上传的不是一个文件");return;}if (!pictureFile.getName().endsWith(".jpg")) {System.out.println("你上传的不是一个jpg格式文件");return;}if (pictureFile.length() > 1024 * 1024 * 3) {System.out.println("上传图片大小超过限制,最大不超过3M");return;}//创建客户端Socket s = new Socket(InetAddress.getLocalHost(), 8888);//准备一个输入流,用来读取图片BufferedInputStream bis = new BufferedInputStream(new FileInputStream(pictureFile));//准备一个客户端输出流用来传输数据BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());//准备一个客户端输入流用来接收服务端的响应数据BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));//发送图片数据byte[] by = new byte[1024];int len;while ((len = bis.read(by)) != -1) {bos.write(by, 0, len);bos.flush();}s.shutdownOutput();//接收服务端响应String readLine = br.readLine();System.out.println(readLine);}
}

服务端:

public class PicServer implements Runnable {public static void main(String[] args) throws IOException {ServerSocket ss = new ServerSocket(8888);while (true) {//accept方法在传入链接之前一直堵塞,因此不会无限循环Socket a = ss.accept();//使用多线程处理客户端连接,防止客户端的请求阻塞THREAD_POOL_EXECUTOR.submit(new PicServer(a));}}private Socket socket;public PicServer(Socket socket) {this.socket = socket;}static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());@Overridepublic void run() {//客户端已连接反馈System.out.println(socket.getInetAddress().getHostName() + "已连接");//文件命名uuidString fileName = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();try {//客户端输入流BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());//文件输出流BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(("E:\\Idea\\Java-EE\\WebProgram\\src\\main\\resources\\" + fileName + ".jpg")));byte[] b = new byte[1024];int read;while ((read = bis.read(b)) != -1) {bos.write(b, 0, read);bos.flush();}OutputStream os = socket.getOutputStream();//客户端输出流PrintWriter pw = new PrintWriter(os);pw.println(socket.getLocalAddress().getHostName() + "文件已上传");pw.flush();} catch (IOException e) {e.printStackTrace();} finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}
}

4.5 TCP实现简易的多人聊天室

要求先启动服务端,然后启动多个客户端。录入的消息格式为“name:message”,name为指定的其他用户名,用于私聊,name为all的时候表示发送群聊!

服务端:

public class ChatServer {/*** 服务器保存所有的用户*/private static HashSet<User> users = new HashSet<>();public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(8888);//循环处理连接while (true) {//一个连接表示一个用户Socket accept = serverSocket.accept();User user = new User(accept);users.add(user);new Thread(user).start();}}/*** 一个连接表示一个用户,并且能够转发消息*/private static class User implements Runnable {public User() {}//记录连接用户的名字private String name;public String getName() {return name;}//负责接收private DataInputStream is;//负责发送private DataOutputStream os;public User(Socket client) throws IOException {is = new DataInputStream(client.getInputStream());os = new DataOutputStream(client.getOutputStream());name = is.readUTF();this.send("欢迎 " + name + " 进入聊天室", true, false);this.send("您已经进入了聊天室", true, true);}/*** 接收消息,随后转发到对应的用户*/@Overridepublic void run() {while (true) {try {this.send(this.revice(), false, false);} catch (IOException e) {users.remove(this);try {is.close();} catch (IOException ioException) {ioException.printStackTrace();}try {os.close();} catch (IOException ioException) {ioException.printStackTrace();}e.printStackTrace();}}}//接收信息public String revice() throws IOException {return is.readUTF();}/*** 发送消息** @param msg       原始消息*                  如果非系统用户,那么普通用户的原始消息以 name:msg 的方式发送,name为all表示向全部在线用户发送* @param system    是否是系统消息* @param isPrivate 是否是私聊*/public void send(String msg, boolean system, boolean isPrivate) throws IOException {if (system) {if (isPrivate) {send("系统" + ":" + isPrivate + ":" + msg);return;}for (User client : users) {client.send("系统" + ":" + isPrivate + ":" + msg);}} else {if (msg.contains(":")) {String[] split = msg.split(":");if ("all".equals(split[0])) {for (User client : users) {if (client != this) {client.send(this.name + ":" + isPrivate + ":" + split[1]);}}} else {for (User user : users) {if (user.getName().equals(split[0])) {user.send(this.name + ":" + !isPrivate + ":" + split[1]);}}}}}}/*** 发送信息** @param msg 最终消息,格式为  name:isPrivate:msg*/public void send(String msg) throws IOException {os.writeUTF(msg);os.flush();}}
}

客户端:

public class ChatClient {Socket socket;DataInputStream dataInputStream;DataOutputStream dataOutputStream;public ChatClient(Socket socket, String name) throws IOException {this.socket = socket;dataInputStream = new DataInputStream(socket.getInputStream());dataOutputStream = new DataOutputStream(socket.getOutputStream());new Thread(new SendServer(name)).start();new Thread(new ReceiveServer()).start();}/*** 客户端收取消息*/class ReceiveServer implements Runnable {@Overridepublic void run() {while (true) {try {String data = dataInputStream.readUTF();//获得时间String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));//获得发送端口号String[] strs = data.split(":");String type = "消息";if ("true".equals(strs[1])) {type = "私聊";}System.out.println(time + "---" + "来自 " + strs[0] + " 的" + type);System.out.println("\t" + strs[2]);} catch (IOException e) {e.printStackTrace();}}}}/*** 客户端发送消息*/class SendServer implements Runnable {public SendServer(String name) throws IOException {send(name);}@Overridepublic void run() {try {//接收键盘录入的数据BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String str;while ((str = br.readLine()) != null) {dataOutputStream.writeUTF(str);dataOutputStream.flush();//定义结束语句if (str.contains("886")) {break;}}} catch (IOException e) {e.printStackTrace();} finally {//关闭套接字sockettry {socket.close();} catch (IOException e) {e.printStackTrace();}}}public void send(String msg) throws IOException {dataOutputStream.writeUTF(msg);}}
}

测试客户端:

public class ChatTest {public static void main(String[] args) throws IOException {Socket socket = new Socket(InetAddress.getLocalHost(), 8888);new ChatClient(socket, "ChatClient1");}public static class ChatClient2 {public static void main(String[] args) throws IOException {Socket socket = new Socket(InetAddress.getLocalHost(), 8888);new ChatClient(socket, "ChatClient2");}}public static class ChatClient3 {public static void main(String[] args) throws IOException {Socket socket = new Socket(InetAddress.getLocalHost(), 8888);new ChatClient(socket, "ChatClient3");}}
}

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

Java实现简易聊天室以及Scoket编程入门相关推荐

  1. 用Java实现简易聊天室

    说明:如果一个 类,需要有界面的显示,那么该类就需要继承自JFrame,此时,该类就可以被称为一个"窗体类". 服务端代码: package cn.qy.chat;import j ...

  2. Java WebSocket编程与网页简易聊天室

    在webSocket还未引入前,许多开发人员通过各种非正规手段来完成更新网站的最新信息和到所有当前访问者的任务,其中一种手段就是通过浏览器向服务器轮询更新,但这种手段的网络延迟比较明显,其用户体验比较 ...

  3. Socket编程实现简易聊天室

    1.Socket基础知识 Socket(套接字)用于描述IP地址和端口,是通信链的句柄,应用程序可以通过Socket向网络发出请求或者应答网络请求. Socket是支持TCP/IP协议的网络通信的基本 ...

  4. 2020暑假集训项目——Java简易聊天室

    经过一周的学习与搬砖,我成功的完成了暑假集训的第一个项目--Java简易聊天室,这里对整个项目做一个总结.(文末附下载地址) 本项目支持的功能: 1.可同时开启多个客户端进行多人聊天: 2.可与在线的 ...

  5. 网络编程之java简易聊天室实现

    最近浅学习了一些关于网络编程方面的知识,视频是跟着狂神学习的,可能学习的不是很深 说到网络,相信大家都对TCP.UDP和HTTP协议这些都不是很陌生,学习这部分应该先对端口.Ip地址这些基础知识有一定 ...

  6. java聊天室程序_Java简易聊天室程序socket

    Java简易聊天室程序socket chatroomdemo.java package com.socket.demo; import java.io.IOException; import java ...

  7. java用UDP作简易聊天室

    -------------------------------------------------java用UDP作简易聊天室------------------------------------- ...

  8. 【Socket网络编程进阶与实战】-----简易聊天室案例

    前言 本篇博客实现:简易聊天室 聊天室案例: 聊天室数据传输设计 必要条件:客户端.服务器 必要约束:数据传输协议 原理: 服务器监听消息来源,客户端链接服务器并发送消息到服务器.

  9. HTML5 之WebSocket入门demo和简易聊天室

    HTML5 WebSocket WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议. 在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏 ...

最新文章

  1. vue 增加js日志输出文件_Vue.JS项目导入导出JSON文件的方案之一,其他项目也可参考...
  2. Oracle字符集的查看查询和Oracle字符集的设置修改
  3. Silverlight/Windows8/WPF/WP7/HTML5周学习导读(6月25日-7月1日)
  4. C#苹果应用开发——第一讲初始Xamarin
  5. h264 流、帧结构
  6. 深度复盘 | 滴滴专车会员项目如何做好设计优化
  7. linux进程被中断打断,linux – 当中断处理程序被另一个中断中断时,中断上下文如何“恢复”?...
  8. Django 框架02: 模型与站点
  9. 使用RDLC报表(四)--钻取式报表
  10. C语言大型程序的项目管理与实现
  11. 神经网络 代码python_详细使用Python代码和数学构建神经网络— II
  12. 从WORD中取出图片(复制或者另存为)后变模糊了,变得不清楚
  13. 【电脑自检后无法进入电脑系统的搞定妙方】
  14. excel max函数的使用
  15. 创建局域网Git服务器
  16. 手把手教你用Python轻松玩转SQL注入
  17. linux下 scp 和 ssh 命令
  18. 拼多多关键词的排名是怎么查?拼多多排名规则
  19. CODESYS 赛搏机器智能MIC7001总线控制器+松下A6BE总线驱动器
  20. live2d_【内有礼包】七罪看板Live 2D公开,天使的面容,魔鬼的身材~

热门文章

  1. 网站服务器费用是什么,建网站费用都包含什么
  2. 【巧用自带清空内存命令 一键释放内存资源】
  3. 初级网络之OSI-网络/路由协议-IP编址-VLAN-ACL-NAT-DHCP-telnet远程
  4. xe5 TStyleManager 界面风格
  5. H.324M 3G-324M
  6. solaris 10u11 安装vim7.4
  7. RationalDMIS 2020 构建坐标系图形区预览自动显示CRD
  8. 响应式编程时出现:error TS2339: Property 'debounceTime' does not exist on type 'Observableany'.
  9. Bert模型学习之句向量的简单应用
  10. なな : 动态jianmo