【Socket编程】聊天室的构建

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);
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值