網絡編程:Socket與JavaSE線程高級



一: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公司給我們包裝了,因此我們隊線程的使用更加方便。



發佈了46 篇原創文章 · 獲贊 2 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章