Socket简要阐述

Socket的概念

Socket的英文原义是“孔”或“插座”。

在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。

Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。

它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。

HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

Socket原理

Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。

套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

服务器监听:建立服务器端套接字,并处于等待连接的状态,不定位具体的客户端套接字,而是实时监控网络状态。

客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。

为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,

一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

下图为基于TCP协议Socket的通信模型。

hello/hi的简单网络聊天程序实现

服务器端

实现步骤

1.创建ServerSocket对象,绑定监听端口。

2.通过accept()方法监听客户端请求。

3.连接建立后,在接收进程中通过输入流读取客户端发送的请求信息。

4.在服务器发送进程中通过输出流向客户端发送响应信息。

5.关闭相应的资源和Socket。

package com.socket.MultiThread;

import java.io.*;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Scanner;

public class Server {

public static ServerSocket serverSocket;

public static Socket socket;

public static Scanner scanner;

/**

* 构造方法

* 新建serverSocket和Socket

*/

public Server() {

try {

serverSocket = new ServerSocket(6666);

System.out.println("Server is working, waiting for client's link");

socket = serverSocket.accept();

System.out.println("Client has linked with Server");

} catch (IOException i) {

i.printStackTrace();

}

}

/**

* 服务器端发送消息线程

* 作用:从键盘读入消息,发送给服务器端

*/

public class SendThread implements Runnable {

@Override

public void run() {

try {

OutputStream outputStream = socket.getOutputStream();

OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);

PrintWriter printWriter = new PrintWriter(outputStreamWriter, true);

scanner = new Scanner(System.in);

while (true) {

printWriter.println(scanner.nextLine());

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

/**

* 服务器端接收线程

* 作用:使用字符流读取缓冲区中客户端所发送的消息

*/

public class ReceiveThread implements Runnable {

@Override

public void run() {

try {

InputStream inputStream = socket.getInputStream();

InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");

BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

// 输出从客户端端接收到的消息

while (true) {

System.out.println("Client> " + bufferedReader.readLine());

}

} catch (IOException i) {

i.printStackTrace();

}

}

}

public void start() {

Thread send = new Thread(new SendThread()); // 发送进程负责服务器端的消息发送

Thread receive = new Thread(new ReceiveThread()); // 接收进程负责接收客户端的消息

send.start();

receive.start();

}

public static void main(String[] args) {

Server server = new Server();

server.start();

}

}

客户端

实现步骤

1.创建Socket对象,指明需要连接的服务器的地址和端口号。

2.连接建立后,通过输出流向服务器发送请求信息。

3.通过输入流获取服务器响应的信息。

4.关闭相应资源。

package com.socket.MultiThread;

import java.io.*;

import java.net.Socket;

import java.util.Scanner;

public class Client {

public static Socket socket;

public static Scanner scanner;

/**

* 构造方法

* 新建一个socket,并指定了host和port属性,其port与服务器端保持一致

*/

public Client() {

try {

socket = new Socket("127.0.0.1", 6666);

} catch (IOException i) {

i.printStackTrace();

}

}

/**

* 客户端发送消息线程

* 作用:从键盘读入消息,发送给服务器端

*/

public class SendThread implements Runnable {

@Override

public void run() {

try {

OutputStream outputStream = socket.getOutputStream();

OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);

PrintWriter printWriter = new PrintWriter(outputStreamWriter, true);

scanner = new Scanner(System.in);

while (true) {

printWriter.println(scanner.nextLine());

}

} catch (IOException i) {

i.printStackTrace();

}

}

}

/**

* 客户端接收线程

* 作用:使用字符流读取缓冲区中服务器端所发送的消息

*/

public class ReceiveThread implements Runnable {

@Override

public void run() {

try {

InputStream inputStream = socket.getInputStream();

InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"UTF-8");

BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

// 输出从服务器端接收到的消息

while (true) {

System.out.println("Server> " + bufferedReader.readLine());

}

} catch (IOException i) {

i.printStackTrace();

}

}

}

public void start() {

Thread send = new Thread(new SendThread()); // 发送进程负责客户端的消息发送

Thread receive = new Thread(new ReceiveThread()); // 接收进程负责接收服务器端的消息

send.start();

receive.start();

}

public static void main(String[] args) {

Client client = new Client();

client.start();

}

}

程序执行结果

先运行服务器端,后运行客户端,服务器端在监听客户端的连接请求后建立连接。

服务器端与客户端的交互

跟踪分析调用栈 & Linux API对比

创建ServerSocket

在前面的服务器端代码中,我们创建一个ServerSocket是这样做的:

serverSocket = new ServerSocket(6666);

这行代码在平常看来就是创建了一个端口号为6666的ServerSocket。

但是实际上,我们只是调用了大牛们早已经写好并封装在JDK中的方法,这才能够如此简单地完成套接字的创建。

因此下面通过查看JDK源码,追踪其调用栈来看看ServerSocket的创建究竟是如何实现的。

调用栈图示

通过上图中对于jdk代码中socket创建过程的展示,我们了解到:

在java中ServerSocket的创建主要是调用PlainSocketImpl.socketCreate这个native方法来实现的。

源码分析

那么,我们来康一下这个方法:

/**

* The file descriptor object for this socket.

*/

protected FileDescriptor fd; // 文件描述符

@Override

void socketCreate(boolean stream) throws IOException {

if (fd == null) // 空则抛出异常

throw new SocketException("Socket closed");

int newfd = socket0(stream); // 调用jvm的socket0方法来创建新的fd

fdAccess.set(fd, newfd);

}

可以看到PlainSocketImpl.socketCreate方法中有一个重要的变量是fd,在代码块中我也将这个变量的声明一并列出了。

记得在本科的Linux课上老师曾经也着重强调了文件描述符这个概念,那么此fd是彼fd吗?

在了解了Linux内核中Socket的建立之后,就能够得出答案:是的。

在jvm中,调用linux底层api: socket()函数时,执行的步骤为:

创建socket结构体

创建tcp_sock结构体,刚创建完的tcp_sock的状态为:TCP_CLOSE

创建文件描述符与socket绑定

因此,在PlainSocketImpl.socketCreate方法中所实现的也正是这样的逻辑。

Socket绑定

上述分析中,我们会发现:

在PlainSocketImpl.socketCreate中创建socket时,它并没有绑定任何的ip地址与端口,只是实现了与文件描述符的绑定。

这就有点奇怪了,我们在上面的Java代码中创建ServerSocket的时候明明指定了端口号的呀,怎么调用到底层方法它就把端口号丢了呢?

再次分析源码,原来仅仅是new ServerSocket(6666);这一步操作就调用了三次Linux API,其对应关系如下图。

调用栈图示

同样的,我们可以得出:java中ServerSocket的绑定是调用PlainSocketImpl.socketBind这个native方法来实现的。

源码分析

查看以下JDK源码中PlainSocketImpl.socketBind方法的内容。

@Override

void socketBind(InetAddress address, int port) throws IOException {

int nativefd = checkAndReturnNativeFD();

if (address == null) // ip地址为空则抛出异常

throw new NullPointerException("inet address argument is null.");

if (preferIPv4Stack && !(address instanceof Inet4Address)) // 限定IP地址为IPv4版本

throw new SocketException("Protocol family not supported");

// 调用jvm的bind0方法实现绑定

bind0(nativefd, address, port, useExclusiveBind);

if (port == 0) { // 没有给出端口号

localport = localPort0(nativefd);

} else {

localport = port;

}

this.address = address;

}

可以看到,在上面的方法中通过调用bind0这个方法来实现实现的端口号以及IP地址的绑定。

并且,源码限制目前所支持的IP地址是IPv4版本的(虽然目前IPv4地址已经分配完毕),相信在后续的JDK更新中这里会修改过来。

Socket监听

从之前的Java调用Linux API图中可以看到,在完成Socket的创建和绑定之后,服务器端进入监听的状态,等待客户端发出连接的请求。

调用栈图示

从上图可以得出:java中ServerSocket的绑定是调用PlainSocketImpl.socketListen这个native方法来实现的。

源码分析

@Override

void socketListen(int backlog) throws IOException {

int nativefd = checkAndReturnNativeFD();

// 调用jvm的listen0方法实现监听

listen0(nativefd, backlog);

}

在JDK中监听的实现较为简单,主要是通过调用JVM中listen0来实现的,这里不做过多的展开。

Socket Accept

服务器端一直被动等待着客户端的连接,终于有一个客户端使用与之匹配的IP地址和端口号,

并在经历了TCP三次握手之后,客户端建立新的连接Socket对象,服务器就与这个客户端建立了TCP连接。

调用栈图示

从上图可以得出:java中ServerSocket的绑定是调用PlainSocketImpl.socketAccept这个native方法来实现的。

源码分析

@Override

void socketAccept(SocketImpl s) throws IOException {

int nativefd = checkAndReturnNativeFD();

if (s == null)

throw new NullPointerException("socket is null");

int newfd = -1;

InetSocketAddress[] isaa = new InetSocketAddress[1];

if (timeout <= 0) { // 设定有超时计时器

// 没有超时则调用accept0方法建立连接

newfd = accept0(nativefd, isaa);

} else {

// 否则将该客户端挂入阻塞队列中

configureBlocking(nativefd, false);

try {

waitForNewConnection(nativefd, timeout);

newfd = accept0(nativefd, isaa);

if (newfd != -1) {

configureBlocking(newfd, true);

}

} finally {

configureBlocking(nativefd, true);

}

}

// 更新socketImpl的文件描述符值

fdAccess.set(s.fd, newfd);

// 更新socketImpl中的端口号、ip地址以及localport值

InetSocketAddress isa = isaa[0];

s.port = isa.getPort();

s.address = isa.getAddress();

s.localport = localport;

if (preferIPv4Stack && !(s.address instanceof Inet4Address))

throw new SocketException("Protocol family not supported");

}

Java Socekt API与Linux Socket API

在上面的调用栈分析中,无论是ServerSocket的创建、绑定、监听,还是连接都伴随着对glibc的调用。

那么glibc到底何许人也?这里引用百度词条的内容:

glibc是GNU发布的libc库,即c运行库。glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现。由于 glibc 囊括了几乎所有的 UNIX 通行的标准,可以想见其内容包罗万象。而就像其他的 UNIX 系统一样,其内含的档案群分散于系统的树状目录结构中,像一个支架一般撑起整个操作系统。在 GNU/Linux 系统中,其C函式库发展史点出了GNU/Linux 演进的几个重要里程碑,用 glibc 作为系统的C函式库,是GNU/Linux演进的一个重要里程碑。

就绑定功能而言,在上述的调用栈追踪中我们知道了所调用的是底层由glibc提供的Bind方法,

但实际上,最终调用内核的SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)。

因此,可以得出结论:

java的socket实现是通过调用操作系统的socket api实现的

参考链接

简单网络聊天程序java_基于Java实现hello/hi简单网络聊天程序相关推荐

  1. 简单计算器的设计java_(基于java的简易计算器的设计.doc

    (基于java的简易计算器的设计 基于java的简易计算器的设计 摘要 自从java语言诞生以来,java语言就以不可抵挡的趋势很快成为国际上广泛流行的面向对象编程语言,它既具有高级语言的特点,又少了 ...

  2. 用java编写一个聊天程序_基于JAVA实现的一个简单的网络聊天程序

    一.Java Socket的概述 1.Socket套接字方便了开发网络应用程序.TCP面向连接的可靠传输协议.具有数据确认和数据重传机制.保证了发送数据一定能到达通信的对方.UPD协议无连接,不可靠的 ...

  3. 小程序毕业设计 基于java后台微信餐厅座位预定小程序毕业设计参考

    小程序 后台

  4. java源码聊天软件_【原创】基于Java NIO的多人在线聊天工具源码实现(登录,单聊,群聊)...

    近来在学习Java NIO网络开发知识,写了一个基于Java NIO的多人在线聊天工具MyChat练练手.源码公开在Coding上: 编写一个基于Java NIO的多人在线聊天工具,需要以下几方面的知 ...

  5. 并发型服务器响应方式,基于Java NIO 开发高性能并发型服务器程序的研究

    基于Java NIO 开发高性能并发型服务器程序的研究 第8卷%第5期 软件导刊 2009年5月SoftwareGuide Vol.8No.5May.2009 基于JavaNIO开发高性能并发型服务器 ...

  6. 基于java springboot的小说阅读微信小程序含后台管理系统源码

    系统运行环境 开发工具 eclipse(idea),mysql5.7(大于5.5),navicat,小程序开发工具 硬件要求 windows操作系统 cpu:2.4GHz 内存:4G 硬盘:100G ...

  7. 计算机毕业设计ssm基于java的仓储信息管理系统o9ypl系统+程序+源码+lw+远程部署

    计算机毕业设计ssm基于java的仓储信息管理系统o9ypl系统+程序+源码+lw+远程部署 计算机毕业设计ssm基于java的仓储信息管理系统o9ypl系统+程序+源码+lw+远程部署 本源码技术栈 ...

  8. 计算机毕业设计ssm基于Java通识课程管理系统v87xr系统+程序+源码+lw+远程部署

    计算机毕业设计ssm基于Java通识课程管理系统v87xr系统+程序+源码+lw+远程部署 计算机毕业设计ssm基于Java通识课程管理系统v87xr系统+程序+源码+lw+远程部署 本源码技术栈: ...

  9. 简单java socket_基于Java Socket实现一个简易在线聊天功能(一)

    最近做了一个项目,其中有一个在线网页交流的需求,好久没写代码了,手都生疏了,于是先写demo练练手,分享到脚本之家平台,以此做个记录,方便自己和大家使用. 先给大家说下实现步骤分这样几大步: 1.使用 ...

最新文章

  1. mysql kingshard 扩容_mysql中间件之kingshard
  2. 一个高效且友好的TensorFlow图神经网络(GNN)框架:tf_geometric
  3. 【Linux】2.Linux source命令
  4. C#在线获取歌词(转)
  5. Qt Creator预览版
  6. 如何在linux安装mysql7版本的,如何在linux下用tar方式安装Mysql5.7版本
  7. SpringMvc 系统启动时加载数据到内存中
  8. 【快速安装Docker服务及Docker配置、Docker常用命令。】
  9. 最新.NET 5.0 C#6 MVC6 WCF5 NoSQL Azure开发120课视频
  10. 程序员菜鸟到高手的11个阶段,你处于哪个阶段?
  11. html树形结构_数据结构-线性表.md
  12. bat之启动与禁用网卡
  13. python shell怎么调字体大小_如何更改在Python Shell字体大小
  14. arcgis 视频教程 ArcGIS Pro 从0到1
  15. bigemap如何添加第三方地图
  16. 记一次mykings暗云挖矿木马的排查与解决
  17. 电脑检测不到硬盘原因 电脑检测不到硬盘怎么办
  18. ubuntu16.04 配置远程桌面
  19. ART世界探险(6) - 流程控制指令
  20. 如何配置一台深度学习的主机

热门文章

  1. Java成神之路技术整理(长期更新)
  2. emlog链接html,emlog如何做站内外链跳转优化教程
  3. 用python + hadoop streaming 编写分布式程序(一) -- 原理介绍,样例程序与本地调试
  4. element-ui select
  5. 数论考试题(b) 求约数的约数的最大个数
  6. Visual Studio最好用的快捷键(你最喜欢哪个)
  7. flex布局应用与踩坑
  8. 手机网页里的模态对话框
  9. Android--Facebook Login without LoginButton
  10. Jetty 服务器架构分析(中)