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. 企业IT运维以及信息管理部服务器管理

    方法 1.服务器有必要保持简洁.除了必要的应用软件以及安全软件之外,尽量不要安全其它的软件. 2.要做好服务器帐号权利规划和分配,分配够用的权利就行,从而降低密码泄漏带来的损失. 3.注意关注服务器软 ...

  2. bind(),call(),apply()

    call .bind . apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了: call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 ob ...

  3. 如何创建javabeans实例

    如何创建javabeans实例 1.像使用普通java类一样,创建javabean实例 创建一个用户类的javabeans package com.po; /** * * 用户类 * @author ...

  4. nginx 重发机制导致的重复扣款问题

    问题:    nginx 重发机制导制重复提交(客户还款,被扣俩笔款,前端调用一次,后端执行2次) proxy_next_upstream 语法: proxy_next_upstream error ...

  5. 分享一波目前写的最强的autohotkey 插件

    支持各种软件快速切换,补全括号,代码等!!!!!!!! ;这种全局定义要写在所有代码的前面才能让所有代码起作用. SetCapsLockState , AlwaysOff SetNumlockStat ...

  6. ACM-ICPC 2018 南京赛区现场赛 E. Eva and Euro coins (思维)

    题目链接:https://codeforc.es/gym/101981/attachments 题意:给出两个只包含01的字符串,每次可以选择连续k个相同的数字进行翻转,问能否通过若干次操作把两个字符 ...

  7. KETTLE 更新表的两种方式-更新控件和sql更新 2种方式的实现比较

    在实际工作中,我们有可能遇见只更新不插入的情况,可以由以下2种方式去实现: 1.更新控件 如下图所示,根据id字段,更新name和cjsj时间字段 该控件不足的地方是,用来查询关键值得字段不够灵活,一 ...

  8. Activiti工作流学习(一)——Activiti服务类

    Activity有9个service1.DynamicBpmnService动态Bpmn服务Service providing access to the repository of process ...

  9. Oracle 后台进程(五)SMON进程

    转载自:刘相兵 Maclean Liu 文章 你所不知道的后台进程 SMON 功能   SMON(system monitor process)系统监控后台进程,有时候也被叫做 system clea ...

  10. Codevs 1768 种树 3(差分约束)

    1768 种树 3 时间限制: 2 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题目描述 Description 为了绿化乡村,H村积极响应号召,开始种树了. H村里有n幢 ...