socket概念
简单介绍下socket概念,通常不同主机的进程间进行通信会采用下面的方法:
但是不同用户进程就要与不同类型的协议进行通信,所以就诞生了socket,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,通过这个接口就可以统一、方便的使用tcp/ip协议的功能了。
参考: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();
}
}