聊天室案例 以及对TCP通信的使用规范

目录

TCP通信

Socket原理

Socket简介

获取本地地址和端口号

获取远端地址和端口号

获取网络输入流和网络输出流

close方法

Socket通信模型

Server端ServerSocket监听

Client端Socket连接

C S端通信模型

Server端多线程模型

简易聊天室

服务端Server

客户端Client


TCP通信

Socket原理

Socket简介

socket通常称作"套接字”, 用于描述IP地址和端口,是一个通信链的句柄。
在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket ,并绑定到一个端口.上,不同的端口对应于不同的服务。
应用程序通常通过"套接字”向网络发出请求或者应答网络请求。Socket和ServerSocket类位于java.net包中。
ServerSocket用于服务端,Socket是 建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。
 

获取本地地址和端口号

java.net.Socket为套接字类,其提供了很多方法,其中我们可以通过Socket获取本地的地址以及端口1号。
int getLocalPort ( )
该方法用于获取本地使用的端口号
- InetAddress getLocalAddress( )
该方法用于获取套接字绑定的本地地址
使用InetAddress获取本地的地址方法:
- String getC anonicalHostName( )
获取此IP地址的完全限定域名。
- String getHostAddress( )
返回IP地址字符串(以文本表现形式)。
 

public void testSocket(throws Exception {
    Socket socket = new Socket(" localhost" 8088);
    InetAddress add = socket.getLocalAddress();
    System.out.println(add.getCanonicalHostName();
    System.out.println(add.getHostAddress();
    System.out.println(socket.getLocalPort();
}

获取远端地址和端口号

通过Socket获取远端的地址以及端口]号。
- int getPort( )
该方法用于获取远端使用的端口号
- InetAddress .getInetAddress( )
该方法用于获取套接字绑定的远端地址

public void testSocket(throws Exception {
    Socket socket = new Socket(" localhost" 8088); 
    InetAddress inetAdd = socket.getInetAddress();
    System. out.println(
    inetAdd.getCanonicalHostName(); 
    System. out. println(inetAdd.getHostAddress());
    System. out.println(socket.getPort);
}

获取网络输入流和网络输出流

通过Socket获取输入流与输出流,这两个方法是使用
Socket通讯的关键方法。
- InputStream getInputStream( ) 
该方法用于返回此套接字的输入流。
- OutputStream .getOutputStream() 
该方法用于返回此套接字的输出流。
 

public void testSocket(throws Exception {
    Socket socket = new Socket("localhost" ,8088);
    InputStream in = socket.getInputStream0;
    OutputStream out = socket.getOutputStream0); 
}


close方法

当使用Socket进行通讯完毕后, 要关闭Socket以释放系统资源。
- void close( )
关闭此套接字。当关闭了该套接字后也会同时关闭,由此获取的输入流与输出流。
 

Socket通信模型

Server端ServerSocket监听

java.net.ServerSocket是运行于服务端应用程序中。通常创建ServerSocket需要指定服务端口1号,之后监听Socket的连接:

//创建ServerSocket并申请服务端口8088
ServerSocket server = new ServerSocket(8088);
/*方法会产生阻塞,直到某个Socket连接,并返回请求连
接的Socket*/
Socket socket = server.accept();

Client端Socket连接

当服务端创建ServerSocket并通过accept(方法侦听后,我们就可以通过在客户端应用程序中创建Socket来向服务端发起连接。
需要注意的是,创建Socket的同时就发起连接,若连接异常会抛出异常。
 

//参数1 :服务端的IP地址,参数2:服务端的服务端口
Socket socket = new Socket( "localhost" ,8088);

C S端通信模型

 

Server端多线程模型

若想使一个服务端可以支持多客户端连接, 我们需要解决以下问题:循环调用accept方法侦听客户端的连接
使用线程来处理单一客户端的数据交互,因为需要处理多客户端,所以服务端要周期性循环调用accept方法,但该方法会产生阻塞,所以与某个客户端的交互就需要使用线程来并发处理。

 

 

简易聊天室

服务端Server

package socket;

import java.io.BufferedReader;
import java.io.BufferedWriter;
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.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 聊天室的服务端
 * @author 陈
 *
 */
public class Server {
	/*
	 * 服务端使用的是ServerSocket
	 * 它有两个作用:
	 *1.:向系统申请端口.
	 *2.:接收请求该端口的所有客户端的连接
	 */
	private ServerSocket server;
	/*
	 * 内部类可以访问外部类的属性,因此所有的ClinetHandler都可以访问到外部类
	 * Server属性,所以这里离定义一个数组保存所有ClinetHandler对应客户端的
	 * 输出流就可以做到共享了,以便于每一个客户端发消息过来对应的ClinetHandler
	 * 都可以通过遍历这个数组拿到所有对应客户端的输出流广播消息
	 */
//	private PrintWriter allout={};
	private List<PrintWriter> allout=
			Collections.synchronizedList(new  ArrayList<>());
	/**
	 * 服务端构造方法,用来初始化服务端
	 */
	
	public  Server(){
		try {
			/*
			 * 实例化的同时想系统申请服务端口,客户端Socket 就是通过这个
			 * 了该端口,如果再启动一遍时还申请该端口就会提示被占用了.
			 * 2:如果没有启动过两次,那说明系统其它程序占用了该端口,需要
			 * 更换一个可用的
			 */
		
			System.out.println("正在启动服务端...");
			server = new ServerSocket(8088);
			System.out.println("服务端启动完毕!");
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 服务端开始工作方法
	 */
	
	public void start(){
		try {
			/*
			 * ServerSocket 提供的方法
			 * Socket accept()
			 * 该方法是一个阻塞方法,调用该方法后程序"卡住",此时开始等待
			 * 客户端的连接,一旦客户端实例化Socket并连接服务端这边申请的
			 * 端口(8088)时,accept方法会立即返回一个Socket实例,此时等于
			 * 和客户端建立了连接.服务端通过返回的这个Socket就可以与客户端
			 * 进行交互的.
			 * 多次调用accept方法可以接受多个客户端的连接.
			 */
				while(true){
				System.out.println("等待客户端连接...");
				Socket socket = server.accept();
				System.out.println("一个客户端连接了!");
				//启动一个线程处理该客户端交互
				ClinetHandler handler = new ClinetHandler(socket);
				Thread t = new Thread(handler);
				t.start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		Server server = new Server();
		server.start();
	}
	
	/**
	 * 该线程任务用来与指定客户端交互
	 * @author 陈
	 *
	 */
	private class ClinetHandler implements Runnable{
		private Socket socket;
		private String host;//用来记录客户端IP地址信息:
//		private String message;// B方案
		public ClinetHandler(Socket socket){
			this.socket = socket;
			//通过socket远端计算机地址信息(对于服务端这边而言远端就是客户端)
			host = socket.getInetAddress().getHostAddress();
		}
		public void run() {
			PrintWriter pw =null;
			try{
				/*
				 * Socket 提供的方法:
				 * InputStream getInputStream();
				 * 通过该方法获取输入流读取的字节是远端计算机发送过来的字节
				 */
				InputStream in = socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(in,"UTF-8");
				BufferedReader br = new BufferedReader(isr);
				
				
				OutputStream out  = socket.getOutputStream();
				OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
				BufferedWriter bw = new BufferedWriter(osw);
				pw = new PrintWriter(bw,true);
				
				/*
				 * 将输出流存入共享数组allout中
				 * 
				 */
//				synchronized(allout){
//					//1.allout数组扩容
//					allout = Arrays.copyOf(allout, allout.size()+1);
//					
//					//2.将输出流存入数组最后一个位置
//					allout[allout.length-1] = pw;
//				}
				allout.add(pw);
				System.out.println(host+"上线了,当前在线人数:"+allout.size());
				
				String message ="";
				while((message = br.readLine())!=null){
					String info = host+"说:"+message;
					System.out.println(info);
//					System.out.println(host+"说:"+message);// B方案
//					synchronized(allout){
//						//回复所有客户端
//						for(int i=0;i<allout.length;i++){
//							allout[i].println(host+"说"+message);
//						}
//					}
					allout.forEach( 
							(p)->p.println(info)
//							(p)->p.println(host+"说"+message) B方案
							);
				}
				
			}catch(Exception e){
				
			}finally{
				//处理客户端断开连接操作
//				synchronized(allout){
//					//将当前客户端的输出流从数组allout中删除
//					for(int i=0;i<allout.length;i++){
//						if(allout[i]==pw){
//							allout[i] = allout[allout.length-1];
//							allout = Arrays.copyOf(allout, allout.length-1);
//							break;
//						}
//					}
//				}
				allout.remove(pw);
				System.out.println(host+"下线了,当前在线人数"+allout.size());
				try {
					/*
					 *socket的close()方法调用后会与远端计算机断开了连接,
					 *此时通过socket获取输入流 与输出流也被close()了
					 */
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				
			}
			
		}
		
	}
	
}

客户端Client

package socket;

import java.io.BufferedReader;
import java.io.BufferedWriter;
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.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

/**
 * 聊天室的客户端
 * @author 陈
 *
 */
public class Client {
	/*
	 * java.net.Socket 套接字
	 * Socket 封装了TCP协议的通讯细节,使用它就可以与服务端建立网络连接了,
	 * 并且进行通讯,这里的通讯是以两条流的读写完成与服务端的数据交换的
	 */
	private Socket socket;
	
	/**
	 * 客户端构造方法,用与初始化客户端
	 */
	public Client(){
		try {
			/*
			 * 实例化Socket 时需要传入两个参数:
			 *1:服务端的地址信息:(IP)
			 *2.:服务端打开的接口
			 *
			 * 我们可以通过IP找到网络上的服务端计算机,通过其打开的端口
			 * 可以连接到服务端应用程序.
			 * 127.0.0.1 //localhost
			 */
			System.out.println("正在连接服务端...");
			socket = new Socket("localhost",8088);
			System.out.println("已连接服务端!");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 客户端开始工作的方法
	 */
	public void start(){
		
		try {
			//先启动用于读取服务端发过来消息的线程
			ServerHandler  handler = new ServerHandler();
			Thread t = new Thread(handler);
			t.start();
			/*
			 * Socket提供的方法:
			 * OutputStream getOutputStream()
			 * 通过socket的该方法获取输出流写出的字节会通过网络发送给
			 * 远端计算机.
			 */
			OutputStream out = socket.getOutputStream();
			
			OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
			BufferedWriter bw = new BufferedWriter(osw);
			PrintWriter pw = new PrintWriter(bw,true);
			
			Scanner scanner = new Scanner(System.in);
			while(true){
				String message = scanner.nextLine();
				pw.println(message);
				
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args){
		Client client = new Client();
		client.start();
	}
	/**
	 * 该线程负责循环读取服务端发送过来的消息
	 * @author 陈
	 *
	 */
	private class ServerHandler implements Runnable{
		
		public void run() {
			try{
				InputStream in = socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(in,"UTF-8");
				BufferedReader br = new BufferedReader(isr);
				
				String message ="";
				while((message= br.readLine())!=null){
					//读取服务端发送回来的一行字符串
					System.out.println(message);
				}
				
			}catch(Exception e){
				
			}
		}
	}
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值