Java Socket

Socket编程:

1. 网络基础知识

    如果两台计算机要通过网络进行通信,那么需要满足一些必然的条件:

1. 两台主机它们需要有唯一的标识。用来表示它们所处的身份,他们所在的位置,这就是IP地址

2. 它们需要有共同的语言,否则就会出现言语不通,无法交流,这就是我们的协议

3. 每台主机都需要有相应的端口号,一台主机上可以运行多个应用程序,那如何辨别不同应用程序的通信,我们需要使用端口号来进行区分

    这是两台主机通过网络进行通信的必要条件:1. IP地址 2. 协议 3. 端口号 

    TCP/IP协议

    TCP/IP是目前世界上应用最为广泛的协议。

    是以TCP和IP为基础的不同层次上多个协议的集合。

    也称为:TCP/IP协议族 或TCP/IP协议栈。

    两个主机要进行通信,都要遵循TCP和IP的协议:

    TCP: Transmission Control Protocol 传输控制协议

    IP: Internet Protocol 互联网协议

在实际的应用当中,一般会将网络分层:


常见的会将网络分为5层。

物理层:是我们用户最直观接触到的,例如网线,网卡,双绞线等等...

而TPC/IP协议是在第4层的传输层

第5层应用层也是用户直接接触到的,应用层有许多的协议,例如:

HTTP超文本传输协议

FTP文件传输协议

SMTP简单邮件传送协议

Telnet远程登陆服务

IP地址:

为实现网络中不同计算机之间的通信,每台机器都必须有一个唯一的标识---IP地址。

端口号:

1. 用于区分不同应用程序。

2. 端口号范围为0~65535,其中0~1023为系统所保留

3. IP地址加上端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础。

4. http:80    ftp : 21      telent : 23

Java中网络支持:

1. InetAddress : 用于标识网络上的硬件资源。说白了主要用来标记IP地址相关信息

2. URL : 统一资源定位符,通过URL可以直接读取或写入网络上的数据。

3. Socket : 使用TCP协议实现网络通信的Socket相关的类。

4. Datagram : 使用UDP协议,将数据保存在数据报中,通过网络进行通信。

2. InetAddress类

    1. InetAddress类用于表示网络上的硬件资源,表示互联网协议(IP)地址。

    InetAddress类没有构造方法,但是有许多静态的方法。详细可查看官方API。

    Java提供了InetAddress类来代表IP地址,InetAddress下还有2个子类:Inet4Address、Inet6Address,它们分别代表Internet Protocol version 4(IPv4)地址和Internet Protocol version 6(IPv6)地址。

  InetAddress类没有提供构造器,而是提供了如下两个静态方法来获取InetAddress实例:

  getByName(String host):根据主机获取对应的InetAddress对象。

  getByAddress(byte[] addr):根据原始IP地址来获取对应的InetAddress对象。

  InetAddress还提供了如下三个方法来获取InetAddress实例对应的IP地址和主机名:

  String getCanonicalHostName():获取此 IP 地址的全限定域名。

  String getHostAddress():返回该InetAddress实例对应的IP地址字符串(以字符串形式)。

  String getHostName():获取此 IP 地址的主机名。

  除此之外,InetAddress类还提供了一个getLocalHost()方法来获取本机IP地址对应的InetAddress实例


3.  URL

    1. URL(Uniform Resource Locator)统一资源定位符,表示Internet上某一资源的地址。

    2. URL由两部分组成:协议名称和资源名称,中间用冒号隔开。

    以下为URL中的一些方法:

public static void main(String[]args){
		try {
			//创建一个URL的实例
			URL imooc = new URL("http://www.imooc.com");
			//?后面表示参数,#后面表示锚点
			URL url = new URL(imooc, "/index.html?username=tom#test");
			System.out.println("协议:" + url.getProtocol());
			System.out.println("主机:" + url.getHost());
			//如果未制定端口号,则使用默认的端口号,此时getPort()方法返回值为-1
			System.out.println("端口号:" + url.getPort());
			System.out.println("文件路径:" + url.getPath());
			System.out.println("文件名:" + url.getFile());
			//相对路径及为#号后面的锚点
			System.out.println("相对路径:" + url.getRef());
			System.out.println("查询字符串:" + url.getQuery());
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}

输出结果如下:


使用URL读取网页内容

1. 通过URL对象的openStream()方法可以得到指定资源的输入流。

2. 通过输入流可以读取,访问网络上的数据。

public static void main(String[]args){
		try {
			URL url = new URL("http://www.baidu.com");
			//通过URL的openStream方法获取URL对象所表示的资源的字节输入流
			InputStream is = url.openStream();
			//将字节输入流转换成字符输入流
			InputStreamReader isr = new InputStreamReader(is,"utf-8");
			//再为字符输入流添加缓冲,提高读取的效率
			BufferedReader br = new BufferedReader(isr);
			//读取数据
			String data = br.readLine();
			while(data != null){
				System.out.println(data);
				data = br.readLine();
			}
			br.close();
			isr.close();
			is.close();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

会将百度页面的html内容显示出来,如下图所示:



4. TCP编程

TCP协议是面向连接,可靠的,有序的,以字节流的方式发送数据。

基于TCP协议实现网络通信的类:

    1. 客户端的Socket类

    2. 服务器端的ServerSocket类

Socket通信模型:

两台主机进行通信,则其中一台就为服务器端,另一台为客户端。

以下为通信模型:


    1. 首先要在Server(服务器)端创建一个ServerSocket,服务器的Socket,绑定相对应的端口,并且再指定的端口进行监听,等待客户端的连接。

    2. 当在客户端创建Socket并且向服务器端发送请求,然后与此同时服务器端收到请求并且接受客户端请求的信息

    3. 一旦接受到客户端请求之后,会创建一个连接Socket,用来与客户端Socket进行通信。

    4. 那么应该如何进行通信,就是通过输入流和输出流,InputStream和OutputStream进行数据的交换,数据的发送,接收以及数据的响应等等。

    5. 如果客户端和服务端双方通信完以后,我们需要分别关闭两端的Socket进行通信的断开。

以上为基于TCP的Socket通信,整个通信的过程。


类 ServerSocket : 此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

类 Socket : 此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。


接下来举一个简单的小例子:

登录的过程:

客户端向服务端输入用户名和密码,服务端响应客户端,“表示欢迎”。

流程如下:


 

代码如下:

服务器端:

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 基于TCP协议的Socket通信,实现用户登陆
 * 服务器端
 * @author Hubbert
 *
 */
public class Server {
	public static void main(String[] args) {
		try {
			//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并侦听此端口。
			ServerSocket serverSocket = new ServerSocket(8888);
			//2.调用ServerSocket的accept()方法开始侦听,等待客户端的连接
			//为了便于查看服务器端的信息
			System.out.println("***服务器即将启动,等待客户端的连接***");
			Socket  socket = serverSocket.accept();
			//3.获取输入流,并读取客户端信息
			InputStream is = socket.getInputStream();
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String info = null;
			//循环读取客户端信息
			while( (info = br.readLine()) != null ){
				System.out.println("我是服务器,客户端说:" + info);
			}
			//4.获取输出流,相应客户端的请求
			OutputStream os = socket.getOutputStream();
			PrintWriter pw = new PrintWriter(os);
			pw.write("欢迎您!");
			pw.flush();//刷新缓存,向客户端输出信息
			socket.shutdownOutput();//关闭输出流
			socket.shutdownInput(); //关闭输入流
			//5.关闭资源
			pw.close();
			os.close();
			br.close();
			isr.close();
			is.close();
			serverSocket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

客户端:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 客户端
 * @author Hubbert
 *
 */
public class Client {
	public static void main(String[] args) {
		
		try {
			//1.创建客户端Socket,指定服务器地址和端口
			Socket socket = new Socket("localhost",8888);
			//2.如果服务器一旦接受请求以后,客户端和服务器就建立起了连接,客户端可以向服务器端发送登陆信息
			//获取输出流,用来向服务器端发送信息
			OutputStream os = socket.getOutputStream();//字节输出流
			PrintWriter pw = new PrintWriter(os);//将输出流包装成为打印流
			pw.write("用户名:admin,密码:123");
			pw.flush();//刷新缓存,向服务器端输出信息
			socket.shutdownOutput();//关闭socket输出流
			//3.获取输入流,来接受服务器端的相应
			InputStream is = socket.getInputStream();
			BufferedReader br = new BufferedReader( new InputStreamReader(is));
			String info = null;
			while( (info = br.readLine()) != null){
				System.out.println("我是客户端,服务器端说:" + info);
			}
			socket.shutdownInput();
			//4.关闭资源
			br.close();
			is.close();
			pw.close();
			os.close();
			socket.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

必须先打开服务器端,点击Run:


然后打开客户端,点击Run:


如图所示可以进行切换Server和Client:


切换到客户端后:



经过TCP Socket的编程以后,即上述中的一个服务器和一个客户端的对话。


但现实生活中,往往是在服务器端运行一个永久的程序,它可以同时跟多个客户端进行通信,并且可以提供多个客户端的请求,提供相对应的服务。


那么我们应该如何实现一个服务端跟多个客户端进行通信呢?应该使用什么技术来实现呢?

-----可以使用多线程服务器

应用多线程来实现服务器与多个客户端之间的通信

基本步骤:

    1. 服务器端创建一个ServerSocket,循环调用accep()来侦听客户端发送过来的请求

    2. 客户端创建Socket,并请求和客户端的连接。

    3. 服务器端接受客户端请求,创建socket与该客户建立专线连接

    4. 建立连接的两个socket在一个单独的线程上对话

    5. 服务器端继续等待新的连接

代码如下:

//服务器线程类

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 服务器线程处理类
 * @author Hubbert
 *
 */
public class ServerThread extends Thread{
	// 和本线程相关的Socket
	Socket socket = null;
	
	public ServerThread(Socket socket){
		this.socket = socket;
	}
	
	//线程执行操作,响应客户端的请求
	@Override
	public void run(){
		InputStream is = null;
		InputStreamReader isr = null;
		BufferedReader br = null;
		OutputStream os = null;
		PrintWriter pw = null;
		try {
			//获取输入流,并读取客户端信息
			is = socket.getInputStream();
			isr = new InputStreamReader(is);
			br = new BufferedReader(isr);
			String info = null;
			//循环读取客户端信息
			while( (info = br.readLine()) != null){
				System.out.println("我是服务器,客户端说:" + info);
			}
			//获取输出流,相应客户端的请求
			os = socket.getOutputStream();
			pw = new PrintWriter(os);
			pw.write("欢迎您!");
			pw.flush();//刷新缓存,向客户端输出信息
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				//5.关闭资源
				if(pw != null){
					pw.close();
				}
				if(os != null){
					os.close();
				}
				if(br != null){
					br.close();
				}
				if(isr != null){
					isr.close();
				}
				if(is != null){
					is.close();
				}
				if(socket != null){
					socket.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

//服务端

package com.imooc.ServerThread;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) {
		try {
			//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并侦听此端口。
			ServerSocket serverSocket = new ServerSocket(8888);
			
			//为了便于查看服务器端的信息
			System.out.println("***服务器即将启动,等待客户端的连接***");
			//记录客户端的数量
			int count = 0;
			Socket socket = null;
			//循环侦听等待客户端的连接
			while(true){
				//2.调用ServerSocket的accept()方法开始侦听,等待客户端的连接
				socket = serverSocket.accept();
				//3. 创建一个新的线程
				ServerThread serverThread = new ServerThread(socket);
				//4. 启动线程,执行客户端的通信
				serverThread.start();
				count++;
				System.out.println("客户端的数量为:" + count);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

//客户端

package com.imooc.ServerThread;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 客户端
 * @author Hubbert
 *
 */
public class Client {
	public static void main(String[] args) {
		
		try {
			//1.创建客户端Socket,指定服务器地址和端口
			Socket socket = new Socket("localhost",8888);
			//2.如果服务器一旦接受请求以后,客户端和服务器就建立起了连接,客户端可以向服务器端发送登陆信息
			//获取输出流,用来向服务器端发送信息
			OutputStream os = socket.getOutputStream();//字节输出流
			PrintWriter pw = new PrintWriter(os);//将输出流包装成为打印流
			pw.write("用户名:admin,密码:123");
			pw.flush();//刷新缓存,向服务器端输出信息
			socket.shutdownOutput();//关闭socket输出流
			//3.获取输入流,来接受服务器端的相应
			InputStream is = socket.getInputStream();
			BufferedReader br = new BufferedReader( new InputStreamReader(is));
			String info = null;
			while( (info = br.readLine()) != null){
				System.out.println("我是客户端,服务器端说:" + info);
			}
			socket.shutdownInput();
			//4.关闭资源
			br.close();
			is.close();
			pw.close();
			os.close();
			socket.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

先启动服务端:


再启动客户端:


此时切换到服务端:


此时只有一个用户,看到红色圈那里,还没结束。

此时再次启动客户端,模拟多客户端的连接,在客户端那里修改下用户名和密码:

pw.write("用户名:hubbert,密码:123456");

再次启动客户端:


看到红色那里,服务端还是继续的循环侦听新客户端的连接。


5. UDP编程

UDP协议(用户数据报协议)是无连接,不可靠的,无序的

UDP协议以数据报作为数据传输的载体,也就是说:

进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明数据所要达到的Socket(主机地址和端口号),然后再将数据报发送出去。

接下使用代码:

服务器:


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UDPServer {
	public static void main(String[] args) throws IOException {
		/*
		 * 接受客户端发送的数据
		 */
		//1.创建服务器端DatagramSocket,指定端口
		DatagramSocket socket = new DatagramSocket(8800);
		//2.创建数据报,用于接收客户端发送的数据
		byte [] data = new byte[1024]; //创建字节数组,指定接收的数据包的大小
		DatagramPacket packet = new DatagramPacket(data, data.length);
		//3.接受客户端发送的数据
		System.out.println("服务器端已经启动,等待客户端发送数据...");
		socket.receive(packet); //此方法在接收数据报之前会一直阻塞
		//4.读取数据
		String info = new String(data,0,packet.getLength());
		System.out.println("我是服务器,客户端说:" + info);
		
		/*
		 * 向客户端响应数据
		 */
		//1.定义客户端的地址,端口号,数据
		InetAddress address = packet.getAddress();
		int port = packet.getPort();
		byte[] data2 = "欢迎您".getBytes();
		//2. 创建数据报,包含响应的数据信息
		DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address,port);
		//3.响应客户端
		socket.send(packet2);
		//4.关闭资源
		socket.close();
	}
}

客户端:


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class UDPClient {
	public static void main(String[] args) throws IOException {
		/*
		 * 向服务器端发送数据
		 */
		
		//1.定义服务器的地址,端口号,数据
		InetAddress address = InetAddress.getByName("localhost");
		int port = 8800;
		byte[]data = "用户名:admin;密码:123".getBytes();
		//2.创建数据报,包含发送的信息
		DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
		//3.创建DatagramSocket对象
		DatagramSocket socket = new DatagramSocket();
		//4.向服务器发送数据报
		socket.send(packet);
		
		/*
		 * 接受服务器端响应的数据
		 */
		//1.创建数据报,用户接收服务器端响应的数据
		byte[] data2 = new byte[1024];
		DatagramPacket packet2 = new DatagramPacket(data2,data2.length);
		//2.接受服务器端的信息
		socket.receive(packet2);
		//3.读取数据
		String reply = new String(data2,0,packet2.getLength());
		System.out.println("我是客户端,服务器说"+reply);
		//4.关闭资源
	}
}

结果如下:




总结:

1. Socket通信原理

2. 基于TCP的Socket通信


注意点:

1.多线程的优先级


2.是否关闭输出流和输入流

对于同一个socket,如果关闭了输出流,则与该输出流关联的socket也会被关闭,所以一般不用关闭输出流,关闭socket即可。



感谢以下文章:

使用InetAddress

慕课网

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值