網絡編程__【TCP傳輸】(重點)【Socket & ServerSocket】


概述:

TCP是面向鏈接的,在創建socket服務時就要有服務端存在,鏈接成功造成通路後,在該通道內進行數據的傳輸。java

與UDP不一樣,TCP加入了網絡流的概念,做爲客戶端InputStream的源
安全

TCP傳輸步驟:服務器

Socket和ServerSocket
創建客戶端和服務器端
創建鏈接後,經過Socket中的IO流進行數據的傳輸
關閉socket
一樣,客戶端與服務器端是兩個獨立的應用程序。
網絡

1、TCP傳輸示例

客戶端Socket
Socket對象在創建時就能夠去鏈接指定主機
步驟:
1,建立Socket服務,明確要鏈接的主機和端口
2,經過Socket流獲取iol流對象,進行數據的傳輸
3,關閉資源
多線程

import java.net.*;
import java.io.*;
class  TcpClient
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket("127.0.0.1",10005);
		OutputStream out = s.getOutputStream();
		out.write("TCP is coming".getBytes());
		s.close();
	}
}

服務端:ServerSocket
1,創建服務端的socket服務,並監聽一個端口
2,獲取連接過來的客戶端對象;經過ServerSocket的accept()方法
該方法是阻塞式的,須要等待鏈接
3,客戶端若是發送數據,服務端要經過對應的客戶端對象獲取該客戶端對象的讀取流來讀取發送過來的數據
打印在控制檯
4, 關閉客戶端和服務端(可選)
併發

class  TcpServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10005);
		Socket s = ss.accept();	//該阻塞式方法獲取到客戶端對象
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"...connected");

		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf,0,len));
		
		s.close();	//關閉客戶端,節省資源
		ss.close();	//關閉服務端(可選)
	}
}

二;演示TCP傳輸客戶端和服務端互訪

需求:客戶端給服務端發送數據,服務端收到後給客戶端反饋信息

客戶端:
1,創建Socket服務,指定要鏈接的主機和端口
2,獲取socket流中的輸出流,將數據寫到該流中
3,獲取Socket流中的輸入流,獲取服務端反饋
4,關閉客戶端資源
socket

import java.io.*;
import java.net.*;
class TcpClient2
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket("127.0.0.1",10008);
		OutputStream out = s.getOutputStream();
		out.write("服務端你好,我是客戶端".getBytes());//發送信息

		InputStream in = s.getInputStream();	//讀取反饋
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf,0,len));
		s.close();
	}
}

服務端,接收數據進行處理並給客戶端作出反饋

class TcpServer2
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10008);
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		InputStream in = s.getInputStream();//讀取信息
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(ip+":"+(new String(buf,0,len)));

		OutputStream out = s.getOutputStream();//發送反饋
		out.write("服務端收到!".getBytes());

		s.close();
		ss.close();
	}
}

3、需求:創建一個文本轉換服務器

客戶端給服務端發送文本,服務端將文本轉成大寫返回給客戶端
客戶端能夠持續進行文本轉換,當客戶端輸入over時,轉換結束。

分析:
客戶端:
操做設備上的數據,可使用IO技術,並按照io的操做規律來思考
源:鍵盤錄入
目的:網絡設備,網絡輸出流。
操做的是文本數據,可使用字符流,加入緩衝區提升效率

客戶端步驟:
1,創建服務
2,獲取鍵盤錄入
3,將數據發給服務端
4,獲取服務端返回的大寫數據
5,關閉資源
this

import java.io.*;
import java.net.*;
class TransClient
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket("127.0.0.1",10086);
		//定義源,鍵盤錄入數據
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		//定義目的,將數據寫入到Socket輸出流,發送給服務器
//		BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);//替換BufferedWriter
		//定義源:取Socket輸入流,接收服務器返回的信息
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		String line = null;
		while ((line=bufr.readLine()) !=null)
		{
			if ("over".equals(line))
				break;
			out.println(line);//目的:打印流,自動換行刷新
			String str = bufIn.readLine();
			System.out.println("server"+str);
		}
		bufr.close();
		s.close();
	}
}

服務端:

class TransServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10086);
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"Connect");
		//源,讀取Socket輸入流中的數據
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		//目的,Socket輸出流,將大寫數據寫入到socket輸出流,反饋給客戶端
//		BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);//打印流,替代輸出流
		
		String line = null;
		while ((line=bufIn.readLine()) !=null)
		{
			System.out.println(line);
			out.println(line.toUpperCase());//自動換行刷新,一步頂三步替換write/newLine/flush
		}
		s.close();
		ss.close();
	}
}

運行結果:spa



可能會出現的問題:
1,客戶端和服務端都在等待,沒法進行操做
緣由:
客戶端和服務端都有阻塞式的方法,沒讀到結束標記就一直阻塞,致使兩端等待
如readLine()讀到換行符纔會中止,須要加上手動換行標記,使其在流中傳輸時仍有換行標記;


2,可使用PrintWriter來替代BufferedWriter。能夠簡化操做步驟;如println()方法能夠替代字符流緩衝區的write/newLine/flush三個方法
.net

      PrintWriter();的構造方法不要忘記加"true"

4、Tcp上傳文本文件

客戶端上傳文本文件到服務端,服務端保存後給客戶端作出反饋

用到io流的字符流,使用緩衝區提升效率

客戶端:

import java.io.*;
import java.net.*;
class  TextClient
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket("127.0.0.1",10086);
		BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java"));//源:讀取文件
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);//目的:將數據寫入到Socket寫入流
		String line = null;
		while ((line=bufr.readLine()) !=null)
		{
			out.println(line);
		}
		s.shutdownOutput();//關閉客戶端的輸出流,至關於給流中加一個結束標記-1

		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));//源:獲取客戶端反饋
		String str = bufIn.readLine();		//阻塞式方法,等待服務端Socket流的反饋,會致使客戶端阻塞
		System.out.println(str);	//目的:打印到控制檯
		bufr.close();
		s.close();
	}
}

服務端:

class  TextServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10086);
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"connected");
		//源:獲取Socket輸入流的數據
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		PrintWriter out = new PrintWriter(new FileWriter("server.txt"),true);//目的:將文件寫入到目的地
		String line = null;
		while ((line=bufIn.readLine()) !=null)//阻塞式方法:無結束標記就會一直等待
		{
			out.println(line);	//
		}
		PrintWriter pw = new PrintWriter(s.getOutputStream());//服務端阻塞,Socket沒法到達客戶端
		pw.println("文件上傳成功");	//循環不結束,下面就執行不到,
		out.close();
		s.close();
		ss.close();
	}
}

運行結果:



【小結】
由於用到阻塞式方法,會形成兩端阻塞等待,因此每次TCP會話都要定義結束標記
定義結束標記方法:
1,自定義文字標記,例如輸入字符"over"結束程序
優勢:簡單易用。缺點:若是文本中出現該字符會致使操做提早結束
2,使用時間戳:long time = System.currentTimeMillis();
使用DataOutputStream獲取Socket中流;操做readLong
優勢:時間惟一,不會重複。缺點:操做太繁瑣
3,Socket自帶方法;shutdownOutput()
簡單易用,不會重複


5、TCP客戶端併發上傳圖片

需求:實現多個客戶端同時向服務端發送圖片,服務端保存圖片並返回給客戶端上傳成功的信息

分析:圖片傳輸要用到io流中的字節流傳輸;    服務端要對輸入的路徑進行健壯性判斷:文件是否存在、圖片格式、大小等;   服務端要同時對多個客戶端請求進行處理,須要用到多線程技術,將要處理的任務封裝到線程對象中,客戶端每發起一個成功的請求服務端就開闢一條線程;若是客戶端重複上傳了同一個文件就給文件加標識。

步奏:客戶端:

1,建立服務
2,讀取客戶端已有的圖片數據
3,經過Socket輸出流將數據發送給服務端
4,讀取服務端的反饋信息
5,關閉資源

import java.io.*;
import java.net.*;
class  PicClient
{
	public static void main(String[] args) throws Exception
	{	//從命令行輸入一個路徑,路徑必須是一個連續的字符串
		if (args.length==0){//若是沒有傳參數
			System.out.println("請選擇一張圖片");
			return;
		}
		File file = new File(args[0]);
		if(!(file.exists() && file.isFile())){//若是路徑不存在或者不是文件
			System.out.println("未找到文件,請從新輸入");
			return;
		}
		if (file.getName().endsWith(".jpg")){
			System.out.println("格式錯誤:jpg");
			return;
		}
		if (file.length()>10240*1024*3){//圖片大小限制在3M之內
			System.out.println("文件過大,請壓縮到3M之內上傳");
			return;
		}
		//以上爲上傳圖片前客戶端的建壯性判斷;下面開始圖片的上傳
		Socket s = new Socket("127.0.0.1",10086);
		FileInputStream fis = new FileInputStream(file);
		OutputStream out = s.getOutputStream();
		byte[] buf = new byte[1024];
		int len = 0;
		while ((len=fis.read(buf)) !=-1)
		{
			out.write(buf,0,len);
		}
		s.shutdownOutput();//數據寫完時通知服務端,再也不阻塞

		InputStream in = s.getInputStream();//獲取服務端反饋
		byte[] bufIn = new byte[1024];
		int num = in.read(bufIn);
		System.out.println(new String(bufIn,0,num));
		fis.close();
		s.close();
	}
}

服務端:

侷限性:
服務端一次只能處理一個客戶端請求,該請求沒有執行完就沒法循環回來執行accept();其它客戶端請求只能等待
爲了可以讓多個客戶端併發訪問服務端,在服務端引入多線程技術;將每一個客戶端請求封裝到一個單獨的線程中進行處理
定義線程:
只要明確客戶端在服務端執行的代碼便可,將該任務存入run()方法中

class  PicServer //服務端
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10086);
		while (true)
		{
			Socket s = ss.accept();//獲取socket對象
			new Thread(new PicThread(s)).start();//將socket對象封裝進線程對象,經過Thread開啓線程
		}
	}
}
class PicThread implements Runnable//線程任務
{
	private Socket s;
	PicThread(Socket s){
		this.s = s;
	}
	public void run()//實現run 方法
	{
		int count = 0;//要定義成局部,循環判斷;若是count定義爲成員會共享該數據,並且會形成線程安全問題
		String ip = s.getInetAddress().getHostAddress();
		try
		{
			System.out.println(ip+"...Conected");
			InputStream in = s.getInputStream();//源:獲取客戶端文件

			File file = new File(ip+".jpg");
			while(file.exists())//循環判斷在服務端該文件是否存在
				file = new File(ip+"("+(++count)+")"+".jpg");

			FileOutputStream fos = new FileOutputStream(file);//目的:
			byte[] buf = new byte[1024];
			int len = 0;
			while ((len=in.read(buf)) !=-1)
			{
				fos.write(buf,0,len);
			}
			OutputStream out = s.getOutputStream();//通知客戶端上傳成功
			out.write("圖片上傳成功".getBytes());
			fos.close();
			s.close();
		}
		catch (Exception e){
			throw new RuntimeException(ip+":圖片上傳失敗!");
		}
	}
}

運行結果:


【總結:】

在實際的使用過程當中,服務端不可能一次只處理一個客戶端的請求,一般是大量客戶端的併發訪問;該示例是服務器多線程處理的雛形,要作重點掌握


6、客戶端併發登錄

客戶端併發登錄
需求:實現多個客戶端同時登錄服務端
分析:
客戶端將登錄信息發送給服務端,服務端經過驗證後確認是否夠登錄,並將登錄結果反饋給客戶端;爲防止服務器壓力過大,每一個用戶名只能登錄三次。
要點:服務端須要用到多線程處理多條請求,使用io流驗證用戶信息和傳輸數據

代碼實現:

import java.net.*;
import java.io.*;
class  LoginCilent//客戶端
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket("127.0.0.1",10086);
		BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));//用戶輸入
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);	//發送到服務器
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));//獲取反饋
		
		for (int x=0; x<3 ;x++ )
		{
			String line = buf.readLine();
			if (line == null)
				break;
			out.println(line);

			String info = bufIn.readLine();//讀取反饋
			System.out.println("info:"+info);
			if(info.contains("歡迎"))//登錄成功的信息
				break;
		}
		buf.close();
		s.close();
	}
}

class LoginServer//服務端
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10086);
		while (true)//服務端應長時間開啓等待客戶端訪問
		{
			Socket s = ss.accept();
			new Thread(new UserThread(s)).start();
		}
	}
}
class UserThread implements Runnable//線程任務
{
	private Socket s;
	UserThread(Socket s){
		this.s = s;
	}
	public void run()
	{
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"...connected...");
		try
		{
			for (int x=0; x<3 ;x++ )
			{
				BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
				String name = bufIn.readLine();
				if(name==null)
					break;

				BufferedReader bufr = new BufferedReader(new FileReader("User.txt"));
				PrintWriter out = new PrintWriter(s.getOutputStream(),true);
				String line = null;
				boolean flag = false;
				while ((line=bufr.readLine()) !=null)
				{
					if(line.equals(name))
					{
						flag = true;
						break;//找到用戶就跳出當前while循環,將標記改成true
					}
				}
				if (flag)//由標記來決定是否執行
				{
					System.out.println(name+"已登陸");//服務端監控
					out.println(name+": 登錄成功,歡迎光臨");//反饋
					break;	//跳出for循環
				}
				else
				{
					System.out.println(name+"嘗試登錄");	//服務端監控
					out.println(name+": 用戶名不存在!");//反饋
				}
			}
			s.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException("驗證失敗"+ip);
		}
	}
}
運行結果:

相關文章
相關標籤/搜索