网络编程二:TCP传输、TCP双向传输、文本转换、并发上传图片、客户端并发登录

1  TCP传输

TCP特点:

      1,建立连接,形成传输数据的通道。

      2,在连接中进行大数据量传输。

      3,通过三次握手完成连接,是可靠协议。

      4,必须建立连接,效率会稍低。

 

演示tcp传输,客户端向服务端发送数据。

      1tcp分客户端和服务端。

      2,客户端对应的对象是Socket

            服务端对应的对象是ServerSocket

      3,建立连接后,通过Socket中的IO流进行数据的传输。

      4,关闭socket

同样,客户端与服务器端是两个独立的应用程序。

 

客户端:

      通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机。

      因为tcp是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功。

      形成通路后,在该通道进行数据传输。通路一建立,Socket流就存在了,包括输入流和输出流。

 

      需求:给服务端发送一个文本数据。

      步骤:

      1,创建Socket服务,并指定要连接的主机和端口。

      2,调用Socket的方法,获取输入流或输出流。

 

服务端:

      需求:定义端点,接收数据,并打印在控制台上。

      服务端没有流对象,拿客户端的流对象和客户端通信。

      步骤:

      1,建立服务端的Socket服务。ServerSocket();

      并监听一个端口。(其实就是给该应用程序打数字标识)

      2,获取连接过来的客户端对象。

            通过ServerSocket的 accept方法。没有连接就会等,所以这个方法是阻塞式的。

      3,客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据,并打印在控制台。

      4,关闭服务端(可选操作)。

 

代码及注释:

import java.net.*;
import java.io.*;

class TcpClient{
	public static void main(String[] args) throws Exception{
		//创建客户端的Socket服务,指定目的主机和端口。
		Socket s = new Socket("127.0.0.1",10003); //必须指定端口建立连接
		
		//为了发送数据,应该获取socket流中的输出流。
		OutputStream out = s.getOutputStream();
		
		//把数据写入socket通道中,服务端再从socket通道读取数据。
		out.write("tcp 哥们来了".getBytes());
		
		s.close();
	}
}

class TcpServer {
	public static void main(String[] args) throws IOException{
		//建立服务端的socket服务,并监听一个端口。
		ServerSocket ss = new ServerSocket(10003);
		
		//通过accept方法获取连接过来的客户端对象。
		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(); //关闭服务端,可选操作。
	}
}

2  TCP传输第二个例子

演示tcp的传输的客户端和服务端的互访,上一个例子中只是客户端向服务端发送数据。

 

需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息。

 

客户端:

      1,建立Socket服务,指定要连接的主机和端口。

      2,获取socket流中的输出流,将数据写到该流中,通过网络发送给服务端。

      3,获取socket流中的输入流,将服务端反馈的数据获取到,并打印。

      4,关闭客户端资源。

 

服务端:

      1,建立ServerSocket服务,指定监听的端口。

      2,获取连接过来的客户端的Socket对象。

      3,获取Socket对象的输入流,用以接收客户端传送过来的数据。

      4,获取Socket对象的输出流,用以服务端向客户端发送反馈数据。

      5,关闭客户端资源,关闭服务端资源(可选操作)。

 

代码和注释:

import java.net.*;
import java.io.*;

class TcpClient2 {
	public static void main(String[] args) throws Exception{
		Socket s = new Socket("192.168.0.100",10004);//建立客户端socket服务,指定目的主机和端口。
		
		OutputStream out = s.getOutputStream(); //输出流,用以向服务端发送数据。
		
		out.write("服务端,你好".getBytes()); //数据写入socket通道中。
		
		InputStream in = s.getInputStream(); //输入流,用以读取服务端返回的数据。
		
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf,0,len));
		
		s.close(); //关闭服务端的socket
	}
}

class TcpServer2 {
	public static void main(String[] args) throws Exception{
		ServerSocket ss = new ServerSocket(10004);//建立服务端socket服务,指定监听的端口。
		
		Socket s = ss.accept();  //获取连接过来的客户端socket对象。
		
		String ip = s.getInetAddress().getHostAddress(); //获取客户端IP地址。
		System.out.println(ip+"......connected");
		
		InputStream in = s.getInputStream(); //Socket输入流,用以接收客户端传送过来的数据。
		
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf,0,len));
		
		OutputStream out = s.getOutputStream();//输出流,用以发送服务端给客户端的反馈数据。
		
		out.write("我收到,客户端,你好".getBytes()); //把数据写入socket通道中.
		
		s.close();
		ss.close();
	}
}

3  TCP传输练习:文本转换

需求:建立一个文本转换服务器。

客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。

而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。

 

客户端:

      既然是操作设备上的数据,就可以使用IO技术,并按照IO操作的规律来思考。

      源:键盘录入。

      目的:Socket输出流。

      步骤:

      1,建立服务。

      2,获取键盘录入。

      3,将数据发给服务端。

      4,获取服务端返回的大写数据。

      5,结束,关资源。

      都是文本数据,可以使用字符流进行操作,同时提高效率,加入缓冲。

服务端:

      源:socket读取流。

      目的:socket输出流。

      都是文本,装饰。

 

该例子会出现的问题。

现象:客户端和服务端都在莫名的等待。为什么呢?

因为客户端和服务端都有阻塞式方法。这些方法没有读到结束标记,那么就一直等待。而导致两端,都在等待。解决方法见下一节。

关键词:阻塞式方法。

 

代码示例:

import java.net.*;
import java.io.*;

class TransClient {
	public static void main(String[] args) throws Exception{
		Socket s = new Socket("192.168.0.100",10005); //建立Socket并连接到目的主机
		
		//定义读取键盘数据的流对象。
		BufferedReader bufr = 
			new BufferedReader(new InputStreamReader(System.in));//源是键盘录入。
			
		//定义目的,将数据写入到socket输出流,发给服务端。
		BufferedWriter bufOut = 
			new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //目的是socket输出流。
		
		//定义一个socket读取流,读取服务端返回的大写信息。
		BufferedReader bufIn = 
			new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		String line = null;
		while((line=bufr.readLine())!=null){
			if(line.equals("over"))
				break;
			bufOut.write(line);
			bufOut.newLine();
			bufOut.flush();
			
			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(10005);//监听10005端口
		Socket s = ss.accept(); //获取客户端的Socket对象,ServerSocket没有IO流
		
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"......connected");
		
		//读取socket读取流中的数据。源。
		BufferedReader bufIn = 
			new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		//目的:socket输出流。将大写数据写入到socket输出流,发送给客户端。
		BufferedWriter bufOut = 
			new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		
		//打印流的println自动刷新缓冲区,而且打印流可接收字节流和字符流。
	//  PrintWriter bufOut = new PrintWriter(s.getOutputStream, true);
		
		String line = null;
		while((line=bufIn.readLine())!=null){
			System.out.println(line);
			bufOut.write(line.toUpperCase());//toUpperCase()字符串转大写。
			bufOut.newLine();
			bufOut.flush();
		}
		
		s.close();
		ss.close();
	}
}

4  TCP客户端并发上传图片

客户端:

      1,创建Socket服务并连接到服务端。

      2,读取客户端本地已有的图片数据。

      3,通过socket输出流,将数据发给服务端。

      4,通过socket读取流,读取服务端反馈信息。

      5,关闭资源。

服务端:

      1,创建ServerSocket服务并监听端口。

      2,获取客户端的Socket对象。

      3,通过Socket对象的socket读取流,读取客户端发来的数据。

      4,通过Socket对象的socket输出流,把反馈信息发送给客户端。

      5,关闭资源。

 

这个服务端有个局限性,当A客户端连接上以后,被服务端获取到,服务端执行具体流程。

这时B客户端连接服务端,就只能等待。

因为服务端还没有处理完A客户端的请求,还没有循环回来执行下次accept方法。

所以暂时获取不到B客户端对象。

 

为了可以让多个客户端同时并发访问服务端,即服务端可以同时处理多个客户端的请求。

那么服务端就用到多线程,就把对每个客户端请求的处理,封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。

 

如何定义线程呢?

只要明确了每一个客户端要在服务端执行的代码即可,将该代码存入run方法中。

 

代码示例:

import java.net.*;
import java.io.*;

class PicClient {
	public static void main(String[] args) throws Exception{
		//只能传一个参数,即只能指定一个路径,一次只能上传一个文件。
		if(args.length!=1){
			System.out.println("请选择一个jpg格式的图片");
			return;
		}
		File file = new File(args[0]);
		
		if(!(file.exists() && file.isFile())){ //文件不存在或不是文件
			System.out.println("该文件有问题,要么不存在,要么不是文件");
			return;
		}
		if(!(file.getName().endsWith(".jpg"))){ //不是jpg图片文件
			System.out.println("图片格式错误,请重新选择");
			return;
		}
		if(file.length()>1024*1024*5){  //图片不能大于5M 
			System.out.println("图片过大");
			return;
		}
	
		Socket s = new Socket("192.168.0.100",10007);//创建Socket并连接到服务端
		
		//要上传的文件,命令:“java PicClient e:\EVE.jpg”
		FileInputStream fis = new FileInputStream(file);
		
		OutputStream out = s.getOutputStream();//Socket输出流
			
		byte[] buf = new byte[1024];
		int len = 0;
		while((len=fis.read(buf))!=-1){
			out.write(buf,0,len);
		}
		
		// 告诉服务端,数据已写完,送一个结束标记过去,阻塞方法继续执行。
		s.shutdownOutput();  //禁用此Socket的输出流,任何以前写的数据都将发送。
		
		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();
	}
}

class PicServer {
	public static void main(String[] args) throws Exception{
		ServerSocket ss = new ServerSocket(10007);//监听指定端口
		
		while(true){
			Socket s = ss.accept(); //获取客户端的Socket对象。
			new Thread(new PicThread(s)).start(); //对客户端请求的处理封装在线程中。
		}
	}
}

class PicThread implements Runnable {
	private Socket s;
	PicThread(Socket s){
		this.s = s;
	}
	public void run(){
		int count = 1;
		String ip = s.getInetAddress().getHostAddress();
		try{
			System.out.println(ip+".....connected");
			InputStream in = s.getInputStream(); //获取Socket输入流
			
			//实现多次上传同一个文件,不覆盖。
			File file = new File("e:\\"+ip+"("+count+")"+".jpg");
			while(file.exists())
				file = new File("e:\\"+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("server:上传图片成功".getBytes());
			
			fos.close();
			s.close();
		}
		catch(Exception e){
			throw new RuntimeException(ip+"上传失败");
		}
	}
}

5  TCP客户端并发登录

客户端通过键盘录入用户名。服务端对这个用户名进行校验。

 

如果该用户存在,在服务端显示“xxx,已登录”。并在客户端显示“xxx,欢迎光临”。

如果该用户不存在,在服务端显示“xxx,尝试登录”。并在客户端显示“xxx,该用户不存在”。

最多就登录三次。

 

用户名写在user.txt中,保存到当前ClassPath目录。

 

代码示例:

import java.net.*;
import java.io.*;

class LoginClient{
	public static void main(String[] args) throws Exception{
		Socket s = new Socket("192.168.0.100",10008); //创建Socket并连接到服务端。
		
		BufferedReader bufr = 
			new BufferedReader(new InputStreamReader(System.in));//键盘录入
			
		PrintWriter out = 
			new PrintWriter(s.getOutputStream(),true);//true代表println自动刷新缓冲区。
		
		BufferedReader bufIn = 
			new BufferedReader(new InputStreamReader(s.getInputStream()));
			
		for(int x=0; x<3; x++){ //最多登录三次
			String line = bufr.readLine();
			if(line==null)
				break;
			out.println(line);
			
			String info = bufIn.readLine();
			System.out.println("info: "+info);
			if(info.contains("欢迎"))
				break;
		}
		bufr.close();
		s.close();
	}
}

class LoginServer {
	public static void main(String[] args) throws Exception{
		ServerSocket ss = new ServerSocket(10008);//创建服务端的ServerSocket
		
		while(true){
			Socket s = ss.accept(); //获取客户端的Socket对象。
			
			//服务端对客户端请求的处理,封装在线程的run方法中。
			new Thread(new UserThread(s)).start();
		}
	}
}

class UserThread implements Runnable{
	private Socket s;
	UserThread(Socket s){ //客户端的Socket对象
		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 =   //获取Socket输入流,读取客户端发送来的数据。
					new BufferedReader(new InputStreamReader(s.getInputStream()));
				
				String name = bufIn.readLine();
				
				BufferedReader bufr = new BufferedReader(new FileReader("user.txt"));
				PrintWriter out = new PrintWriter(s.getOutputStream(),true); //Socket输出流
				
				String line = null;
				boolean flag = false;
				while((line=bufr.readLine())!=null){
					if(line.equals(name)){ //true,则登录成功,否则失败。
						flag = true;
						break;
					}
				}
				
				if(flag){
					System.out.println(name+",已登录");
					out.println(name+",欢迎光临");
					break;
				}
				else{
					System.out.println(name+",尝试登录");
					out.println(name+",用户名不存在");
				}
			}
			s.close();
		}
		catch(Exception e){
			throw new RuntimeException(ip+"登录失败");
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值