java Socket通信实践

本文深入浅出地介绍了Socket通信的基本概念,展示了如何利用Socket实现TCP/IP协议的互联通信,包括客户端与服务端的连接建立、数据收发及地址获取。同时,通过实践案例对比了阻塞I/O与NIO、Netty框架的效率差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

socket概念

简单介绍下socket概念,通常不同主机的进程间进行通信会采用下面的方法:title

但是不同用户进程就要与不同类型的协议进行通信,所以就诞生了socket,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,通过这个接口就可以统一、方便的使用tcp/ip协议的功能了。

title

参考:https://www.cnblogs.com/zengzy/p/5107516.html

socket实践

下面分享一个socket的实践,IOServer负责socket服务供客户端连接,输出客户端的地址和socket请求内容,并返回消息给客户端。server端首先创建一个serverSocket来监听8000端口,然后创建一个线程A,线程A里面不断调用阻塞的方法获得链接,所以每当有客户端建立连接,就会创建一个线程B进行处理。在线程B内部,不断获取客户端的输出,并返回消息给客户端。这种效率很低下,后面文章末尾参考文档NIO以及netty框架的改进,本文也是基于参考文档的实践。

IOClient端负责请求服务端,通过线程池模拟多个客户端的情况。

可以简单了解到:

1. 客户端、服务端如何建立连接

2. 客户端、服务端如何相互通信,接收消息。

3. 服务端如何获取客户端的请求地址。

4. 使用线程池模拟多个用户请求。

IOServer.java

package networktest;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * server端首先创建一个serverSocket来监听8000端口,然后创建一个线程,线程里面不断调用阻塞的方法获得链接(2)。
 * 当获取到链接以后,为每条链接创建一个新的线程,这个线程负责从该链接中读取数据(3,4)
 * 缺点:每个连接都需要一个线程来维护,每个线程包含一个while死循环。造成
 * (1)线程资源受限;
 * (2)线程数目爆炸,系统进行线程切换效率低下
 * (3)读取按照字节流读取,效率低下。
 * @author huiminlu
 *
 */
public class IOServer {
public static void main(String[] args) throws Exception {
	//创建绑定到特定端口的服务器套接字
	ServerSocket serverSocket = new ServerSocket(8000);
	ExecutorService pool = Executors.newCachedThreadPool();
	//(1)接收新链接线程
	new Thread(() -> {
		try {
			while(true) {
				try {
					//(2)阻塞的方法获取新的链接
					Socket socket = serverSocket.accept();
					System.out.println("intaddress:"+socket.getInetAddress());
					System.out.println("localaddress:"+socket.getLocalAddress());
					System.out.println("localsocketaddress:"+socket.getLocalSocketAddress());
					System.out.println("remotesocketaddress:"+socket.getRemoteSocketAddress());
					//(3)每个链接都创建一个线程,负责读取数据
					pool.submit(new ServerRunnable(socket));			
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
			}
		} finally {
			// TODO: handle finally clause
			try {
				serverSocket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}		
	}).start();	
}
}

class ServerRunnable implements Runnable{
	Socket socket;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			byte[] data = new byte[1024];
			//获取socket的输入流(读数据)
			InputStream inputStream = socket.getInputStream();
			//获取socket的输出流(写数据)
			PrintWriter socketOut = new PrintWriter(socket.getOutputStream());
			//currentThread是Thread的静态方法,可以直接获取
			System.out.println(Thread.currentThread().getName());
			while(true) {
				int len;
				//(4)按字节流的方式读取数据
				while((len = inputStream.read(data)) != -1) {
					System.out.println(new String(data, 0, len));
					//给客户端返回信息
					socketOut.write("server ack\n");
					socketOut.flush();
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	public ServerRunnable(Socket socket) {
		// TODO Auto-generated constructor stub
		this.socket = socket;
		
	}
}

 

IOClient.java

package networktest;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class ClientRunnable implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			Socket socket = new Socket("127.0.0.1", 8000);
			//InetAddress:要链接的地址,对方的地址
			System.out.println("client intaddress:"+socket.getInetAddress());
			//LocalAddress:socket被绑定的本地地址
			System.out.println("client localaddress:"+socket.getLocalAddress());
			//LocalSocketAddress:bouned 本地socket地址
			System.out.println("client localsocketaddress:"+socket.getLocalSocketAddress());
			//getRemoteSocketAddress: is connected 被连接的地址
			System.out.println("client remotesocketaddress:"+socket.getRemoteSocketAddress());
			OutputStream out = socket.getOutputStream();
			InputStream ins = socket.getInputStream();
			byte[] data = new byte[1024];
				try {
					while(true) {
					out.write((new Date() + ":hello world" ).getBytes());
					socket.getOutputStream().flush();
					ins.read(data);
					System.out.println(new String(data));
					Thread.sleep(2000);
				}} catch (Exception e) {
					// TODO: handle exception
				}
				finally {
					socket.close();
				}
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
}
/**
 * 客户端连接本地的8000端口,即IOServer监听的端口。然后每2秒写入
 * @author huiminlu
 *
 */
public class IOClient {
public static void main(String[] args) {
	ExecutorService pool = Executors.newCachedThreadPool();
	pool.submit(new ClientRunnable());
	pool.submit(new ClientRunnable());
	pool.submit(new ClientRunnable());
	pool.submit(new ClientRunnable());
	
	pool.shutdown();
	
}
}

启动服务器端后,再启动客户端,服务器端输出如下:

intaddress:/127.0.0.1
localaddress:/127.0.0.1
localsocketaddress:/127.0.0.1:8000
remotesocketaddress:/127.0.0.1:52591
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-4
Sat Jun 15 22:56:48 CST 2019:hello world
Sat Jun 15 22:56:48 CST 2019:hello world
Sat Jun 15 22:56:48 CST 2019:hello world

可以看到,socket.accept是一个阻塞的方法,有请求以后,才会继续执行;这里有4个线程,每个客户端的连接都会启动一个新的线程进行处理。

客户端输出如下:

client intaddress:/127.0.0.1
client intaddress:/127.0.0.1
client intaddress:/127.0.0.1
client intaddress:/127.0.0.1
client localaddress:/127.0.0.1
client localsocketaddress:/127.0.0.1:52589
client remotesocketaddress:/127.0.0.1:8000
client localaddress:/127.0.0.1
client localsocketaddress:/127.0.0.1:52591
client remotesocketaddress:/127.0.0.1:8000
client localaddress:/127.0.0.1
client localsocketaddress:/127.0.0.1:52590
client remotesocketaddress:/127.0.0.1:8000
client localaddress:/127.0.0.1
client localsocketaddress:/127.0.0.1:52592
client remotesocketaddress:/127.0.0.1:8000
server ack

参考:https://mp.weixin.qq.com/s/EMuiQC7ZhoOx118fUyJ7vg

TCP 与 UDP

前面使用的socket是tcp协议进行通信,需要先连接之后才能发送数据。
DatagramSocket使用的是UDP协议,客户端不需要先连接服务端,可以直接发送给指定服务器。

分享一个udp协议进行通信的例子:

客户端:

package networktest;

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

/**
 * 通过udp协议发送数据客户端。
 * 1. 通过datagramSocket创建UDP服务
 * 2. 创建要发送的数据,并封装为datagramSocket进行传输
 * 3. 发送数据包 
 * 4. 关闭
 * @author huiminlu
 *
 */
public class UdpClient {
	public static void main(String[] args) throws IOException {
		//1. 通过datagramSocket创建UDP服务
		DatagramSocket socket = new DatagramSocket();
		//2. 创建要发送的数据,并封装为datagramSocket进行传输
		String s = "使用udp进行传输";
		byte[] buff = s.getBytes();
		InetAddress ia = InetAddress.getLocalHost();
		DatagramPacket packet = new DatagramPacket(buff, buff.length, ia, 8899);
		//3. 发送数据包 
		socket.send(packet);
		//4. 关闭
		socket.close();
	}
}

服务器端:

package networktest;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
 * 使用udp服务端接收数据
 * 1. 创建udp socket服务对象 DatagramSocket
 * 2. 创建服务包接收对象,DatagramPacket
 * 3. 接收数据包到DatagramPacket对象,receive是一个阻塞方法,直到有数据了才会返回
 * 4. 根据数据包,获取客户端ip 数据 端口等信息
 * 5. 关闭
 * @author huiminlu
 *
 */
public class UdpServer {
	public static void main(String[] args) throws IOException {
		//1. 创建udp socket服务对象 DatagramSocket
		DatagramSocket socket = new DatagramSocket(8899);
		//2. 创建服务包接收对象,DatagramPacket
		byte[] buff = new byte[1024];
		DatagramPacket packet = new DatagramPacket(buff, buff.length);
		// 3. 接收数据包到DatagramPacket对象,receive是一个阻塞方法,直到有数据了才会返回
		socket.receive(packet);
		//4. 根据数据包,获取客户端ip 数据 端口等信息
		String ip = packet.getAddress().getHostAddress();
		int port = packet.getPort();
		String hostname = packet.getAddress().getHostName();
		String data = new String(packet.getData(), 0, packet.getLength());
		System.out.println(ip+":"+hostname+":"+port+":"+data);
		//5. 关闭
		socket.close();
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值