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

Socket简要阐述

Socket的概念

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

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

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

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

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

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

Socket原理

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

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

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

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

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

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

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

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

bb6d23c91cb6df1ac5c6e08e6625f3f7.png

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();

}

}

程序执行结果

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

f2f1e781eba7b62653cbcf4332e24ea2.png

服务器端与客户端的交互

4caef56396b1166e68b58e5da867b76f.png

92df023bfb8b5661d6bbc5f3a7153da0.png

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

创建ServerSocket

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

serverSocket = new ServerSocket(6666);

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

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

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

调用栈图示

f1f4c1fee11c4ebdd9c466f008e9823a.png

通过上图中对于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,其对应关系如下图。

912468d8f06f99315a0842d50d75179f.png

调用栈图示

0f8b5bb22677783a2f54658b125e3a16.png

同样的,我们可以得出: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的创建和绑定之后,服务器端进入监听的状态,等待客户端发出连接的请求。

调用栈图示

efa27ccfa1016e67525b7a556560db60.png

从上图可以得出: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连接。

调用栈图示

a1bbba96a7e6c853c5526c7c3089aba8.png

从上图可以得出: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实现的

参考链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/432945.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为基于策略划分VLAN的配置方法及示例

学过思科交换机的朋友&#xff0c;可能对基于策略划分VLAN的配置方法印象非常深&#xff0c;感觉确实比较复杂&#xff0c;先要配置VMPS以及VMPS数据库&#xff0c;但在华为交换机中&#xff0c;这种现象得到了彻底改变&#xff0c;因为它有了一种特殊的端口类型——Hybrid。说…

我的世界java刷怪数量_Minecraft我的世界Java版18w16a更新发布

Minecraft我的世界Java版18w16a更新发布&#xff01;Minecraft 1.13 仍未发布&#xff0c;18w16a为其第32个预览版。目前新版本已经基本完成了所有特性&#xff0c;现在更专注于漏洞修复和细节打磨了&#xff01;MINECRAFT SNAPSHOT 18W16AA Minecraft Java Edition snapshotJa…

IOS web app一些实用的属性设置

IOS对safari私有的属性很多&#xff0c;虽然很多不为人知但是却很实用。掌握好这些属性对web app和混合app的开发会很有帮助。 1.format-detection[telephoneno] 是否自动把电话号码转为链接 1<meta name"format-detection" content"telephoneno">IO…

小米功能机支持java吗_小米竟然卖功能机了!2.8吋/15天超长待机

【手机中国 新闻】众多周知&#xff0c;小米是从智能手机起家的&#xff0c;对于功能机从未涉足。但自从有了强大的小米生态链&#xff0c;制造各种科技产品那都不是事儿了。8月2日上午10点&#xff0c;小米有品众筹频道上线了一款功能手机——QIN多亲AI电话&#xff0c;仅售19…

Linux内核Crash分析

http://blog.chinaunix.net/uid-20788636-id-4377271.html 在工作中经常会遇到一些内核crash的情况&#xff0c;本文就是根据内核出现crash后的打印信息&#xff0c;对其进行了分析&#xff0c;使用的内核版本为&#xff1a;Linux2.6.32。 每一个进程的生命周期内&#xff0c…

java用beaninfo_JavaBeanInfo 和 Spring 之间的关系

Java Beans在这一章章节中笔者将和各位一起探讨关于 Java Beans 相关的内容。本章主要围绕 java.beans 这个包路径下的代码进行一些说明。在 Spring 中我们可以看到 BeanInfoFactory 该接口可以用来获取 Class 对应的 BeanInfo 对象&#xff0c;在 CachedIntrospectionResults …

selenium ruby和java_Selenium 2之Ruby版——安装篇

自从知道了Selenium的存在后&#xff0c;就一直都想&#xff0c;若要学习自动化&#xff0c;就要学习像Selenium这种比较有潜力的。Selenium有针对各种语言(java, C#, Python, Ruby, Perl)的版本&#xff0c;在此选择Ruby为学习方向&#xff0c;一来可以借此学习下Ruby&#xf…

基于visual Studio2013解决面试题之0702输出数字

&#xfeff;&#xfeff;&#xfeff;题目解决代码及点评/*输入数字 n&#xff0c;按顺序输出从 1 最大的 n 位 10 进制数。比如输入 3&#xff0c;则输出 1、2、3一直到最大的 3 位数即 999。 */#include <iostream> using namespace std;//在不考虑大数的情况下&#…

冠榕智能灯光控制协议分析(controller-node)

1. 在Z-WAVE PC Controller软件选择已配对的智能开关。 从上图中可以看到&#xff0c;我们的智能开关的node id是11&#xff0c;即0x0B。 2. 向智能开关发送灯光的开闭数据。 CommandClasses选择COMMAND_CLASS_BASIC CommandName选择BASIC_SET Value为00时关闭灯光&#xff0…

java中手动装入新类到类装饰器_关于java:抽象装饰器类中的功能而不是装饰器...

我目前正在阅读《Head First Design Patterns》一书&#xff0c;在"Decorator"一章中有以下示例&#xff1a;在书中&#xff0c;conditionmentDecorator类被描述为一个abstract decorator。下面是代码示例&#xff1a;public abstract class CondimentDecorator exte…

跨浏览器开发工作小结

本篇小结是在2011年时候总结的&#xff0c;当时做一个产品的跨浏览器兼容工作&#xff0c;由于产品开发的时间比较早&#xff0c;最开始只能在IE下面(IE 8、IE 9还有点点问题)使用&#xff0c;做跨浏览器兼容工作的时候&#xff0c;主要是适配IE 6--IE 9、Safari、FireFox、Chr…

冠榕智能灯光控制协议分析(node-controller)

1. 在Z-WAVE PC Controller软件选择已配对的智能开关。 从上图中可以看到&#xff0c;我们的智能开关的node id是11&#xff0c;即0x0B。 2. 按下智能开关&#xff0c;用串口工具可以看到以下信息。 01 0D 00 04 00 0B 07 60 0D 01 01 00 03 FF 6B 01 0D 00 04 00 0B 07 60…

冠榕智能灯光控制协议分析(controller-node) 2

z-wave第一篇&#xff0c;我们用COMMAND_CLASS_BASIC - BASIC_SET控制智能开关。但是智能开关上有两个执行器&#xff0c;我们只能控制其中一路&#xff0c;那么我们如何控制另一路的开关的。在z-wave第二篇&#xff0c;我们分析了智能开关两个按键发送的消息&#xff0c;发现&…

基于visual Studio2013解决面试题之0902内存拷贝

&#xfeff;&#xfeff;&#xfeff;题目解决代码及点评/*用 C 语言实现函数 void * memmove(void *dest,const void *src,size_t n)memmove 函数的功能是拷贝 src 所指的内存内容前 n 个字节到 dest 所指的地址上。 简单循环拷贝即可&#xff0c;但是这道题&#xff0c;要深…

冠榕智能灯光控制协议分析(controller init)

上面几篇已经详细介绍了z-wave协议的分析方法&#xff0c;这一章&#xff0c;我们分析z-wave pc controller初始化时的通信信息。我们只将关键信息列出&#xff0c;然后直接将分析出来的串口数据列出。 1. 得到z-wave版本 01 03 00 15 E9 06 01 10 01 15 5A 2D 57 61 76 65…

jmeter找不到java_Windows下Jmeter安装出现Not able to find Java executable or version问题解决方案...

最近在做一个开放接口平台性能测试 , 指标是最少达到1000/s的并发 , 接口鉴权 百万级的表 在1s内完成..在众多压测工具中 ,,选择了Apache的jmeter ,于官网下载了最新版本http://jmeter.apache.org/download_jmeter.cgi (jmeter下载地址)由于jmeter运行是基于java的,所以需要…

ZDB5304烧写方法

1&#xff0e; 跳线和5304的位置如下图 2. 打开z-wave programmer软件&#xff0c;设置如下图&#xff0c;注意烧写接口为uart&#xff0c;烧写的时候会提示的。选yes是uart&#xff0c;选no是spi。 烧写过程中会提示按下reset或释放reset按键。照做即可。 烧完后&#xff0c…

基于Z-Wave无线技术的指纹锁系统设计

http://www.chinaaet.com/article/218940 摘 要&#xff1a; 结合新兴的低功耗的Z-Wave短距无线通信技术&#xff0c;设计一种应用于酒店的智能指纹锁无线管理与控制系统。该系统的门锁硬件电路包括主控制器S3C2440、指纹采集模块、电机驱动模块及ZM3102无线模块&#xff0c;…

Hibernate一对一关联------主键关联(亲测成功)

1、创建两个实体&#xff08;Company.java和Login.java&#xff09;代码如下&#xff1a; 1 package wck.stu.vo.onetoonein;2 3 public class Company {4 private String id "";5 6 private String companyName "";7 8 private Str…

INTEL和AMD两大巨头的前身

仙童半导体公司&#xff0c;曾经是世界上最大、最富创新精神和最令人振奋的半导体生产企业&#xff0c;为硅谷的成长奠定了坚实的基础。更重要的是&#xff0c;这家公司还为硅谷孕育了成千上万的技术人才和管理人才&#xff0c;它不愧是电子、电脑业界的“西点军校”&#xff0…