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

0 Linux Socket API

Berkeley套接字接口,一个应用程序接口(API),使用一个Internet套接字的概念,使主机间或者一台计算机上的进程间可以通讯。 它可以在很多不同的输入/输出设备和驱动之上运行,尽管这有赖于操作系统的具体实现。 接口实现用于TCP/IP协议,因此它是维持Internet的基本技术之一。 它是由加利福尼亚的伯克利大学开发,最初用于Unix系统。 如今,所有的现代操作系统都有一些源于Berkeley套接字接口的实现,它已成为连接Internet的标准接口。

我们所用的Linux Socket API实际上就是 Berkeley Socket,下面给出一些建立TCP连接常用的Socket接口函数及其功能概要:

  • socket() 创建一个新的确定类型的套接字,类型用一个整型数值标识(文件描述符),并为它分配系统资源。
  • bind() 一般用于服务器端,将一个套接字与一个套接字地址结构相关联,比如,一个指定的本地端口和IP地址。
  • listen() 用于服务器端,使一个绑定的TCP套接字的tcp状态由CLOSE转至LISTEN;操作系统内核为此监听socket所对应的tcp服务器创建一个pending socket队列和一个established socket队列;参数backlog指定pending socket队列的长度,0表示长度可以无限大。pending socket,就是某客户端三次握手的syn包到达,内核为这个syn包对应的tcp请求生成一个socket(状态为SYN_RECV),但三次握手还没有完成时的socket。
  • connect() 用于客户端,为一个套接字分配一个自由的本地端口号。 如果是TCP套接字的话,它会试图获得一个新的TCP连接。
  • accept() 用于服务器端。 它接受一个从远端客户端发出的创建一个新的TCP连接的接入请求,创建一个新的套接字,与该连接相应的套接字地址相关联。
  • send()recv(),或者write()read(),或者recvfrom()sendto(), 用于往/从远程套接字发送和接受数据。
  • close() 用于系统释放分配给一个套接字的资源。 如果是TCP,连接会被中断。

1 hello/hi程序

1.0 功能概述

下面提供一个使用Java语言的简单的hello/hi程序代码,程序分为两部分,分别是服务端和客户端。该程序利用Java Socket API建立一个TCP连接,先从客户端向服务端发送一个“Hi,I am Client”字符串,然后服务端再返回一个“Hi,I am Server”字符串。

1.1 服务端

服务端的功能是使用Java的ServerSocket类的对象监听8090端口,然后使用ServerSocket类的accept()与8090端口建立连接,当收到一个来自客户端的"Hi,I am Client"时,回复一个"Hi,I am Server",代码如下:

public class Server {
public static void main(String[] args) throws Exception {
ServerSocket socket = new ServerSocket(8090);// 监听8090端口
System.out.println("TCP server ready.");
Socket sock = socket.accept();// 建立连接
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) { String cmd = reader.readLine();// 读取发送到服务端的数据
if ("Hi,I am Client".equals(cmd)) {
System.out.println("Client:"+cmd+"\n");
writer.write("Hi,I am Server"+ "\n");
writer.flush();
} else {
writer.write("require data\n");
writer.flush();
}
}
}
sock.close();// 关闭连接
socket.close();// 关闭监听端口
}
}

1.2 客户端

客户端的功能是获取本机地址,然后与本机的8090端口建立连接。向服务端发送"Hi,I am Client"后等待服务的回应的消息。

public class Client {
public static void main(String[] args) throws IOException {
InetAddress addr = InetAddress.getLoopbackAddress();// 获取本机地址,即“127.0.0.1”
try (Socket socket = new Socket(addr, 8090)) {// 与本机8090端口建立连接
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8))) {
writer.write("Hi,I am Client\n");
writer.flush();
String resp = reader.readLine();// 读取本机8090端口返回的数据
System.out.println("Server: " + resp);
}
}
}
}
}

2 分析Java Socket API和Linux Socket API的关系

上面两段程序调用ServerSocket类的构造方法和close(),还有Socket类的构造方法、accept()getInputStream()、``getOutputStream()close()`方法。下面通过对ServerSocket类以及Socket类的源码分析找到Java Socket API和Linux Socket API的对应关系。

2.0 SocketImpl类与Linux Socket API的关系

抽象类SocketImpl是实际上实现套接字的所有类的通用超类,它将实际的Socket操作抽象出来,在逻辑上与Linux Socket API的核心操作相对应,如果找到SocketImpl类的操作,就相当于找到了对应的Linux Socket API操作。

2.1 ServerSocket类

2.1.0 SeverSocket()

在服务端程序中,构造了一个ServerSocket类对象监听8090端口:

ServerSocket socket = new ServerSocket(8090);

找到相应的源码:

public ServerSocket(int port) throws IOException {
this(port, 50, (InetAddress)null);
}
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
this.created = false;
this.bound = false;
this.closed = false;
this.closeLock = new Object();
this.oldImpl = false;
this.setImpl();
if (port >= 0 && port <= 65535) {
if (backlog < 1) {
backlog = 50;
} try {
this.bind(new InetSocketAddress(bindAddr, port), backlog);
} catch (SecurityException var5) {
this.close();
throw var5;
} catch (IOException var6) {
this.close();
throw var6;
}
} else {
throw new IllegalArgumentException("Port value out of range: " + port);
}

可以看到,this.bind(new InetSocketAddress(bindAddr, port), backlog);语句中调用了bind()方法,查看其具体实现:

this.getImpl().bind(epoint.getAddress(), epoint.getPort());
this.getImpl().listen(backlog);

发现SeverSocket.bind()方法与Linux Socket API中的bind()listen()函数相对应。

所以ServerSocket()方法对应的Linux Socket API是socket()bind()listen()

2.1.1 accept()

服务端中还使用ServerSocket.accept()方法初始化了一个Socket对象,下面找到其源码:

public Socket accept() throws IOException {
if (this.isClosed()) {
throw new SocketException("Socket is closed");
} else if (!this.isBound()) {
throw new SocketException("Socket is not bound yet");
} else {
Socket s = new Socket((SocketImpl)null);
this.implAccept(s);
return s;
}
}

发现其对应的Linux Socket API是accept()

2.1.2 close()

public void close() throws IOException {
synchronized(this.closeLock) {
if (!this.isClosed()) {
if (this.created) {
this.impl.close();
} this.closed = true;
}
}
}

显然,ServerSocket.close()对应的Linux Socket API是close()

2.2 Socket类

在客户端和服务端分别构造了一个Socket类对象用于建立Tcp连接和通信。下面分析所用函数的源码。

2.2.0 Socket()

客户端和服务端使用的构造函数分别是Socket(SocketImpl impl)Socket(InetAddress address, int port),前者其实是复制了一个Socket类的对象,只需看后者的源码即可:

public Socket(InetAddress address, int port) throws IOException {
this(address != null ? new InetSocketAddress(address, port) : null, (SocketAddress)null, true);
}

接着找真正的调用的构造函数:

private Socket(SocketAddress address, SocketAddress localAddr, boolean stream) throws IOException {
this.created = false;
this.bound = false;
this.connected = false;
this.closed = false;
this.closeLock = new Object();
this.shutIn = false;
this.shutOut = false;
this.oldImpl = false;
this.setImpl();
if (address == null) {
throw new NullPointerException();
} else {
try {
this.createImpl(stream);
if (localAddr != null) {
this.bind(localAddr);
} this.connect(address);
} catch (IllegalArgumentException | SecurityException | IOException var7) {
try {
this.close();
} catch (IOException var6) {
var7.addSuppressed(var6);
} throw var7;
}
}
}

发现该方法调用了Socket类的bind()connect()方法。

根据前面已经知道,ServerSocket类的ServerSocket.bind()方法与Linux Socket API中的bind()listen()函数相对应,那么在Socket类中是否是这么对应的呢?查看一下Socket.bind()方法的核心语句:this.getImpl().bind(addr, port);发现在Socket.bind()方法对应只是对应Linux Socket API中的bind()

Socket.connect()方法的核心语句如下:

if (!this.oldImpl) {
this.impl.connect(epoint, timeout);
} else {
if (timeout != 0) {
throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)");
} if (epoint.isUnresolved()) {
this.impl.connect(addr.getHostName(), port);
} else {
this.impl.connect(addr, port);
}
}

发现对应的Linux Socket API中的函数是connect()

由上面的分析得出,Socket()对于的Linux Socket API是socket()bind()connect()

2.2.1 getInputStream() & getOutputStream()

像之前一样,找到对应的源码:

public InputStream getInputStream() throws IOException {
if (this.isClosed()) {
throw new SocketException("Socket is closed");
} else if (!this.isConnected()) {
throw new SocketException("Socket is not connected");
} else if (this.isInputShutdown()) {
throw new SocketException("Socket input is shutdown");
} else {
InputStream is = null; try {
is = (InputStream)AccessController.doPrivileged(new PrivilegedExceptionAction<InputStream>() {
public InputStream run() throws IOException {
return Socket.this.impl.getInputStream();
}
});
return is;
} catch (PrivilegedActionException var3) {
throw (IOException)var3.getException();
}
}
}
public OutputStream getOutputStream() throws IOException {
if (this.isClosed()) {
throw new SocketException("Socket is closed");
} else if (!this.isConnected()) {
throw new SocketException("Socket is not connected");
} else if (this.isOutputShutdown()) {
throw new SocketException("Socket output is shutdown");
} else {
OutputStream os = null; try {
os = (OutputStream)AccessController.doPrivileged(new PrivilegedExceptionAction<OutputStream>() {
public OutputStream run() throws IOException {
return Socket.this.impl.getOutputStream();
}
});
return os;
} catch (PrivilegedActionException var3) {
throw (IOException)var3.getException();
}
}
}

可以看到,Socket.getInputStream()Socket.getOutputStream()对应的Linux Java API是recv()send()

2.2.2 close()

public synchronized void close() throws IOException {
synchronized(this.closeLock) {
if (!this.isClosed()) {
if (this.created) {
this.impl.close();
} this.closed = true;
}
}
}

同样,Socket.close()对应的Linux Java API是close()

3 总结

由上面的探究可知,Java Socket API和Linux Socket API的关系如下表:

Java Socket API Linux Socket API
ServerSocket() socket()bind()listen()
ServerSocket.accept() accept()
ServerSocket.bind() bind()listen()
ServerSocket.close() close()
Socket() socket()bind()connect()
Socket.bind() bind()connect()
Socket.getInputStream() recv()
Socket.getOutputStream() send()
Socket.close() close()

可以看到,Java Socket API对Linux Socket API进行了进一步封装,使之更容易根据不同情况进行TCP连接。

参考资料

https://www.cnblogs.com/abcboy/p/9769230.html

https://github.com/mengning/net

https://zh.wikipedia.org/wiki/Berkeley%E5%A5%97%E6%8E%A5%E5%AD%97

hello/hi的简单的网络聊天程序的更多相关文章

  1. 以您熟悉的编程语言为例完成一个hello/hi的简单的网络聊天程序

    Socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信,应用程序通常通过"套接字"向网络发出 ...

  2. 以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

    套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程. 端 ...

  3. python实现一个简单的网络聊天程序

    一.Linux Socket 1.Linux Socke基本上就是BSD Socket(伯克利套接字) 伯克利套接字的应用编程接口(API)是采用C语言的进程间通信的库,经常用在计算机网络间的通信.B ...

  4. 用Java实现简单的网络聊天程序

    Socket套接字定义: 套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开.读写和关闭等操作.套接字允许应用程序将I/O插入到网络中,并与网络中的其他 ...

  5. telnet指令研究—以网络聊天程序为例

    一.telnet指令 Telnet取名自Telecommunications和Networks的联合缩写,是早期个人计算机上连接到服务器主机的一个网络指令,由于存在安全问题,现在已经很少被使用.在wi ...

  6. C# 异步通信 网络聊天程序开发 局域网聊天室开发

    Prepare 本文将使用一个NuGet公开的组件技术来实现一个局域网聊天程序,利用组件提供的高性能异步网络机制实现,免去了手动编写底层的困扰,易于二次开发,扩展自己的功能. 在Visual Stud ...

  7. boost asio异步读写网络聊天程序client 实例具体解释

    boost官方文档中聊天程序实例解说 数据包格式chat_message.hpp <pre name="code" class="cpp">< ...

  8. boost asio异步读写网络聊天程序客户端 实例详解

    boost官方文档中聊天程序实例讲解 数据包格式chat_message.hpp <pre name="code" class="cpp">< ...

  9. 使用Java实现hello/hi的简单网络聊天程序

    Socket又称套接字,是基于应用服务与TCP/IP通信之间的一个抽象,它是计算机之间进行通信的一种约定或一种方式.通过socket这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送 ...

随机推荐

  1. WPF使用转换器(Converter)

    1.作用:可以将源数据和目标数据之间进行特定的转化, 2.定义转换器,需要继承接口IValueConverter [ValueConversion(typeof(int), typeof(string ...

  2. 5.7 zip 版本的安装 以及遇到的坑

    https://blog.csdn.net/indexman/article/details/80291537 https://blog.csdn.net/weidong_y/article/deta ...

  3. [WebMethod]参数介绍

    一.WebService的调试 net 2.0新建webservice为了安全考虑,默认关闭了Post和Get方法 .让其打开,可在Web.config文件的<system.web>下增加 ...

  4. input 更改 pleaseholder 的字体样式

    /* WebKit browsers */input::-webkit-input-placeholder {    color: red;    font-size: 14px;}/* Mozill ...

  5. C# LINQ标准查询操作符

    首先添加数据集合 [Serializable] public class Racer : IComparable<Racer>, IFormattable { public Racer() ...

  6. js原生ajax与jquery的ajax的用法区别

    什么是ajax和原理? AJAX 是一种用于创建快速动态网页的技术. 通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据 XMLHttpRequest对象的基本属性: onre ...

  7. Appium自动化测试教程-自学网-monkey简介

    Monkey简介 在Android的官方自动化测试领域有一只非常著名的“猴子”叫Monkey,这只“猴子”一旦启动,就会让被测的Android应用程序像猴子一样活蹦乱跳,到处乱跑.人们常用这只“猴子” ...

  8. MySQL 8.0.3性能大杀器 —— CATS 事务调度新算发

    转载自:https://www.sohu.com/a/203933205_487483?sec=wd&spm=smpc.author.fd-d.2.1557386676880JSjtJwV 好 ...

  9. 【csp模拟赛6】相遇--LCA

    对于30%的数据:暴力枚举判断 对于60%的数据:还是暴力枚举,把两条路径都走一遍计一下数就行,出现一个点被访问两次即可判定重合 对于100%的数据:找出每条路径中距离根最近的点(lca),判断这个点 ...

  10. Til the Cows Come Home ( POJ 2387) (简单最短路 Dijkstra)

    problem Bessie is out in the field and wants to get back to the barn to get as much sleep as possibl ...