网络编程__【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);
		}
	}
}
运行结果:

相关文章
相关标签/搜索