Socket实现多人聊天室功能(未完)

socket也叫套接字, 是为了方便程序员进行网络开发而被设计出来的编程接口. socket在七层模型中是应该属于传输层(TCP,UDP协议)之上 的一个抽象接口.

OSI七层模型

应用层

网络服务与最终用户的一个接口。
协议有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP

表示层

数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层)
格式有,JPEG、ASCll、DECOIC、加密格式等

会话层

建立、管理、终止会话。(在五层模型里面已经合并到了应用层)
对应主机进程,指本地主机与远程主机正在进行的会话

传输层

定义传输数据的协议端口号,以及流控和差错校验。
协议有:TCP UDP,数据包一旦离开网卡即进入网络传输层

网络层

进行逻辑地址寻址,实现不同网络之间的路径选择。
协议有:ICMP IGMP IP(IPV4 IPV6) ARP RARP

数据链路层

建立逻辑连接、进行硬件地址寻址、差错校验 [2]  等功能。(由底层网络定义协议)
将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。

物理层

建立、维护、断开物理连接。(由底层网络定义协议)

从上面的七层模型中我们可以知道建立socket连接是需要IP地址和端口号的, 好了, 废话不多说, 直接进入代码环节.

(待完成) 解释socket的接口

(待完成) 先写简单socket的demo来解释阻塞式IO, 才能更好的理解为什么后面需要用到多线程实现多客户端同时连接

~~


实现简单聊天室功能

聊天室服务端

设计: 一个线程在不断的accept新客户端的连接, 每收到一个客户端连接就起一个线程来监听该客户端的输入流, 并记录该向客户的out流到listeners中, 当收到信息时广播到所有客户端out流中

代码: Server.java

package socket.chatroom;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 服务器, 这里需要一直在accept, 所以监听输入流的工作就交给另一个线程
 * @author Kenny
 *
 */
public class Server {

	static List<OutputStream> listeners = new ArrayList<>(16); 
	static final int PORT = 10000;

	public static void main(String[] args) {
		Server server = new Server();
		server.startServer();
	}

	private void startServer() {
		try (
			ServerSocket server = new ServerSocket(PORT);
		){
			System.out.println("[服务器]启动成功");
			
			while (true) {
				System.out.println("[服务器]等待连接....");
				try { 
					// 阻塞直到有客户端连接过来
					Socket socket = server.accept();
					System.out.println("[服务器]客户端:" + socket.getInetAddress().getHostAddress()
							+ "已连接");
					
					handle(socket);
					
				} catch (Exception e) {
					System.out.println("[异常]连接中断:" + e.getMessage());
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} 
		
	}
	
	/**
	 * 处理socket,多线程监听客户端输入流
	 * @param scanner
	 * @param socket
	 * @throws IOException
	 */
	private void handle(Socket socket) throws IOException {
		InputStream in = socket.getInputStream();
		OutputStream out = socket.getOutputStream();
		
		// 将客户倾听者注册到广播列表里
		addListener(out);
		
		// 监听客户端发来的消息的线程, 收到客户端消息时, 广播出去
		new Thread(new ServerReadThread(in, out, this)).start();
	}
	
	/**
	 * ArrayList是线程非安全的, 多线程下操作要注意
	 * @param out
	 */
	public synchronized void addListener(OutputStream out) {
		this.listeners.add(out);
	}
	
	public synchronized void removeListener(OutputStream out) {
		this.listeners.remove(out);
	}

	/**
	 * 推送消息
	 * @param message
	 */
	public synchronized void push(String message) {
		listeners.forEach(out -> {
			try {
				out.write(message.getBytes());
				out.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		});
	}
	
}

ServerReadThread.java

package socket.chatroom;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 客户端读广播的线程
 * @author Kenny
 *
 */
class ServerReadThread implements Runnable {
	
	private InputStream in;		// 客户端向服务器的输入流
	private OutputStream out;	// 向客户端的输出流, 保存该对象是为了IO结束时关闭out并从server.listener中删除
	private Server server;		// 服务器
	
	public ServerReadThread(InputStream in, OutputStream out, Server server) {
		this.in = in;
		this.server = server;
	}

	@Override
	public void run() {
		try (
			InputStream in = this.in;
		) {
			byte[] buffer = new byte[1024];
			int len = -1;
			while ((len = in.read(buffer)) != -1) {
				// 读信息
				String message = new String(buffer, 0, len);
				System.out.println(message);
				
				// 广播消息
				server.push(message);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// IO结束, 关闭out, 从server中清除该out
			try {
				if (out != null)
					out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			server.removeListener(out);
		}
	}
	
}

聊天室客户端

设计: 主线程监听Scanner输入流, 并向服务器发送数据, 另起一个线程监听服务器向客户端的输入流 Client.java

package socket.chatroom;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * 简单socket客户端demo
 * @author Kenny
 *
 */
public class Client {
	
	private String name;

	public Client(String name) {
		this.name = name;
	}
	
	public static void main(String[] args) {
		Client client = new Client("小红");
		client.connect();
	}
	
	private void connect() {
		try (
			Scanner scanner = new Scanner(System.in);
			Socket socket = new Socket("127.0.0.1", Server.PORT);
				
			OutputStream out = socket.getOutputStream();
			InputStream in = socket.getInputStream();
		) {
		
			handle(scanner, in, out);
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 主线程监听客户输入信息,并发送给服务端, 另起一个线程监听服务端发来的信息
	 * @param scanner	扫描客户窗口输入信息
	 * @param in		服务端向客户端的输入流
	 * @param out		客户端向服务端的输出流,scanner扫描到的信息将会写到out里
	 * @throws IOException
	 */
	private void handle(Scanner scanner, InputStream in, OutputStream out) throws IOException {
		new Thread(new ClientReadThread(in)).start();
		
		while (scanner.hasNextLine()) {
			// 写信息
			String message = scanner.nextLine();
			message = name + ": " + message;
			out.write(message.getBytes());
			out.flush();
		}
	}
}

ClientReadThread.java

package socket.chatroom;

import java.io.IOException;
import java.io.InputStream;

/**
 * 客户端读广播的线程
 * @author Kenny
 *
 */
class ClientReadThread implements Runnable {
	
	private InputStream in;		// 服务器向客户端的输入流
	
	public ClientReadThread(InputStream in) {
		this.in = in;
	}

	@Override
	public void run() {
		try (
			InputStream in = this.in;
		) {
			byte[] buffer = new byte[1024];
			int len = -1;
			while ((len = in.read(buffer)) != -1) {
				// 读信息
				String message = new String(buffer, 0, len);
				System.out.println(message);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}

运行结果

服务端截图 客户端小红截图 客户端小明截图 客户端小天截图


© 2019. All rights reserved.

Powered by Hydejack v8.4.0