Java 소켓 탐색
Java의 소켓은 일반 소켓과 NioSocket의 두 가지 유형으로 나눌 수 있습니다.
일반 소켓 사용
Java의 네트워크 통신은 소켓을 통해 구현되며, 소켓은 서버측에서 사용되는 소켓과 서버측에서 사용되는 소켓으로 구분됩니다. 요청을 수신한 후 소켓은 데이터 전송을 완료하는 데 사용됩니다. 클라이언트는 소켓을 직접 사용하여 요청을 시작하고 데이터를 전송합니다.
ServerSocket 및 소켓 사용에 대한 간단한 대화형 소개:
1. ServerSocket을 만듭니다.
ServerSocket의 구성 방법은 5가지가 있습니다. 가장 편리한 방법은 포트 매개변수를 전달하는 것입니다.
2. 생성된 ServerSocket의 accept 메소드를 호출하여 모니터링
accept 메소드는 차단 메소드이므로 accept 메소드 호출 후 프로그램이 중지되고 대기합니다. 연결 요청의 경우 요청을 받은 후 수락 메서드가 소켓을 반환할 때까지 프로그램이 계속 실행되지 않습니다.
3. accept 메서드에서 반환된 소켓을 사용하여 클라이언트와 통신합니다.
서버 코드 예:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; public class Server { public static void main(String[] args) { try { //创建一个ServeSocket,设置端口为8080 ServerSocket serverSocket = new ServerSocket(8080); //运行Socket监听,等待请求 此方法会阻塞线程,当有请求时才会继续执行 Socket socket = serverSocket.accept(); //接收到请求之后使用Socket进行通信,创建BufferedReader用于读取请求的数据 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream()); String line = in.readLine(); System.out.println(line); //创建PrintlnWriter,用于发送数据 out.println("已经接受到了数据"); out.flush(); System.out.println("Server关闭" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date())); //关闭资源 out.close(); in.close(); socket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } finally { } } }
클라이언트 코드 예:
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; /** * 客户端 * @author sanchan */ public class Client { public static void main(String[] args) { //需要先启动Server否则报错java.net.ConnectException: Connection refused try { String msg="你好,ServerSocket!"; //创建一个Socket,与本机8080端口连接 Socket socket=new Socket("127.0.0.1",8080); //使用Socket创建PrintWriter和BufferedReader进行数据的读写 PrintWriter out=new PrintWriter(socket.getOutputStream()); out.println(msg); out.flush(); BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream())); String line=in.readLine(); System.out.println(line); //关闭资源 in.close(); out.close(); socket.close(); System.out.println("client关闭"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date())); } catch (Exception e) { e.printStackTrace(); } finally { } } }
NioSocket 사용
nio(새 IO)는 JDK1 .4입니다. 새로 추가된 IO 모드인 nio는 하위 계층에서 새로운 처리 방법을 채택하여 Java IO의 효율성을 크게 향상시킵니다. 소켓은 IO의 한 유형이기도 하며 nio는 원래 ServerSocket 및 Socket에 각각 해당하는 ServerSocketChannel 및 SocketChannel이라는 해당 클래스를 제공합니다.
nio의 세 가지 기본 사항 이해:
1. 버퍼
2.
3. Selector
小故事里有大智慧:
试想一下如果电商是只要有订单就派人直接取货去送货【这种模式就相当于之前的Socket方式】,而不是现在的快递模式:将货物统一到中转站,分拣员按照配送范围分配快递员,然后快递员统一送货【这种模式就相当于NioSocket模式】。那么你得多长时间收到你得快递/(ㄒoㄒ)/~~
Buffer就是货物,Channel就是快递员,Selector就是中转站的分拣员。
NioSocket使用步骤:
1. 创建ServerSocketChannel并设置相应参数
SerSocketChannel可以使用自身的静态工厂方法open创建。 每个ServerSocketChannel对应一个ServerSocket,可以调用其socket方法来获取【不过如果使用该ServerSocket监听请求就又回到原来的 普通Socket 模式了,一般只用于使用bind方法绑定端口】。 ServerSocketChannel可以通过`SelectableChannel configureBlocking(boolean block)` 方法来设置是否采用阻塞模式。设置非阻塞模式之后可以调用register方法注册Selector【阻塞模式不可以使用Selector】
2. 创建Selector并注册Selector到ServerSocketChannel上
Selector可以使用自身的静态工厂方法open创建。
创建后通过上面所说的Channel的register方法注册到ServerSocketChannel或者SocketChannel上。
3.调用Selector的select方法等待请求
通过select方法等待请求,select方法可传入代表最长等待时间的long型参数。在设定时间内接收到相应操作的请求则返回可以处理请求的数量,否则在超时后返回0,程序继续执行。如果传入0或者使用无参的重载方法,则会采用阻塞模式直到有相应操作的请求出现。
4. 使用Selector接收请求并处理
接收到请求后Selector调用selectedKeys返回SelectionKey的Set集合。
5. 使用SelectionKey获取到Channel、Selector和操作类型并进行具体操作。
SelectionKey保存了处理当前请求的Channel和Selector,并提供了不同的操作类型。前面提到的Channel注册Selector的register方法参数中第二个参数就是SelectionKey定义的。共有四种:
SelectionKey.OP_ACCEPT //请求操作SelectionKey.OP_CONNECT //链接操作SelectionKey.OP_READ //读操作SelectionKey.OP_WRITE //写操作
只有在register方法中注册了对应的操作Selector才会关心相应类型操作的请求。
Selector和Channel是多对多关系。
Selector是按不同的操作类型进行分拣,将分拣结果保存在SelectionKey中,可分别通过SelectionKey的channel、selector方法来获取对应的Channel和Selector。可以使用SelectionKey的isAcceptable、isConnectable、isReadable和isWritable方法来判断是什么类型的操作。
Buffer是专门用于存储数据,有四个极为重要的属性:
capacity:容量。
Buffer最多可以保存元素的数量,创建时设置,使用过程中不可修改。
limit:可以使用的上限。
刚创建Buffer时limit等于capacity。如果给limit设置【不能超过capacity】之后,limit就成了最大可访问的值。
例如,一个Buffer的capacity为100,表示最多可以保存100个数据,只写入20个之后就要读取,在读取时limit就会设置为20。
position:当前所操作元素所在索引位置。
position从0开始,随着get和put方法自动更新。
mark:用来暂时保存position的值。
position保存到mark之后就可以修改并进行相关的操作,操作完成后可以通过reset方法将mark的值恢复到position。
mark默认值为-1,且其值必须小于position的值。
例如,Buffer中一共保存了20个数据,position为10,现在想读取15到20之间的数据,这时就可以调用Buffer的mark方法将目前的position保存到mark中,然后调用Buffer的position(15)将position指向第15个元素,这时就可以读取。读取完成之后使用Buffer的reset就可以将position恢复到10.
如果调用Buffer的position方法时传入的值小于mark当前的值,则会将mark设为-1。
这四个属性大小关系:mark<=position<=limit<=capacity
我们将前面的普通Socket示例的服务端改写一下:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.util.Iterator; /** * @author sanchan * @since 1.0 */ public class NIOServer { public static void main(String[] args) { /** * 启动监听 * 当监听到请求时根据SelectionKey的操作类型交给内部类Handler进行处理 */ try { //创建ServerSocketChannel ServerSocketChannel ssc = ServerSocketChannel.open(); //设置监听8080端口 ssc.socket().bind(new InetSocketAddress(8080)); //设置为非阻塞模式 ssc.configureBlocking(false); //为ServerSocketChannel注册Selector Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); //创建Handler Handler handler = new Handler(1024); while (true) { //等待请求,每次等待阻塞3s,超过3秒后线程继续运行,如果传入0或使用无参重载方法,将一直阻塞 if (selector.select(3000) == 0) { System.out.println("等待请求超时~~~~~"); continue; } System.out.println("处理请求~~~~~"); //获取等待处理的SelectionKey Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); try { //根据不同请求操作选择对应的处理方法 if (key.isAcceptable()) { handler.handleAccept(key); } if (key.isReadable()) { handler.handleRead(key); } } catch (IOException e) { e.printStackTrace(); //如果异常就说明连接结束,移除 keyIterator.remove(); continue; } } } } catch (IOException e) { e.printStackTrace(); } finally { } } /** * 请求处理类 */ private static class Handler { private int bufferSize = 1024; private String localCharset = "UTF-8"; public Handler() { } public Handler(int bufferSize) { this(bufferSize, null); } public Handler(String localCharset) { this(-1, localCharset); } public Handler(int bufferSize, String localCharset) { if (bufferSize > 0) this.bufferSize = bufferSize; if (localCharset != null) this.localCharset = localCharset; } /** * 处理请求操作 * * @param key * @throws IOException */ public void handleAccept(SelectionKey key) throws IOException { //获取Channel SocketChannel sc = ((ServerSocketChannel) key.channel()).accept(); //设置非阻塞 sc.configureBlocking(false); //注册读操作的Selector sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); } /** * 处理读操作 * * @param key * @throws IOException */ public void handleRead(SelectionKey key) throws IOException { //获取Channel SocketChannel sc = ((SocketChannel) key.channel()); //获取ByteBuffer /** * Buffer专门用于存储数据,有四个极为重要的属性: * 1. capacity:容量。 * Buffer最多可以保存元素的数量,创建时设置,使用过程中不可修改。 * 2. limit:可以使用的上限。 * 刚创建Buffer时limit等于capacity。如果给limit设置【不能超过capacity】之后,limit就成了最大可访问的值。 * 例如,一个Buffer的capacity为100,表示最多可以保存100个数据,只写入20个之后就要读取,在读取时limit就会设置为20。 * 3. position:当前所操作元素所在索引位置。 * position从0开始,随着get和put方法自动更新。 * 4. mark:用来暂时保存position的值。 * position保存到mark之后就可以修改并进行相关的操作,操作完成后可以通过reset方法将mark的值恢复到position。 * mark默认值为-1,且其值必须小于position的值。 * 例如,Buffer中一共保存了20个数据,position为10,现在想读取15到20之间的数据,这时就可以调用Buffer的mark方法将目前的position保存到mark中,然后调用Buffer的position(15)将position指向第15个元素,这时就可以读取。读取完成之后使用Buffer的reset就可以将position恢复到10. * 如果调用Buffer的position方法时传入的值小于mark当前的值,则会将mark设为-1。 */ ByteBuffer buffer = (ByteBuffer) key.attachment(); //重置ByteBuffer。设置limit=capacity、position=0、mark=-1 buffer.clear(); //没有获取到内容则关闭 if (sc.read(buffer) == -1) { sc.close(); } else { /** * flip()作用: * 在保存数据时保存一个数据position加1,保存完成后要读取数据 * 就得设置limit=position,position=0 **/ buffer.flip(); //返回数据到客户端 String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString(); System.out.println("从客户端获取到了数据:" + receivedString); String sendString = "服务端已经获取到了数据:" + receivedString; buffer = ByteBuffer.wrap(sendString.getBytes(localCharset)); sc.write(buffer); //关闭SocketChannel sc.close(); } } } }

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











데이터베이스 작업에 MyBatis-Plus 또는 기타 ORM 프레임 워크를 사용하는 경우 엔티티 클래스의 속성 이름을 기반으로 쿼리 조건을 구성해야합니다. 매번 수동으로 ...

시스템 도킹의 필드 매핑 처리 시스템 도킹을 수행 할 때 어려운 문제가 발생합니다. 시스템의 인터페이스 필드를 효과적으로 매핑하는 방법 ...

IntellijideAultimate 버전을 사용하여 봄을 시작하십시오 ...

일부 애플리케이션이 제대로 작동하지 않는 회사의 보안 소프트웨어에 대한 문제 해결 및 솔루션. 많은 회사들이 내부 네트워크 보안을 보장하기 위해 보안 소프트웨어를 배포 할 것입니다. ...

Java 객체 및 배열의 변환 : 캐스트 유형 변환의 위험과 올바른 방법에 대한 심층적 인 논의 많은 Java 초보자가 객체를 배열로 변환 할 것입니다 ...

다른 아키텍처 CPU에 대한 Java 프로그램의 메모리 누출 현상 분석. 이 기사는 Java 프로그램이 ARM과 X86 Architecture CPU에 다른 메모리 동작을 보여주는 사례에 대해 논의합니다.

그룹 내에서 정렬을 구현하기 위해 이름을 숫자로 변환하는 방법은 무엇입니까? 그룹으로 사용자를 정렬 할 때는 종종 사용자 이름을 숫자로 변환하여 다르게 만들 수 있습니다 ...

Redis 캐싱 솔루션은 제품 순위 목록의 요구 사항을 어떻게 인식합니까? 개발 과정에서 우리는 종종 a ... 표시와 같은 순위의 요구 사항을 처리해야합니다.
