1.服务器与客户端一对一的交谈(初级)
/**
* server
*/
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket server=new ServerSocket(7878);
Socket socket=server.accept();
DataInputStream dis=new DataInputStream(socket.getInputStream());
DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
while(true){
//服务器接收客户端消息
String msg2=dis.readUTF();
System.out.println(msg2);
//服务器发送消息
String msg="[server]:welcome";
dos.writeUTF(msg);
dos.flush();
}
}
}
/**
* 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.net.UnknownHostException;
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket client=new Socket("localhost",7878);
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
DataOutputStream dos=new DataOutputStream(client.getOutputStream());
DataInputStream dis=new DataInputStream(client.getInputStream());
while(true){
//从控制台输出信息
String msg=br.readLine();
//客户端发送数据
dos.writeUTF(msg);
dos.flush();
//客户端接收数据
String msg2=dis.readUTF();
System.out.println(msg2);
}
}
}
这种一对一聊天模式存在一个问题,客户端必须先发送消息,然后等服务器回应以后再接收数据,因为客户端发送和
接收消息是处于同一线程的,输入流和输出流在同一条路径下,而客户端和服务器聊天的时候是无论它是否收到了消
息都可以发送出去,所以这里需要将输入流和输出流分离开,也就是使用多线程。
2.服务器与客户端一对一的交谈(进阶)
服务器代码不变,使用多线程将客户端输入输出流分开
/**
* 客户端发送数据
*/
public class Send implements Runnable{
private BufferedReader br;
private DataOutputStream dos;
private boolean isRunning=true;//控制线程的标识
public Send() {
br=new BufferedReader(new InputStreamReader(System.in));
}
public Send(Socket client) {
this();
try {
dos=new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos,br);
}
}
private String getMsgFromConsole(){
try {
return br.readLine();
} catch (IOException e) {
}
return "";
}
public void send(){
String msg=getMsgFromConsole();
if(msg!=null && !msg.equals("")){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos,br);
}
}
}
@Override
public void run() {
while(isRunning){
send();
}
}
}
/**
* 客户端接收数据
*/
public class Receive implements Runnable{
private DataInputStream dis;
private boolean isRunning=true;
public Receive() {
}
public Receive(Socket client) {
try {
dis=new DataInputStream(client.getInputStream());
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis);
}
}
public String receive(){
String msg=null;
try {
msg=dis.readUTF();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis);
}
return msg;
}
@Override
public void run() {
while(isRunning){
System.out.println(receive());
}
}
}
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket client=new Socket("localhost",7878);
new Thread(new Send(client)).start();
new Thread(new Receive(client)).start();
}
}
/**
* 关闭流工具类
*/
public class CloseUtil {
public static void CloseALL(Closeable... io){
for(Closeable temp:io){
try {
if(temp!=null){
temp.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
通过这样的调整,输入输出在不同的线程,但是这里的服务器只能连接一个客户端,如何使服务器对应多个客户端,
并且每个客户端之间不会相互干扰呢?需要在服务器上使用多线程。
一个客户端对应一个线程,每个客户端又有独立的输入流和输出流
3.服务器和多个客户端一次会谈
服务端加入多线程,客户端不变
/**
* server
*/
public class Server {
private List<MyChannel> all=new ArrayList<MyChannel>();//所有客户端集合
public static void main(String[] args) throws IOException {
new Server().start();
}
public void start() throws IOException{
ServerSocket server=server = new ServerSocket(7878);
while(true){
Socket client=server.accept();
MyChannel channel=new MyChannel(client);
all.add(channel);
new Thread(channel).start();
}
}
/**
* 客户端管道、线程
*/
private class MyChannel implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private boolean isRunning=true;
public MyChannel() {
}
public MyChannel(Socket client) {
try {
dis=new DataInputStream(client.getInputStream());
dos=new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis,dos);
all.remove(this);//移除自身
}
}
public String receive(){
try {
return dis.readUTF();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis);
all.remove(this);
}
return "";
}
/**
* 给其他客户端发送消息
*/
private void sendOthers(){
String msg=receive();
for(MyChannel temp:all){
if(temp==this){
continue;
}
temp.send(msg);
}
}
public void send(String msg){
if(msg==null || msg.equals("")){
return ;
}
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos);
all.remove(this);
}
}
@Override
public void run() {
sendOthers();
}
}
}
需要注意的是,经过这样的改变,服务端只能为每个客户端转发一次消息,进行一次会谈,也就是假如我有一个
Client-A,Client-B,Client-C,A给BC只能发一次消息,同样的B,C也只能给AC,AB发一次消息,这是因为在start方法
中,server.accept()在死循环中,也就是除非有新的客户端连接到服务器,服务器的main线程会一直处于阻塞状态,
如果将这句代码放到死循环外面,会报错
4.客户端私聊一次会谈
为每个客户端加用户名,在进入聊天室时做提示,比如“xxx进入了聊天室”,“欢迎进入聊天室”
假设这里有两个客户端,depp,tom,depp要和tom私聊时,直接在控制台输入:@tom:XXX
这样消息就只能发送给tom
【客户端 receive类代码不变】
/**
* server
*/
public class Server {
private List<MyChannel> all=new ArrayList<MyChannel>();//所有客户端集合
public static void main(String[] args) throws IOException {
new Server().start();
}
public void start() throws IOException{
ServerSocket server=server = new ServerSocket(7878);
while(true){
Socket client=server.accept();
MyChannel channel=new MyChannel(client);
all.add(channel);
new Thread(channel).start();
}
}
/**
* 客户端管道、线程
*/
private class MyChannel implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private boolean isRunning=true;
private String username;
public MyChannel() {
}
public MyChannel(Socket client) {
try {
dis=new DataInputStream(client.getInputStream());
dos=new DataOutputStream(client.getOutputStream());
this.username=receive();
this.send("welcome to chat room !");
this.sendOthers("进入了聊天室!");
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis,dos);
all.remove(this);//移除自身
}
}
public String receive(){
try {
return dis.readUTF();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis);
all.remove(this);
}
return "";
}
/**
* 给其他客户端发送消息
*/
private void sendOthers(String msg){
//判断是否为私聊
if(msg.startsWith("@") && msg.indexOf(":")>-1){
//获取名字
String name=msg.substring(1, msg.indexOf(":"));
//获取内容
String content=msg.substring(msg.indexOf(":")+1);
for(MyChannel temp:all){
if(temp.username.equals(name)){
temp.send(this.username+" whisper : "+content);
}
}
}else{
for(MyChannel temp:all){
if(temp==this){
continue;
}
temp.send(this.username+" "+msg);
}
}
}
public void send(String msg){
if(msg==null || msg.equals("")){
return ;
}
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos);
all.remove(this);
}
}
@Override
public void run() {
sendOthers(receive());
}
}
}
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("please input username:");
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String username=br.readLine();
if(username.equals("")){
return ;
}
Socket client=new Socket("localhost",7878);
new Thread(new Send(client,username)).start();
new Thread(new Receive(client)).start();
}
}
/**
* 客户端发送数据
*/
public class Send implements Runnable{
private BufferedReader br;
private DataOutputStream dos;
private String username;
private boolean isRunning=true;//控制线程的标识
public Send() {
br=new BufferedReader(new InputStreamReader(System.in));
}
public Send(Socket client,String username) {
this();
try {
dos=new DataOutputStream(client.getOutputStream());
this.username=username;
send(this.username);
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos,br);
}
}
private String getMsgFromConsole(){
try {
return br.readLine();
} catch (IOException e) {
}
return "";
}
public void send(String msg){
if(msg!=null && !msg.equals("")){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos,br);
}
}
}
@Override
public void run() {
while(isRunning){
send(getMsgFromConsole());
}
}
}
【解决第三点中出现的问题】
在第三中,虽然可以实现创建多个client线程请求服务器资源,但是每个client只能向服务器发送一次信息
为解决这个问题,使得每个客户端可以向服务器发送多个消息,对服务器端做出如下改变:
(由于温习聊天室是因为其他项目 所以功能做了相应的改变 但思路是一致的)
public class Server {
private static DataInputStream dis;
private static DataOutputStream dos;
//需要实现runnable的类来开启线程
private class Servers implements Runnable {
public Servers() {
}
//封装出来的一个功能模块 检验电压是否正常
public String elecTest(String clientMsg) {
String msg = null;
int number = Integer.parseInt(clientMsg.substring(clientMsg
.indexOf("-") + 1));
if (number >= 215 && number <= 225) {
msg = "Machine normal operation!";
} else if (number < 215 && number >= 200) {
msg = "Working electric tension is too low!";
} else if (number > 225 && number < 235) {
msg = "Working electric tension is too high!";
} else {
msg = "WARNING irregular working";
}
return msg;
}
@Override
public void run() {
//一个用户发多次消息
while (true) {
String clientMsg;
try {
clientMsg = dis.readUTF();
System.out.println(clientMsg);
String msg = elecTest(clientMsg);
dos.writeUTF(msg);
dos.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
public void start(Socket socket) throws IOException {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
Servers s = new Servers();
new Thread(s).start();
}
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(7878);
Server serverForMain = new Server();
//实现一个服务器连接多个客户端
while (true) {
Socket socket = server.accept();
serverForMain.start(socket);
}
}
}