Java socket深层次通信_Socket详解

前面两篇分析了TCP和UDP协议,本篇来分析一下Socket,有了前面的基础,对理解Socket有很大的帮助,同时也是对TCP和UDP的更深层次的了解,经过多天的资料研究和代码分析,对socket也算是有了一个清楚的认识,鉴于网上的知识比较散,所以想把关于socket的知识整理一下,希望能够帮助想理解socket的童鞋,本着这些目的,本篇就来仔细分析一下Socket的原理。

文章内容有点长,先罗列一下需要分析的要点:

1. Socket是什么?

2. Java中Socket的使用

3. Java中Socket的源码分析

4. Linux系统中Socket对TCP的详细处理过程

1. Socket是什么?

我们知道进程通信的方法有管道、命名管道、信号、消息队列、共享内存、信号量,这些方法都要求通信的两个进程位于同一个主机。但是如果通信双方不在同一个主机又该如何进行通信呢?

在计算机网络中有一个tcp/ip协议族,使用tcp/ip协议族就能达到我们想要的效果,如下图所示:

83766ae7c3e6

socket所处层次

图中可以看到socket是位于应用层和传输层之间的一个抽象层,那么它存在的意义又是什么呢?其实没有socket抽象层,也是可以的,但是,当我们使用不同的协议进行通信时就得使用不同的接口,还得处理不同协议的各种细节,这就增加了开发的难度,软件也不易于扩展。于是UNIX BSD就发明了socket这种东西,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己去进行磁盘读写,内存管理。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。

2.Java中Socket的使用

在Java中,我们使用Socket有两种,一个是基于TCP的Socket,一个是基于UDP的DatagramSocket,本篇只分析基于TCP的Socket。现在我们来看Socket的使用:

客户端:指明主机端口创建Socket,获取输出流,写入数据。

Socket socket = null;

OutputStream os = null;

try {

socket = new Socket("192.168.1.106",9200);

os = socket.getOutputStream();

os.write("hello server !".getBytes());

} catch (IOException e) {

e.printStackTrace();

}finally {

try {

if(os != null){

os.close();

}

if(socket != null){

socket.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

服务端:指明绑定的端口,创建ServerSocket,开启端口监听。

ServerSocket serverSocket;

try {

serverSocket = new ServerSocket(9200);

} catch (IOException e) {

return;

}

while (isRunning){

try {

Socket client = serverSocket.accept();

handleClient(client);

} catch (IOException e) {

e.printStackTrace();

}

}

try {

serverSocket.close();

} catch (IOException e) {

e.printStackTrace();

}

这里只是一个简单的示例,当然实际开发中可能需要更多的东西,比如在handleClient(client)处理数据的时候加入线程池去处理,以及client的关闭等,这里只是简单的使用,主要目的还是看其源码。

3.Java中Socket的源码分析

3.1 服务端ServerSocket的源码解析

为了便于理解,我们先分析服务端ServerSocket的源码。通常我们在创建的时候需要指明绑定的端口,来看其构造函数:

public ServerSocket(int port) throws IOException {

this(port, 50, null);

}

public ServerSocket(int port, int backlog) throws IOException {

this(port, backlog, null);

}

public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {

setImpl();

if (port < 0 || port > 0xFFFF)

throw new IllegalArgumentException(

"Port value out of range: " + port);

if (backlog < 1)

backlog = 50;

try {

bind(new InetSocketAddress(bindAddr, port), backlog);

} catch(SecurityException e) {

close();

throw e;

} catch(IOException e) {

close();

throw e;

}

}

ServerSocket有三个指明端口的构造函数,其中参数backlog表示已建立连接的最大数量,也就是accept中未被我们取走的最大连接数,这个现在不理解也没关系,后面会重点介绍这个参数,这里当未指明时,它的值默认是50;InetAddress是对IP、DNS、端口等信息的封装类。

我们看到,在构造函数中,先调用了setImpl()方法,看其源码:

private void setImpl() {

if (factory != null) {

impl = factory.createSocketImpl();

checkOldImpl();

} else {

// No need to do a checkOldImpl() here, we know it's an up to date

// SocketImpl!

impl = new SocksSocketImpl();

}

if (impl != null)

impl.setServerSocket(this);

}

factory是SocketImplFactory类型,是创建SocketImpl的工厂类;impl是SocketImpl类型。

/**

* The factory for all server sockets.

*/

private static SocketImplFactory factory = null;

/**

* The implementation of this Socket.

*/

private SocketImpl impl;

当我们调用构造函数的时候factory肯定是空的,所以此时会给ServerSocket的impl变量赋值为SocksSocketImpl的对象,然后调用 impl.setServerSocket(this)将ServerSocket自己和SocksSocketImpl关联起来。

回到构造函数,接下来会判断端口的正确性以及backlog的处理,最后调用了bind(new InetSocketAddress(bindAddr, port), backlog)方法来绑定该端口,看其源码:

public void bind(SocketAddress endpoint, int backlog) throws IOException {

if (isClosed())

throw new SocketException("Socket is closed");

if (!oldImpl && isBound())

throw new SocketException("Already bound");

if (endpoint == null)

endpoint = new InetSocketAddress(0);

if (!(endpoint instanceof InetSocketAddress))

throw new IllegalArgumentException("Unsupported address type");

InetSocketAddress epoint = (InetSocketAddress) endpoint;

if (epoint.isUnresolved())

throw new SocketException("Unresolved address");

if (backlog < 1)

backlog = 50;

try {

SecurityManager security = System.getSecurityManager();

if (security != null)

security.checkListen(epoint.getPort());

getImpl().bind(epoint.getAddress(), epoint.getPort());

getImpl().listen(backlog);

bound = true;

} catch(SecurityException e) {

bound = false;

throw e;

} catch(IOException e) {

bound = false;

throw e;

}

}

首先进行一大堆的判断,之后获取SecurityManager,这是一个安全策略管理器,在执行一些不安全、敏感等操作之前通常需要用到这个东西来先检查一下,比如这里在监听之前,调用了checkListen()方法,来检查当前线程是否允许在指定端口的连接请求过程中的使用wait等待,如果不允许,则会抛出异常。接着继续往下看,getImpl()源码:

SocketImpl getImpl() throws SocketException {

if (!created)

createImpl();

return impl;

}

void createImpl() throws SocketException {

if (impl == null)

setImpl();

try {

impl.create(true);

created = true;

} catch (IOException e) {

throw new SocketException(e.getMessage());

}

}

/**

* Creates a socket with a boolean that specifies whether this

* is a stream socket (true) or an unconnected UDP socket (false).

*/

protected synchronized void create(boolean stream) throws IOException {

this.stream = stream;

if (!stream) {

ResourceManager.beforeUdpCreate();

// only create the fd after we know we will be able to create the socket

fd = new FileDescriptor();

try {

socketCreate(false);

} catch (IOException ioe) {

ResourceManager.afterUdpClose();

fd = null;

throw ioe;

}

} else {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 中进行 Socket 通信时,我们可以使用多线程来实现同时与多个客户端进行通信。这种方式可以提高服务器的并发处理能力。 具体实现方法是,服务器端使用一个主线程监听客户端的连接请求,当有新的连接请求时,主线程创建一个新的线程来处理该客户端的请求。这样,每个客户端的请求都会在一个单独的线程中进行处理,互不影响。 以下是一个简单的 Java Socket 多线程服务器端代码示例: ```java import java.net.*; import java.io.*; public class MultiThreadServer implements Runnable { private Socket clientSocket; public MultiThreadServer(Socket clientSocket) { this.clientSocket = clientSocket; } public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("Server started"); while (true) { Socket clientSocket = serverSocket.accept(); System.out.println("Accepted connection from " + clientSocket); new Thread(new MultiThreadServer(clientSocket)).start(); } } public void run() { try { PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { System.out.println("Received message: " + inputLine); out.println("Message received: " + inputLine); } in.close(); out.close(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } ``` 在上面的代码中,我们创建了一个 MultiThreadServer 类来处理客户端请求。在 main 方法中,我们首先创建了一个 ServerSocket 并在端口 8080 上监听客户端连接请求。当有新的连接请求时,我们创建一个新的 MultiThreadServer 线程来处理该客户端的请求。 在 MultiThreadServer 类中,我们实现了 Runnable 接口,并重写了 run 方法。在 run 方法中,我们使用 BufferedReader 和 PrintWriter 来读取和发送消息。注意,在处理完一个客户端请求后,我们需要关闭连接。 以上就是一个简单的 Java Socket 多线程服务器端代码示例。通过这种方式,我们可以轻松实现高并发的 Socket 通信

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值