Java中Socket编程 TCP|UDP通信

 通信数据源有哪些: 文件、byteArrayOutputStream、socke管道、控制台

1. 基础概念: 

 1. 地址:Ip地址
 2. 端口: 计算机中区分不同进程
 
 同一个协议下,端口不能重复使用,不同协议可以
 1024以下端口预留给系统的   比如http 80  ftp 21
 
 URL: 统一资源定位符
组成: 
   协议+主机域名+端口+资源文件名
 
 3. 数据传输协议
 tcp 协议: 三次握手,面向连接,数据流,打电话,数据不会丢失
 udp 协议, 发短信, 数据可能都是
 
 协议分成网络通信封装:
 1. 数据封装,发送数据
 应用层: http,ftp,snmp 都是应用层协议
 传输层: 端口, tcp/udp  传输层协议,具体数据包结构百度
 网络层: Ip,具体数据包结构百度
 数据连接层: Mac地址
 2. 数据拆分:   数据接收

2. 地址 Api 基础 使用

  InetAddress  封装IP地址和域名
  InetSocketAddress  封装IP和端口
  URL  统一资源定位符
  组成:  协议+主机域名+端口+资源文件名
  通过 URL 获取资源,网络爬虫

  2.1.InetAddress  封装IP地址和域名

 public static void main(String[] args) throws URISyntaxException, IOException {
		/**
		 * InetAddress  封装IP地址和域名
		 */
		 InetAddress addr= InetAddress.getLocalHost();
		 System.out.println(addr.getHostAddress());  // 获取本机Ip,192.168.2.208
		 System.out.println(addr.getHostName()); // 获取计算机名
		 
		 addr= InetAddress.getByName("www.baidu.com");
		 System.out.println(addr.getHostAddress());  //根据域名返回Ip
		 
		 addr= InetAddress.getByName("182.61.200.6");
		 System.out.println(addr.getHostAddress());
		 System.out.println(addr.getHostName()); // 如果Ip地址不存在,或者dns不解析,返回Ip,否则返回域名
		 	 
	}

2.2.InetSocketAddress  封装IP和端口

 public static void main(String[] args) throws URISyntaxException, IOException {
		 /**
		  *   封装IP和端口
		  */
//	InetSocketAddress addr1=new InetSocketAddress("127.0.0.1",8888);
//	System.out.println(addr1.getAddress());	  //
//		 System.out.println(addr1.getHostName());
//	System.out.println(addr1.getPort());
	}

2.3. URL  统一资源定位符
  组成:  协议+主机域名+端口+资源文件名

 public static void main(String[] args) throws URISyntaxException, IOException {
/**
	 *  URL: 统一资源定位符
组成:  协议+主机域名+端口+资源文件名
	 */
//	URL url=new URL("http://www.baidu.com:8080/myServlet?username=xiaoming");
//	System.out.println("协议:"+url.getProtocol());
//	System.out.println("主机名:"+url.getHost());
//	System.out.println("端口:"+url.getPort());
//	System.out.println("资源:"+url.getFile());
//	System.out.println("相对路径:"+url.getPath());
//	System.out.println("锚点:"+url.getRef());
//	System.out.println("参数:"+ url.getQuery());
	/**
	 * 协议:http
主机名:www.baidu.com
端口:8080
资源:/myServlet?username=xiaoming
相对路径:/myServlet
锚点:null
参数:username=xiaoming
	 */
	// 类似File相对路径
	URL url1= new URL("http://baidu.com:8080/a/");
	URL url2= new URL(url1, "b.txt");
	System.out.println("url2:"+url2.toString());  //http://baidu.com:8080/a/b.txt
}

2.4. 通过 URL 获取资源,网络爬虫

 public static void main(String[] args) throws URISyntaxException, IOException {

	/**
	 * 资源访问  通过流, 模仿网络爬虫,  爬虫网页 保持 到本地
	 */
	URL url3=new URL("http://www.baidu.com");
	 InputStream inputStream=url3.openStream();
	BufferedReader reader=new BufferedReader(
			new InputStreamReader(inputStream,"utf-8"));
	String msg=null;
	
	BufferedWriter bw=new BufferedWriter(
			new OutputStreamWriter(
					new FileOutputStream("baidu.html"),"utf-8"));
	
    while(( msg=reader.readLine())!=null) {
    	bw.append(msg);
    	bw.newLine();
    	 
      System.out.println(msg);
    }
    bw.flush();
    bw.close();
    reader.close();
	
	 }
	

3. Udp通信

   3.1. 发送字符串    UdpServer 端:  
   3.2.发送基本数据类型,比如子弹个数发送
   【把double转化为内存中double,不是字符串, Double在内存中存储
     字符串在内存中是逐个字符的ASCII码存储的】

package com.denganzhi.pp;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class UdpServer {
  public static void main0001(String[] args) throws IOException {
	
	  // 1. 创建  服务端 Ip+ 端口
	  DatagramSocket server=new DatagramSocket(8888,InetAddress.getByName("localhost"));
	  // 2.  准备接受数据容器
	  byte[] container=new byte[1024];
	  // 3. 封装包  
	  DatagramPacket packet=new DatagramPacket(container,container.length);
	  // 4. 接收数据
	  server.receive(packet);
      // 5.分析数据
	  byte[] data=packet.getData();
	  int len= packet.getLength();
	  System.out.println(new String(data,0,len));
	  
	  // 6. 释放
	  server.close();
	  
	  
}
  
  
  public static void main(String[] args) throws IOException {
		
	  // 1. 创建  服务端 Ip+ 端口
	  DatagramSocket server=new DatagramSocket(8888,InetAddress.getByName("localhost"));
	  // 2.  准备接受数据容器
	  byte[] container=new byte[1024];
	  // 3. 封装包  
	  DatagramPacket packet=new DatagramPacket(container,container.length);
	  // 4. 接收数据, 阻塞
	  System.out.println("udp阻塞");
	  server.receive(packet);
	 System.out.println("udp接收数据");
      // 5.分析数据
	  Double result= convert(packet.getData());
	
	 System.out.println("result:"+result);  
	  // 6. 释放
	  server.close();  
   }
  //  data-> double
	public static double convert(byte[] data) throws IOException {
	 
	 DataInputStream dis=new DataInputStream(new ByteArrayInputStream(data));
	 
	 double num=  dis.readDouble();
	 dis.close();
		
	return num;	
	}
}

  UdpClient 端:

package com.denganzhi.pp;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class UdpClient {
	/**
	 * 字符串 发送 
	 * @param args
	 * @throws IOException
	 */
	public static void main001(String[] args) throws IOException {
		// 1. 创建 客户端+ 端口
		 DatagramSocket client=new DatagramSocket(6666,InetAddress.getByName("localhost"));
		// 2. 数据准备
		 String msg="udp编程";
		 byte[] data= msg.getBytes();
		 //3. 打包( 发送地点以及 端口), 打包直接发送,所有udp有丢包可能
		 DatagramPacket packet=new DatagramPacket(data, data.length,new InetSocketAddress("127.0.0.1",8888));
		 // 4. 发送数据
		 client.send(packet);
         // 5. 关闭资源
		 client.close();
		
	}
  /**
   * 基本数据类型 的传输
   * @param args
   * @throws IOException
   */
	public static void main(String[] args) throws IOException {
		// 1. 创建 客户端+ 端口
		 DatagramSocket client=new DatagramSocket(6666,InetAddress.getByName("localhost"));
		// 2. 数据准备
		 byte[] data= convert(11.01);
		 //3. 打包( 发送地点以及 端口)
		 DatagramPacket packet=new DatagramPacket(data, data.length,new InetSocketAddress("127.0.0.1",8888));
		 // 4. 发送数据
		 client.send(packet);
         // 5. 关闭资源
		 client.close();
		
	}
	
	
	public static byte[] convert(double num) throws IOException {
	byte[] data=null;
	ByteArrayOutputStream bos=new ByteArrayOutputStream();
	DataOutputStream dos=new DataOutputStream(bos);
	dos.writeDouble(num);
	dos.flush();
	data= bos.toByteArray();
	dos.close();
		
	return data;	
	}
}

  4. Tcp通信

 4.1.  TcpServer 发送字符串、发送基本数据类型,直接写DataInputStream,服务端

package com.denganzhi.pp;

import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
  public static void main(String[] args) throws IOException {
		 //1.. 创建服务器,  指定端口
		ServerSocket server=new ServerSocket(8888);   
		 // 2. 指定客户端   阻塞式
		//  完成 三次握手 , 建立管道
		System.out.println("服务器阻塞,等待客户端连接");
		Socket socket= server.accept();
		System.out.println("一个客户端建立连接");
		
		// 服务端输出数据
//  BufferedWriter bw=new BufferedWriter(
//		  new OutputStreamWriter(socket.getOutputStream()));
//  // 必须 有行 结束符,socket内存数据才可以被写出
//  String str="我是来自服务器";
//  bw.write(str);
//  bw.newLine(); // 必须有
//  bw.flush();
		
		// 直接写出基本数据类型,比如游戏中子弹个数
DataOutputStream dos=new DataOutputStream(
		socket.getOutputStream());
dos.writeDouble(11.01);
dos.flush();
  
		
   }
}

  客户端实现:

package com.denganzhi.pp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class TcpClient {
  public static void main(String[] args) throws UnknownHostException, IOException {
	//1. 创建客户端 必须指定要连接的 服务器
	  // tcp 客户端端口有系统分配
	  Socket clinet=new Socket("localhost",8888);
	    
//BufferedReader reader=new BufferedReader(
//		new InputStreamReader(
//				clinet.getInputStream()));
//
//String echo= reader.readLine();
//
//System.out.println(echo);
	  
	  DataInputStream dis=new DataInputStream(clinet.getInputStream());
	  
	 double result = dis.readDouble();
	 
	 System.out.println("result:"+result);
	 	  
   }
}

5. Tcp实现聊天实现

    要点: 1. 客户端可以发送数据, 必须要把收发数据放入不同的线程中
    2.  服务器实现多个连接客户端监听
 服务器必须受到数据才发送数据到客户端
 所以服务器必须把不同客户端放入独立的线程中

代码流程:

 1. 
Client:  1. 实现客户端上线功能, 为每一个客户端接发数据启动一个线程
Server:   1. 这里实现客户端上线功能,为每一个客户端通道启动一个线程, 把任务我放入List中

2. 
Client  :  客户端A发送数据,
Server:  服务器A通道线程接收数据,服务器sendOthers() 发送给List中其他任务线程,如果是群聊,遍历List发送即可,

如果是私聊,在List找到对应 线程通道根据name ,send即可

实现功能: 群聊、 私聊 @test:您好

版本1:服务器代码支持客户A发送到服务器,服务器回客户端A,服务器只是支持一个客户端

package com.denganzhi.multi;

import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
 
	 
	public static void main(String[] args) throws IOException {
		
		 //1.. 创建服务器,  指定端口
		ServerSocket server=new ServerSocket(8888);   
		 // 2. 指定客户端   阻塞式
		//  完成 三次握手 , 建立管道
		System.out.println("服务器阻塞,等待客户端连接");
	//	while(true) {   // 服务器实现多线程,多个客户端连接
			
			Socket socket= server.accept();
			System.out.println("一个客户端建立连接");
			DataInputStream dis=new DataInputStream(socket.getInputStream());
			DataOutputStream dos=new DataOutputStream(
					socket.getOutputStream());
			
			while(true) {
				// 读取客户端数据 
				String msg= dis.readUTF();
				System.err.println("服务收到数据:"+msg);
				
				 // 服务器输出数据
					
				 dos.writeUTF("服务器输出数据120");
					dos.flush();
			}
			
	//	}
	}
}

版本2:服务器代码支持群聊,私聊版本:

package com.denganzhi.multi;

import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class TcpServer {


	List<MyChannel> allChannels=new ArrayList<>();
	
	public static void main(String[] args) throws IOException {
		
		new TcpServer().start();
	}
	
	public void start() throws IOException {
		 //1.. 创建服务器,  指定端口
		ServerSocket server=new ServerSocket(8888);   
		 // 2. 指定客户端   阻塞式
		//  完成 三次握手 , 建立管道
		System.out.println("服务器阻塞,等待客户端连接");
		while(true) {   // 服务器实现多线程,多个客户端连接
			Socket socket= server.accept();
			System.out.println("一个客户端建立连接");
			
			// 服务器接发数据放入一个线程中
		MyChannel myChannel=new MyChannel(socket);
		// 把通过的客户端连接任务装起来
       allChannels.add(myChannel);
		new Thread(myChannel).start();
		
		 
		} 
		
	}
	
	
	class MyChannel implements Runnable{
		boolean isRunning=true;
		DataInputStream dis=null;
		DataOutputStream dos=null;
		private String name;
		public  MyChannel(Socket clinet) {
			try {
				 dis = new DataInputStream(clinet.getInputStream());
				 dos=new DataOutputStream(
						clinet.getOutputStream());
				String name= dis.readUTF();
				this.name= name;  
				// 1. 这里实现客户端上线功能,为每一个Runnable分配一个name 
				System.out.println(name+"进入聊天室");
				this.send("欢迎自己"+this.name+ "欢迎进入聊天室");
				this.sendOthers("通知"+this.name+" 进入聊天室");
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				CloseUtils.closeAll(dis,dos);
				isRunning=false;
				allChannels.remove(this);
			}
		}
		
		@Override
		public void run()  {
			while(isRunning) {
				// 读取客户端数据 ,实现私聊功能
//			String msg=receive();
//			send(msg);
				
			// 实现群聊
				sendOthers();
			}
		}
		
		
	public String receive() {
		String msg="";
		try {
			msg = dis.readUTF();
			System.err.println(this.name+"发送过来数据-----》"+ "服务收到数据:"+msg);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			CloseUtils.closeAll(dis,dos);
			isRunning=false;
			allChannels.remove(this);
		}
		return msg;
	
	}
		
 // 发送数据
	public void send(String msg) {
		 // 服务器输出数据
		 try {
	///		dos.writeUTF("服务器输出数据120");
			 dos.writeUTF(msg);
			 dos.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			CloseUtils.closeAll(dis,dos);
			isRunning=false;
			allChannels.remove(this);
		}
	}
	
	public void sendOthers() {
		String msg= receive();
		
	//	@test:您好
         
	// 判断私聊还是群聊
		if(msg.startsWith("@")) {
			//  在容器中知道对应的人
			String name= msg.substring(1,msg.indexOf(":"));
			// substring 左 关闭右 开
			String content= msg.substring(msg.indexOf(":")+1);
			for (MyChannel myChannel : allChannels) {
				if(myChannel.name.equals(name)) {
				    myChannel.send(this.name+"私聊 你: " + content+ "");
				}
			}
			
		}else {
			// 发送给所有人
			for (MyChannel myChannel : allChannels) {
				if(myChannel==this) { 
				   continue;
				}
				// 发送给其他客户客户端
				myChannel.send("群发消息:"+  msg);
			}
		}
	}
     public void sendOthers(String msg) {
		
		for (MyChannel myChannel : allChannels) {
			if(myChannel==this) { 
			   continue;
			}
			// 发送给其他客户客户端
			myChannel.send(msg);
		}
	}
	}
}

 客户端代码:

package com.denganzhi.multi;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

import javax.swing.plaf.ColorChooserUI;

public class TcpClient {
	
	static boolean cIsRunning=true;
	static boolean sIsRunning=true;

	 
  public static void main(String[] args) throws UnknownHostException, IOException {
	//1. 创建客户端 必须指定要连接的 服务器
	  // tcp 客户端端口有系统分配
	  Socket clinet=new Socket("localhost",8888);
	     
	
	  
 // 客户端  写数据
 // 2.  客户端接收、发送消息位于不同线程中
 new Thread(new Runnable() {
	boolean isFirst=true;
	@Override
	public void run() {
		
	while(cIsRunning) {
		 DataOutputStream dos=null;
		 if(isFirst) {
			 // 1. 客户端上线功能
			  System.out.println("请输入用户名:");
		 }else{
			 System.out.println("请输入用发送信息:");
		 }
		 isFirst=false;
		 
			Scanner sc=new Scanner(System.in);
			String sendMsg= sc.next();	
	//BufferedReader sc=new BufferedReader(new InputStreamReader(System.in));
	
		
			 
			try {
			//	 String sendMsg= sc.readLine();  
				 if(sendMsg==null && sendMsg!="" && sendMsg.trim()!="") {
					 return ;
				 }
				dos = new DataOutputStream(clinet.getOutputStream());
			//  dos.writeUTF("客户端写入服务器数据110");
				  dos.writeUTF(sendMsg);
				  dos.flush();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				cIsRunning= false;   // 出异常了,停止发送数据
				CloseUtils.closeAll(dos);
			
			}
	}
	
	}
}).start();
 
	// 2.  客户端接收、发送消息位于不同线程中
	  new Thread(new Runnable() {
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(sIsRunning) {
				 DataInputStream dis=null;
				try {
					dis = new DataInputStream(clinet.getInputStream());
					 String result = dis.readUTF();
					 System.err.println("服务端回的数据:"+result);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					sIsRunning=false;
					CloseUtils.closeAll(dis);
				}
			}
			
		}
	}).start();
	  
	
	 	  
   }
}

CloseUtils 工具类: 

package com.denganzhi.multi;

import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;

public class CloseUtils {
	
	 // 可变参数 ,工具类关闭流
	 // 规则: 如果有其他参数,只能在最后一个位置,处理方式和数组一致
	 // 
	 public static void closeAll(Closeable ... io){
		for (Closeable closeable : io) {
		    if(closeable!=null){
		    	try {
					closeable.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		    }
		}
	 }

	
}

 Server服务端终端控制台输出:

 ClientA客户端控制台输出:

 ClientB客户端控制台输出:

 源码地址:https://download.csdn.net/download/dreams_deng/12305064

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值