小李的40天java历程——Day12(JSE)

TCP

  • 利用tcp传输协议,写一个客户端,服务端的聊天模式
    在这里插入图片描述
  • 服务端
package se01.day11.chat;
/**
 * 服务端:
 * 
 * @author lei_l
 *
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
	/*服务端的serverSocket主要负责:
	 * 1.向系统申请服务端入口(客户端就是通过这个端口与之连接)
	 * 2.监听申请的服务端口.当客户端通过该端口尝试建立连接时,ServerScoket会在服务器创建一个Scoket与客户端建立连接
	 */
	private ServerSocket server;
	
	/*保存所有客户端的输出流,用于实现多用户之间的对话*/
	private List<PrintWriter> allOut;
	
	/*构造方法初始化服务端*/
	public Server() throws Exception{
		//初始化并申请服务端口
		server = new ServerSocket(8088);
		
		allOut = new ArrayList<PrintWriter>();//遍历不用线程安全,其他需要.所以自己先维护之间的线程安全关系
	}
	
	/*服务端的输出流存入集合*/
	private synchronized void addOut(PrintWriter pw) {
		
	}
	
	/*服务端的输出流从集合中删除*/
	private synchronized void removeOut(PrintWriter pw) {
		
	}
	
	/*给指定的客户发送信息,目前是遍历*/
	private synchronized void sendMessage(String message) {
		for(PrintWriter out : allOut) {
			out.println(message);
		}
	}
	
	/*服务端开始工作的方法*/
	public void start() {
		try {
			System.out.println("等待客户端请求...");
			//accept方法是一个阻塞方法:作用是监听服务端口,如果有客户端尝试连接,则创建一个socket与之连接交互
			Socket socket = server.accept();
			System.out.println("客户端请求,连接成功");
			
			//由于有冲突,就是只能监听以一个,只能在那里直接接受一个客户端发送的请求所以创建一个线程来处理客户端交互
			ClientHandler clientHandler = new ClientHandler(socket);
			Thread t = new Thread(clientHandler);
			t.start();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/*主方法*/
	public static void main(String[] args) {
		try {
			Server server = new Server();
			server.start();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("服务端启动失败...");
		}
	}
	
	
	/**
	 * 该线程负责一个客户端的交互
	 * 由于有冲突,就是只能监听以一个,只能在那里直接接受一个客户端发送的请求所以创建一个线程来处理它
	 * @author lei_l
	 *
	 */
	class ClientHandler implements Runnable{
		/*通过这个socket来和客户端进行交互*/
		private Socket socket;
		
		/*客户端的地址信息*/
		private String host;
		
		/*用户的昵称*/
		private String nickName;
		
		/*构造方法,用来创建ClientHandler时就将socket传过来*/
		public ClientHandler(Socket socket) {
			this.socket = socket;
			//通过socket获取客户端的地址信息
			InetAddress address = socket.getInetAddress();
			//获取IP地址
			host =address.getHostAddress();
		}
		
		@Override
		public void run() {
			PrintWriter pw = null;
			try {
				//获取输入流,接受客户端发送来的信息
				InputStream in = socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(in,"utf-8");
				BufferedReader br = new BufferedReader(isr);
				
				//先读一行字符串为昵称.
				nickName = br.readLine();
				System.out.println(nickName+"上线了");
				
				//获取输出流,返回给客户端信息
				OutputStream out = socket.getOutputStream();
				OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");
				pw = new PrintWriter(osw,true);
				//将输出流传入数组
				addOut(pw);
				
				//输出客户端的信息,为了断开客户端时不会报空指针的错 为空时:windows抛异常  Linux:显示为空null
				String message = null;
				while ((message = br.readLine()) != null) {
//					//为什么全部输出不行呢?会报socket异常,导致serve关闭?
//					System.out.println(host+"说:"+message);
//					//返回给自己消息
//					pw.println(host+message);
					
					//转发消息给别的客户端
					sendMessage(nickName+"说"+message);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				//处理当前客户端断开后的逻辑
				sendMessage(nickName+"下线了!");
				//将该客户端的输出流从共享集合中删除
				removeOut(pw);
			}
			
			/*关闭socket就不用管输入流了*/
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	
}


  • 客户端
package se01.day11.chat;
/**
 * ?不是说和主方法并列的方法必须要用Static修饰么?
 * 客户端:
 * 		1)java.net.Socket:封装了Tcp协议,调用该方法就可以基于Tcp协议进行网络通讯
 * 		2)Socket用于客户端:相当于打电话.这边的信号输出是别的电话信号的输入
 * 			2.1)IP地址:	每一个服务器都有自己的IP地址,IP地址是通过路由器分配的.访问本地IP有两种localhost和127.0.0.1
 * 				服务端口号:每一个正在运行的应用程序都会向系统申请一个端口号.而每一个系统接受到发送来的请求时,会携带端口号和信息.与端口号匹配则传输给其信息
 * 		
 * @author lei_l
 *
 */

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client {
	/*先有电话实体,用于与远程计算机连接并通讯的作用*/
	private Socket socket;
	
	/*定义构造方法初始化客户端*/
	public Client()throws Exception{
		System.out.println("正在连接服务端");
		//实例化scoket的过程就是建立连接的过程.若远端服务器没有响应,则会抛出异常
		socket = new Socket("localhost",8088);//IP地址(127.0.0.1),端口号
		System.out.println("成功建立连接");
	}
	
	/*启动客户端的方法*/
	public void start() {
		try {
			Scanner scan = new Scanner(System.in);
			//现要求输入一个客户端的昵称
			String nickName = null;
			while(true) {
				System.out.println("请为您起一个昵称");
				nickName = scan.next();
				if (nickName.length() >0 ) {
					break;
				}
				System.out.println("输入有误");
			}
			System.out.println("欢迎"+nickName+"登陆,开始聊天吧!");
			
			//Socket提供了一个字节输出流方法,通过该方法可将写的数据发送到服务端,且不用管申明类型是否为接口,一定是他的实现类
			OutputStream out = socket.getOutputStream();
			OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");
			PrintWriter pw = new PrintWriter(osw);
			
			//将客户端的呢称发送给服务器
			pw.println(nickName);
			
			//启动读取服务器发送过来的消息.线程
			ServerHandler serverHandler = new ServerHandler();
			Thread t = new Thread(serverHandler);
			t.start();
			
			//将字符串发送到服务端
			while(true){
				pw.println(scan.next());
				pw.flush();
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/*主方法*/
	public static void main(String[] args) {
		try {
			Client client = new Client();
			client.start();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("客户端启动失败...");
		}
		
				
	}
	
	/**
	 * 返回数据的接收,不能写一条才返回一条,能写的同时也返回其他的客户的消息,所以采用线程
	 * @author lei_l
	 *
	 */
	class ServerHandler implements Runnable{
		
		@Override
		public void run() {
			try {
				//输入流,接收服务器返回来的数据
				InputStream is= socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(is,"utf-8");
				BufferedReader br = new BufferedReader(isr);
				
				//显示返回的数据
				String message = null;
				while ((message = br.readLine()) != null) {
					System.out.println(message);
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
	}
	
	
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值