java Socket网络编程

Socket的英文原义是“孔”或者“插座”,中文是“套接字”。应用程序通常通过套接字向网络发出请求或者应答网络请求。他最早出现在UNIX系统中,是UNIX上的一套网络程序通讯的标准,已被广泛移植到其他平台。

在Internet上的主机一般运行了多个服务软件,同时提供了几种服务,每种服务都打开一个Socket并绑定到一个端口上,不同的端口对应于不同的服务进程。

Socket实质上提供了进程通信的端点,网络上的两个程序通过一个双向的通讯链路实现数据的交换,这个双向链路的一端称为一个Socket。TCP/IP的5层模型中,Socket位于应用层和传输层。

Socket分为三种类型:1、流式套接字SOCK_STREAM。提供了一个面向链接,可靠的数据传输服务,数据无差错、无重复的发送,且按发送顺序接收。内设流量控制,避免数据流超限。其实他对应使用的是TCP协议。2、数据报式套接字SOCK_DGRAM。提供无连接服务。数据报以独立包形式被发送,不提供无差错保证,数据可能丢失或重复,并且接收顺序无序。其实他对应使用的是UDP协议。3、原始式套接字SOCK_RAW。该接口允许对较低层次协议,如IP直接访问。可以接收本机网卡上的数据帧或数据包,对监听网络流量和分析很有作用。

java将每一种套接字封装到了不同的类中,这些类位于java.net包中。

流式套接字类:Socket类和ServerSocket类。

Socket类就是负责处理客户端通信的一个java类。两种常用的构造方法与使用说明:1、Socket(String host,int port)这个构造方法是创建一个流式套接字,并且将其连接到指定主机的指定端口号,即向host主机的port端口发起连接请求。2、Socket(String host,int port,InetAddresslocalAddr,int LocalPort)这个构造方法也是创建一个流式套接字,并将其连接到指定远程主机上的指定远程端口,向host主机的port端口发起连接请求,发起请求的计算机为localAddr,端口为localPort。其中InetAddress类表示互联网协议地址,包含IP地址,也就是java对IP地址的封装。

处理服务器端通信的ServerSocket类。两种常用的构造方法:1、ServerSocket()创建一个ServerSocket对象。2、ServerSocket(int port)创建一个ServerSocket对象,并绑定到指定的端口。

Socket变成步骤:服务器端:1、建立一个服务器Socket绑定指定端口并开始监听。2、使用accept()方法阻塞等待监听,获得新的连接。3、建立输入和输出流。4、在已有的协议上产生会话。5、使用close()关闭流和Socket。

客户端:1、建立客户端Socket连接,指定服务器的位置以及端口。2、得到Socket的读写流。3、利用流按照一定的协议对Socket进行读写操作。4、使用close()关闭流和Socket。

例如,使用Socket实现客户端和服务器端数据传输

服务器端

public class Server {
	public static void main(String[] args) {
		try {
			ServerSocket ss = new ServerSocket(8800);
			Socket s = ss.accept();
			InputStream is = s.getInputStream();
			OutputStream os = s.getOutputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			PrintWriter pw = new PrintWriter(os);
			String info = null;
			while((info=br.readLine())!=null)
				System.out.println("服务器端接受信息:"+info);
			String reply = "welcome!";
			pw.write(reply);
			pw.flush();
			//关闭资源
			pw.close();
			br.close();
			os.close();
			is.close();
			s.close();
			ss.close();		
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

客户端

public class Client {
	public static void main(String[] args) {
		try {
			Socket s = new Socket("localhost", 8800);
			InputStream is = s.getInputStream();
			OutputStream os = s.getOutputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			PrintWriter pw = new PrintWriter(os);
			String info = "这是客户端发送信息!";
			pw.write(info);
			pw.flush();
			s.shutdownOutput();
			String reply = null;
			while((reply=br.readLine())!=null)
				System.out.println("客户端响应信息为:"+reply);
			pw.close();
			br.close();
			os.close();
			is.close();
			s.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

运行结果:

在服务器控制台界面:

服务器端接受信息:这是客户端发送信息!

在客户端控制台界面:

客户端响应信息为:welcome!

使用对象序列化流对Socket发送和接收的数据进行封装,实现了网络中传输对象。因此,通过Socket传递java对象,采用的方法就是对象序列化。

在客户端通过ObjectOutputStream来写入java对象,而服务器端通过ObjectInputStream对以前使用ObjectOutputStream写入的对象,进行反序列化来读取或者重构对象。

注意:对象需要实现Serializable接口。

例如:

实体类:

public class User implements Serializable{
	
	private String loginName;
	private int password;
	
	public User(String name,int pwd){
		loginName = name;
		password = pwd;
	}	
	public String getLoginName() {
		return loginName;
	}
	public void setLoginName(String loginName) {
		this.loginName = loginName;
	}
	public int getPassword() {
		return password;
	}
	public void setPassword(int password) {
		this.password = password;
	}
}
服务器端

public class ObjServer {
	public static void main(String[] args) {
		try {
			ServerSocket ss = new ServerSocket(8900);
			Socket s = ss.accept();
			InputStream is = s.getInputStream();
			ObjectInputStream ois = new ObjectInputStream(is);
			OutputStream os = s.getOutputStream();
			PrintWriter pw = new PrintWriter(os);
			User user = (User)ois.readObject();
			System.out.println("用户名:"+user.getLoginName()+" 用户密码:"+user.getPassword());
			System.out.println("登录成功!");
			pw.write("恭喜成功登录!");
			pw.flush();
			pw.close();
			os.close();
			ois.close();
			is.close();
			s.close();
			ss.close();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}	
	}
}

客户端

public class ObjClient {
	public static void main(String[] args) {
		try {
			Socket s = new Socket("localhost", 8900);
			OutputStream os = s.getOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(os);
			InputStream is = s.getInputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			User user = new User("小明",1234);
			oos.writeObject(user);
			String reply = null;
			while((reply = br.readLine())!=null)
				System.out.println("客户端响应:"+reply);
			br.close();
			is.close();
			oos.close();
			os.close();
			s.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}
}

为了实现在服务器方给多个客户提供服务的功能,可以使用多线程实现多客户的机制。服务器总是在指定的端口监听是否有客户请求,一旦监听到客户的请求,服务器就会启动一个专门的线程来响应客户的请求,而服务器在启动完线程后马上进入到监听状态,等待下一个客户的到来。

使用多线程时,实体类和客户端不用做任何改变,只需要将服务器端的任务以线程的方式单独处理

首先看服务器线程处理类:

public class ServerThread extends Thread{
	Socket s = null;
	
	public ServerThread(Socket s){
		this.s = s;
	}

	@Override
	public void run() {
		try {
			InputStream is = s.getInputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			OutputStream os = s.getOutputStream();
			PrintWriter pw = new PrintWriter(os);
			String info = null;
			while((info = br.readLine())!=null)
				System.out.println("服务器接收到信息:"+info);
			String reply = "这是服务器端发送信息!";
			pw.write(reply);
			pw.close();
			os.close();
			br.close();
			is.close();
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

然后看服务器类

public class Server {
	public static void main(String[] args) {
		try {
			ServerSocket ss = new ServerSocket(8800);
			Socket s = null;
			int num = 0;//记录当前客户端数量
			while(true){
				s = ss.accept();
				ServerThread st = new ServerThread(s);
				st.start();
				num++;
				System.out.println("当前有"+num+"个客户登录!");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}
}

当启动服务器后运行两次客户端程序,则在服务器端控制台输出:

当前有1个客户登录!
服务器接收到信息:这是客户端发送信息!
当前有2个客户登录!
服务器接收到信息:这是客户端发送信息!

当数据需要可靠传递时,我们可以采用流式套接字,比如:远程登录、文件传输等。

UDP协议:数据不可靠传输、代价小、效率高。

基于UDP协议的Socket(数据报十套接字):1、基于UDP协议。2、无连接。3、投出数据包快速高效。4、数据安全性不佳。比如:网络游戏、视频会议、QQ聊天。

java.net包提供了两个类来支持基于UDP协议的Socket编程:DatagramPacket类(数据包。封装了数据、数据长度、目标地址和目标端口)以及DatagramSocket类(发送和接收数据包的套接字,不维护连接状态,不产生输入输出流)。

注意:客户端要向外发送数据、必须首先创建一个DatagramPacket对象,在使用DatagramSocket对象发送。

基于UDP协议的Socket连接示例:

public class Server {
	public static void main(String[] args) {
		try {
			//1.创建接受方(服务器)套接字,并绑定端口号
			DatagramSocket ds=new DatagramSocket(8800);
			//2.确定数据包接受的数据的数组大小
			byte[] buf=new byte[1024];
			//3.创建接受类型的数据包,数据将存储在数组中
			DatagramPacket dp=new DatagramPacket(buf, buf.length);
			//4.通过套接字接受数据
			ds.receive(dp);
			//5.解析发送方发送的数据
			String mess=new String(buf,0,dp.getLength());
			System.out.println("客户端说:"+mess);
			//响应客户端
			String reply="您好,我在的,请咨询!";
			byte[] replys=reply.getBytes();
			//响应地址
			SocketAddress sa=dp.getSocketAddress();
			//数据包
			DatagramPacket dp2=new DatagramPacket(replys, replys.length,sa);
			//发送
			ds.send(dp2);
			//6.释放资源
			ds.close();
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

public class Client {
	public static void main(String[] args) {
		//1.确定发送给服务器的信息、服务器地址以及端口
		String mess="你好,我想咨询一个问题!";
		byte[] buf=mess.getBytes();
		InetAddress ia=null;
		try {
			ia=InetAddress.getByName("localhost");
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		int port=8800;
		//2.创建数据包,发送指定长度的信息到指定服务器的指定端口
		DatagramPacket dp=new DatagramPacket(buf, buf.length,ia,port);
		//3.创建DatagramSocket对象
		DatagramSocket ds=null;
		try {
			ds=new DatagramSocket();
		} catch (SocketException e) {
			e.printStackTrace();
		}
		//4.向服务器发送数据包
		try {
			ds.send(dp);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//接受服务器的响应并打印
		byte[] buf2=new byte[1024];
		//创建接受类型的数据包,数据将存储在数组中
		DatagramPacket dp2=new DatagramPacket(buf2, buf2.length);
		//通过套接字接受数据
		try {
			ds.receive(dp2);
		} catch (IOException e) {
			e.printStackTrace();
		}
		//解析服务器的响应
		String reply=new String(buf2,0,dp2.getLength());
		System.out.println("服务器的响应为:"+reply);
		
		//5.释放资源
		ds.close();
	}
}


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值