hello/hi的简单的网络聊天程序
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的简单的网络聊天程序的更多相关文章
- 以您熟悉的编程语言为例完成一个hello/hi的简单的网络聊天程序
Socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信,应用程序通常通过"套接字"向网络发出 ...
- 以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析
套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程. 端 ...
- python实现一个简单的网络聊天程序
一.Linux Socket 1.Linux Socke基本上就是BSD Socket(伯克利套接字) 伯克利套接字的应用编程接口(API)是采用C语言的进程间通信的库,经常用在计算机网络间的通信.B ...
- 用Java实现简单的网络聊天程序
Socket套接字定义: 套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开.读写和关闭等操作.套接字允许应用程序将I/O插入到网络中,并与网络中的其他 ...
- telnet指令研究—以网络聊天程序为例
一.telnet指令 Telnet取名自Telecommunications和Networks的联合缩写,是早期个人计算机上连接到服务器主机的一个网络指令,由于存在安全问题,现在已经很少被使用.在wi ...
- C# 异步通信 网络聊天程序开发 局域网聊天室开发
Prepare 本文将使用一个NuGet公开的组件技术来实现一个局域网聊天程序,利用组件提供的高性能异步网络机制实现,免去了手动编写底层的困扰,易于二次开发,扩展自己的功能. 在Visual Stud ...
- boost asio异步读写网络聊天程序client 实例具体解释
boost官方文档中聊天程序实例解说 数据包格式chat_message.hpp <pre name="code" class="cpp">< ...
- boost asio异步读写网络聊天程序客户端 实例详解
boost官方文档中聊天程序实例讲解 数据包格式chat_message.hpp <pre name="code" class="cpp">< ...
- 使用Java实现hello/hi的简单网络聊天程序
Socket又称套接字,是基于应用服务与TCP/IP通信之间的一个抽象,它是计算机之间进行通信的一种约定或一种方式.通过socket这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送 ...
随机推荐
- WPF使用转换器(Converter)
1.作用:可以将源数据和目标数据之间进行特定的转化, 2.定义转换器,需要继承接口IValueConverter [ValueConversion(typeof(int), typeof(string ...
- WPF绑定属性
1.创建model类 model类要继承接口INotifyPropertyChanged,用于通知客户端属性值已更改 public class StudentModel : INotifyProper ...
- es批量导入进一对多的数据
es批量导入进一对多的数据 我有一个产品表 一个产品对应多个属性名 一个属性名对应多个属性值 一个产品还对应一个分类名称 控制层 @ApiOperation(value = "导入所有 ...
- centos6.5 安装emqtt
emqtt有监控界面,集群配置也非常简单. # yum -y update # yum install unzip vim wget xsltproc fop tk unixODBC unixODBC ...
- mysql基础篇--新增
语法 #支持单行.多行插入和子查询 insert into 表名(列名,...) values(值1,...); #单行插入 insert into 表名(列名,...) values(值1,...) ...
- Django REST framework+Vue 打造生鲜电商项目(笔记二)
(转自https://www.cnblogs.com/derek1184405959/p/8768059.html)(有修改) 接下来开始引入django resfulframework,体现它的强大 ...
- hibernate步骤和配置
1.引入hibernate的jar包和数据库驱动包 2.src添加hibernate.cfg.xml(hibernate配置文件) 3.数据库编写pojo public class Test { pu ...
- [2019HDU多校第一场][HDU 6584][G. Meteor]
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6584 题目大意:求所有满足\(0<\frac{p}{q}\leq1, gcd(p,q)=1,p\ ...
- Codeforces Round #551 (Div. 2) F. Serval and Bonus Problem (DP/FFT)
yyb大佬的博客 这线段期望好神啊... 还有O(nlogn)FFTO(nlogn)FFTO(nlogn)FFT的做法 Freopen大佬的博客 本蒟蒻只会O(n2)O(n^2)O(n2) CODE ...
- MNIST 数据集介绍
在学习机器学习的时候,首要的任务的就是准备一份通用的数据集,方便与其他的算法进行比较. MNIST数据集是一个手写数字数据集,每一张图片都是0到9中的单个数字,比如下面几个: MNIST数据库 ...