1.、首先,是基于TCP的Socket的单次通信,简单的发送一次消息,得到一次消息
代码如下:
客户端
package com.chenpeng.client;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) throws IOException {
//(1)创建Socket对象
Socket client=new Socket("localhost",9999);
//(2)从键盘获取数据
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
//(3)获取输出流
DataOutputStream dos=new DataOutputStream(client.getOutputStream());
//(4)获取输入流
DataInputStream dis=new DataInputStream(client.getInputStream());
String str=br.readLine();
dos.writeUTF(str);//向服务器端发送数据
System.out.println(dis.readUTF());//接收服务器端的数据
}
}
服务器端:
package com.chenpeng.server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("----------------服务器端已开启---------------------");
//(1)创建ServerSocket对象
ServerSocket server=new ServerSocket(9999);
//(2)监听客户端是否有客户端请求连接
Socket socket=server.accept();
//获取输入流
DataInputStream dis=new DataInputStream(socket.getInputStream());
//获取输出流
DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
String str=dis.readUTF();
System.out.println("客户端发送了:"+str);
dos.writeUTF("服务器端收到了:"+str);
//(5)关闭流
CloseUtil.closeAll(dos,dis,socket);
}
}
工具类:省略,主要是关闭流和socket
缺点:
只能实现客户端发一次消息,服务器端发一次信息
如果我们要实现像QQ一样聊天那样,可以一直发,一直回复怎么办呢?
答:使用循环就可以一直发,使用多线程,发送消息和接收消息使用线程来就可以解决不用先发之后,再等服务器端的回复,而是发送和回复都可以同时进行。
接收数据的方法:
package com.chenpeng.util;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
/**
* 接收数据的线程
* @author chenpeng
*
*/
public class Receive implements Runnable {
//用于接收数据的数据流
private DataInputStream dis;
private boolean flag= true;
//构造方法
public Receive(Socket client) {
try {
dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
//有异常,则关闭资源
flag = false;
CloseUtil.closeAll(dis,client);
}
}
//接收数据的方法
private String getMessage() {
String str ="";
try {
str = dis.readUTF();
} catch (IOException e) {
flag = false;
CloseUtil.closeAll(dis);
}
return str;
}
@Override
public void run() {
while(flag) {
System.out.println(this.getMessage());
}
}
}
发送数据的方法:
package com.chenpeng.util;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* 发送消息的线程
* @author chenpeng
*
*/
public class Send implements Runnable{
//从键盘获取数据
private BufferedReader br;
//发送数据使用数据输出流
private DataOutputStream dos;
private boolean flag = true;//默认为true
//无参构造方法
public Send() {
br = new BufferedReader(new InputStreamReader(System.in));
}
//有参构造器
public Send(Socket client) {
this();//调用本类的无参构造方法
try {
dos = new DataOutputStream(client.getOutputStream());
} catch (IOException e) {//有问题则关闭
flag= false;
CloseUtil.closeAll(dos,client);
}
}
//从键盘得到信息
private String getMessage() {
String str="";
try {
str =br.readLine();
} catch (IOException e) {
flag = false;//
CloseUtil.closeAll(br);
}
return str;
}
//发送给服务端
private void send(String str) {
try {
dos.writeUTF(str);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
dos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
while(flag) {
//调用发送信息的方法
this.send(getMessage());
}
}
}
服务端方法:
/**
* 服务端
*
* @author chenpeng
*
*/
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("----------服务端-----------");
// 1.创建一个ServerSocket对象
ServerSocket server = new ServerSocket(8848);
// 2.监听客户端是否发请求过来
Socket client = server.accept();
// 创建接收数据的线程
Receive receive = new Receive(client);
// 创建发送数据回去的线程
Send send = new Send(client);
//启动线程
new Thread(receive).start();
new Thread(send).start();
}
}
客户端的方法:
package com.chenpeng.Thread;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端
* @author chenpeng
*
*/
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("--------客户端---------");
//创建Socket对象
Socket client= new Socket("localhost",8848);
//创建发送的线程类对象
Send send = new Send(client);
//创建接收的线程类对象
Receive receive = new Receive(client);
//创建Thread类对象的线程并启动线程
new Thread(send).start();
new Thread(receive).start();
}
}
这时候就可以实现客服端发消息,服务器端也能发消息,简单实现了QQ的聊天功能,原理就是两边都使用发送数据和接收数据的线程,然后一直循环。
但是有个小问题,如果我再启动一个客户端发送消息,是不会到服务端的?这里等我思考之后什么回事再解决吧,今天主要是实现“群聊”的功能
3、现在,到了实现“群聊”的时候了
思路:
1、首先基本上和上面客户端和服务器端一直聊没区别,也是使用了发送消息的线程和接收消息的线程
2、但是这里问题来了,一个客户端发送消息,如何让其他的客户端接收到消息呢?这时候,我们需要在服务器端来一个线程类MyChannel,用来记录每个客户端发送的数据,然后再使用群发的功能sendOther(),不能发给自己,所以我们在服务器端上面创建了一个Arraylist集合,用于存放运行着的线程。
package com.chenpeng.util;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.List;
/**
* 每一个客户端都是一条道路
* 1.输入流
* 2.输出流
* 3.接收数据
* 4.发送数据
* @author chenpeng
*
*/
public class MyChannel implements Runnable {
private DataInputStream dis;
private DataOutputStream dos;
private boolean flag = true;
//构造方法
public MyChannel(Socket client) {
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
flag = false;
CloseUtil.closeAll(dis,dos);
}
}
//接收数据的方法
private String receive() {
String str ="";
try {
str = dis.readUTF();
} catch (IOException e) {
flag = false;
CloseUtil.closeAll(dis,dos);
Server.list.remove(this);
}
return str;
}
//发送数据的方法
private void send(String str) {
if(str!=null&&str.length()!=0) {
try {
dos.writeUTF(str);
dos.flush();
} catch (IOException e) {
flag= false;
CloseUtil.closeAll(dos,dis);
Server.list.remove(this);
}
}
}
//转发的方法
private void sendOther() {
String str = this.receive();
List<MyChannel> list = Server.list;
for(MyChannel other: list) {
if(other == this) {
continue;//不发给自己
}
other.send(str);//发送数组
}
}
@Override
public void run() {
while(flag) {
//调用发送数据的方法
sendOther();
}
}
}
注意:客户端这里使用了while循环,一直监听者客户端有没有客户端连接
总结:
单次聊天只能先发数据,然后才能接收数据,如果不发就收不到,因为接收和收到是在一个线程里面实现的,不符合实际的情况
解决方案:
1、想要多次聊天可以使用循环
2、先发后收的的问题可以使用线程来解决,一个接收数据的线程,一个发送数据的线程