网络编程:Socket与JavaSE线程高级

本文详细介绍了网络通信的基本概念和技术,包括UDP和TCP协议的实现方式,并深入探讨了线程高级应用,如ThreadLocal的使用、线程池的工作原理、锁的Condition对象的应用、多线程共享变量的处理及读写锁的实际案例。



一:Socket:套接字,通信的端点。

              就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在            两个Socket间通过IO传输。

        1.UDP传输:

                 1.1.只要是网络传输,必须有socket。

                 1.2.数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。

                        直接操作udp不可能,对于java语言应该将udp封装成对象,易于我们的使用,这个对象就是                                DatagramSocket封装了udp传输协议的socket对象。

                        因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对象。这个数据包                         对象是:就DatagramPacket.通过这个对象中的方法,就可以获取到数据包中的各种信息。

                        DatagramSocket具备发送和接受功能,在进行udp传输时,需要明确一个是发送端,一个是接                           收端。

 

        2.UDP的发送端:

                 2.1.建立udp的socket服务,创建对象时如果没有明确端口,系统会自动分配一个未被使用的端口。

                 2.2.明确要发送的具体数据。

                 2.3.将数据封装成了数据包。

                 2.4.用socket服务的send方法将数据包发送出去。

                 2.5.关闭资源。

代码如下:

public class UdpSendTest {
	public static void main(String[] args) throws IOException {
		/**
		 * 1.建立udp的socket服务
		 * 2.将要发送的数据封装到包中
		 * 3.创建udp套接字服务将数据包发送出去
		 * 4.关闭socket服务
		 */
		System.out.println("发送端启动。。。。");	
		//1.建立udp的socket服务,使用DatagramSocket对象
		DatagramSocket socket = new DatagramSocket();
		//2.将要发送的数据封装到包中,使用DatagramPacket对象
		String str = "我来啦!!!!!!!!!!!!!!!";
		byte[] buf = str.getBytes();
		DatagramPacket packet = new DatagramPacket(buf, buf.length,InetAddress.getByName("127.0.0.1"),9527);
		//3.创建udp套接字服务奖数据包发送出去
		socket.send(packet);
		//4.关闭socket服务
		socket.close();		
	}
}

       3.UDP的接收端:

                3.1.创建udp的socket服务,必须要明确一个端口,作用在于,只有发送到这个端口的数据才是这个                         接收端可以处理的数据。

                3.2.定义数据包,用于存储接收到数据。

                3.3.通过socket服务的接收方法将收到的数据存储到数据包中。

                3.4.通过数据包的方法获取数据包中的具体数据内容,比如ip、端口、数据等等。

                3.5.关闭资源。

代码如下:
public class UdpReceiveTest {
	public static void main(String[] args) throws IOException {
		/**
		 * 1.建立udp的socket服务
		 * 2.创建数据包
		 * 3.使用udp的socket服务接受数据
		 * 4.解析数据包
		 * 5.关闭socket服务
		 */
		System.out.println("接收端启动。。。。");	
		//1.建立udp的socket服务,使用DatagramSocket对象
		DatagramSocket socket = new DatagramSocket(9527);//绑定端口号
		//2.创建数据包,使用DatagramPacket对象
		byte[] buf = new byte[1024];
		DatagramPacket packet = new DatagramPacket(buf, buf.length);
		//3.使用udp的socket服务接受数据
		socket.receive(packet);//等待接受数据,没有接收的话会一直等待
		//4.解析数据包
		String ip = packet.getAddress().getHostAddress();
		int port = packet.getPort();
		String str = new String(packet.getData(),0,packet.getLength());
		//5.关闭socket服务
		socket.close();	
		
		System.out.println("ip:"+ip+" port:"+port);
		System.out.println("data:"+str);
	}
}


          4.TCP传输:两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上                                 的流,称之为socket流。该流中既有读取,也有写入。

                tcp的两个端点:一个是客户端,一个是服务端。

               客户端:对应的对象,Socket

               服务端:对应的对象,ServerSocket

 

          5.TCP客户端:

                 5.1.建立tcp的socket服务,最好明确具体的地址和端口。这个对象在创建时,就已经可以对指定ip和                        端口进行连接(三次握手)。

                 5.2.如果连接成功,就意味着通道建立了,socket流就已经产生了。只要获取到socket流中的读取流                        和写入流即可,只要通过getInputStream和getOutputStream就可以获取两个流对象。

                 5.3.关闭资源。

代码如下:

public class TcpClientTest {
	public static void main(String[] args) throws IOException{
		//客户端发数据到服务端
		/**
		 * Tcp传输,客户端建立的过程
		 * 1.创建Tcp客户端Socket服务,使用的是Socket对象,建议该对象一创建就明确目的地,要连接的主机
		 * 2.若果连接建立成功,说明数据传输通道已建立
		 *     该通道就是Socket流,是底层建立的。
		 *     既然是流,就有输出流和输入流,该流得通过Socket来获取,
		 *     通过socket.getInputStream()和getOutoutStream()方法
		 * 3.使用数据流将数据写出
		 * 4.关闭资源。
		 */
		System.out.println("客户端启动。。。。。。。。。。");
		//1.创建客户端Socket
		Socket socket = new Socket("127.0.0.1",9527);
		//2.获取流
		OutputStream out = socket.getOutputStream();
		//3.使用数据流将数据写出
		out.write("tcp演示。。。。。。".getBytes());
		//接受服务器端回送的数据
		InputStream in = socket.getInputStream();
		byte[] buf = new byte[1024]; 
		in.read(buf);
		System.out.println(new String(buf,0,buf.length));
		//4.关闭资源
		in.close();
		socket.close();//关闭资源就是断开连接,同时流也会关闭 	
	}
}


         6.TCP服务端:

               6.1.创建服务端socket服务,并监听一个端口。

               6.2.服务端为了给客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端                        对象。

               6.3.可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。

               6.4.如果通讯结束,关闭资源。注意:要先关客户端,再关服务端。

代码如下:

public class TcpServerTest {
	public static void main(String[] args) throws IOException{
		//服务器端接受客户端发来的数据并打印在控制台上
		/**
		 * Tcp传输,服务器建立的过程
		 * 1.创建Tcp客户服务器端ServerSocket服务,使用的是ServerSocket对象
		 * 2.服务器端必须提供一对外借口,否则客户端无法连接
		 * 3.获取连接过来的客户端对象
		 * 4.通过客户端对象获取Socket流读取客户端的数据并打印在控制台上
		 * 5.关闭资源。
		 */
		System.out.println("服务器端启动。。。。。。。。。。");
		//1.创建客户端Socket
		ServerSocket serverSocket = new ServerSocket(9527);
		//2.获取客户端对象
		Socket socket = serverSocket.accept();//若没有客户端来连接就一直等待(阻塞)
		//3.通过客户端对象获取流
		InputStream in = socket.getInputStream();
		//4.获取流中的数据
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		String str = new String(buf,0,len);
		System.out.println(str);
		//向客户端回送数据
		OutputStream out = socket.getOutputStream();
		out.write("收到".getBytes());
		//5.关闭资源
		out.close();
		socket.close();
		serverSocket.close();//关闭资源就是断开连接,同时流也会关闭 	
	}
}



二:线程高级部分:

1.ThreadLocal线程范围内的共享变量:
 
代码如下:


package com.ThreadLocal;

import java.util.Random;
//ThreadLocal线程范围内的共享变量
public class ThreadLocalTest {
	//private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
	private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
	public static void main(String[] args) {
		
		for(int i=0;i<2;i++){
			new Thread(new Runnable(){
				int data = new Random().nextInt();
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName()+" "+data);
					//threadLocal.set(data);
					
					MyThreadScopeData.getThreadInstance().setName(Thread.currentThread().getName());
					//当获取MyThreadScopeData的实例时已经将该实例放入线程对象map中,要在次获取时是从map中取出来的
					MyThreadScopeData.getThreadInstance().setAge(data);
					new A().get();
					new B().get();
				}				
			}).start();
		}
	}
	static class A{
		public void get(){
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("class A:"+Thread.currentThread().getName()+" get name: "+myData.getName()
					+",  get Age:"+myData.getAge());
		}
	}
	static class B{
		public void get(){
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("class B:"+Thread.currentThread().getName()+" get name: "+myData.getName()
					+",  get Age:"+myData.getAge());
		}
	}
}
class MyThreadScopeData{
	private MyThreadScopeData(){};//私有构造函数,只能内部自己实例化
	
	public static MyThreadScopeData getThreadInstance(){//实例化对象
		MyThreadScopeData instance = map.get();
		if(instance == null){
			instance = new MyThreadScopeData();
			map.set(instance);
		}
		return instance;
	}
	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();//创建ThreadLocal对象
	
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	private int age;
}


2.线程池的使用:

代码如下:

package com.ThreadPool;

import java.util.concurrent.ExecutorService;
//线程池的使用
public class ThreadPool {
	public static void main(String[] args) {
		//ExecutorService threadPool = Executors.newFixedThreadPool(3);//创建具有固定线程的线程池
		//ExecutorService threadPool = Executors.newCachedThreadPool();//创建线程缓存池,根据要执行的线程自动在线程池里面创建线程
		
		ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建只有一个线程的线程池当该线程池中的线程死掉后会自动在创建一 个线程
		                                                                 //线程死掉后重启        
		for(int i=1;i<=10;i++){
			final int task = i;
			threadPool.execute(new Runnable() {
				@Override
				public void run() {
					for (int i = 1; i <= 10; i++) {
						System.out.println(Thread.currentThread().getName()
								+ " is running !  " + i + "次循环!"+"task "+task);
					}
				}
			});
		}
		threadPool.shutdown();//当线程池没有任务做时关闭线程池
		//threadPool.shutdownNow();//当线程池不管有没有任务做都关闭线程池
		
		Executors.newScheduledThreadPool(3).scheduleAtFixedRate(//线程池调度
				new Runnable() {
					
					@Override
					public void run() {
						System.out.println("booming");
						
					}
				}, 
				2, 
				5, 
				TimeUnit.SECONDS);
	}

}


3.锁的Condition对象:

代码如下:

package com.Thread;
import java.util.concurrent.locks.Condition;

//传统线程互斥与同步
//锁的Condition对象
//多个Condition对象控制多个线程——阻塞队列编程
public class TraditionalThreadCommunication {

	public static void main(String[] args) {
		final Bussiness business = new Bussiness();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 1; i <= 20; i++) {
					business.sub2(i);
				}
			} 

		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 1; i <= 30; i++) {
					business.sub3(i);
				}
			}

		}).start();
		for (int i = 1; i <= 10; i++) {
			business.main(i);
		}
	}
}

class Bussiness {
	private int bShouldSub = 1;// 1代表主线程走,2代表线程2走,3代表线程3走
	private Lock lock = new ReentrantLock();//Lock锁的功能和synchronized相同,都是实现线程互斥
	private Condition condition1 = lock.newCondition();//condition1对象控制主线程
	private Condition condition2 = lock.newCondition();//condition2对象控制sub2线程
	private Condition condition3 = lock.newCondition();//condition3对象控制sub3线程
	public /*synchronized*/ void main(int i) {
		lock.lock();//锁住
		try {
			while (bShouldSub != 1) {// 用while而不用if防止虚假唤醒
				try {
					condition1.await();// 功能和wait()方法一样
					//this.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			for (int j = 1; j <= 10; j++) {
				System.out.println("main " + j + "循环次数 " + i);
			}
			bShouldSub = 2;
			condition2.signal();// 功能和notify()方法一样
			// this.notify();
		} finally {
			lock.unlock();
		}
	}
	public /*synchronized*/ void sub2(int i) {
		lock.lock();//锁住
		try {
			while (bShouldSub != 2) {// 用while而不用if防止虚假唤醒
				try {
					condition2.await();// 功能和wait()方法一样
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			for (int j = 1; j <= 20; j++) {
				System.out.println("sub2 " + j + "循环次数 " + i);
			}
			bShouldSub = 3;
			condition3.signal();// 功能和notify()方法一样,表示唤醒其他线程
		} finally {
			lock.unlock();
		}
	}
	public /*synchronized*/ void sub3(int i) {
		lock.lock();//锁住
		try {
			while (bShouldSub != 3) {// 用while而不用if防止虚假唤醒
				try {
					condition3.await();// 功能和wait()方法一样
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			for (int j = 1; j <= 30; j++) {
				System.out.println("sub3 " + j + "循环次数 " + i);
			}
			bShouldSub = 1;
			condition1.signal();// 功能和notify()方法一样,表示唤醒其他线程
			// this.notify(),wait()方法必须在synchronized中使用
		} finally {
			lock.unlock();
		}
	}


}

4.多线程共享变量:

代码如下:

package com.MultiThreadSharedData;

public class MultiThreadSharedData {
	private int j;//外部内变量
	public static void main(String[] args) {
		MultiThreadSharedData multiThreadSharedData = new MultiThreadSharedData();
		Inc inc = multiThreadSharedData.new Inc();//通过外部类实例创建内部类实例
		Dec dec = multiThreadSharedData.new Dec();//通过外部类实例创建内部类实例
		for(int i=0;i<2;i++){//创建两个线程
			Thread t1 = new Thread(inc);
			Thread t2 = new Thread(dec);
			
			try {
				t1.start();
				t1.join();//让线程1执行完之后再执行线程2
				
				t2.start();
				t2.join();//让线程2执行完之后再执行线程1
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			
		}
	}
	private synchronized void inc(){//外部内 加 方法操纵外部内变量
		j++;
		System.out.println("j++ "+j);
	}
	private synchronized void dec(){//外部内 减 方法操纵外部内变量
		j--;
		System.out.println("j-- "+j);
	}
	class Inc implements Runnable{//内部类操纵外部类得方法以至于操纵外部类同一数据
		@Override
		public void run() {
			for(int i=1;i<=100;i++){
				inc();
			}
		}		
	}
	class Dec implements Runnable{//内部类操纵外部类得方法以至于操纵外部类同一数据
		@Override
		public void run() {
			for(int i=1;i<=100;i++){
				dec();
			}
		}		
	}

}

5.缓存系统,用读写锁ReadWriteLock实现:

代码如下:

package com.CacheSystem;

import java.util.HashMap;

//写一个缓存系统,用读写锁ReadWriteLock实现
public class CacheTest {
	private static Map<String, Object> map = new HashMap<String, Object>();//集合,用于存放数据
	private static ReadWriteLock rwl = new ReentrantReadWriteLock();//创建一个读写锁
	
	public static void main(String[] args) {
		for(int i=1;i<=10;i++){
			new Thread(new Runnable() {		
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName()+getData("1"));
			}
			}).start();
		}
		
	}
	private static Object getData(String key){//获取数据的函数
		Object value = map.get(key);
		rwl.readLock().lock();//读数据时把读锁锁上,则其他线程只能读
		try{
			if (value == null) {//若读取数据位null				
				rwl.readLock().unlock();//若读取数据为null释放读锁
				rwl.writeLock().lock();//释放读锁之后就能加上写锁,写锁时其他线程不能做任何操作
				try {
					value = map.get(key);
					if (value == null) {//在一次判断数据是否为空,防止其他线程在该线程写完数据之后并且释放写锁时再来写数据
						System.out.println(Thread.currentThread().getName()+" 写数据");
						map.put("1", "aaaa");
						value = map.get(key);
					}
					else{
						System.out.println(Thread.currentThread().getName()+" 读数据");
					}
				} finally {
					rwl.writeLock().unlock();//写完数据之后释放写锁
				}
				rwl.readLock().lock();//写锁完之后再加上读锁
			}
			else{
				System.out.println(Thread.currentThread().getName()+" 读数据");
			}
		}finally{
			rwl.readLock().unlock();//最后释放读锁
		}
		return value;
	}

}


总结:

        socket是网络流,对应着InputStream,可以用来进行网络上大量数据的传输与下载,结合线程使用可以大大加深效率。

        线程的高级应用主要是对线程并发库的使用,其实底层的实现原理也是sycnchronized,wait,sleep,notify等方法的组合,只不过由sun公司给我们包装了,因此我们队线程的使用更加方便。



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值