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这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送 ...
随机推荐
- PHP批量更新MYSQL中的数据
原文链接:https://blog.csdn.net/wuming19900801/article/details/62893429 $sql = "update newhouse_clic ...
- slice(), splice(),split(),indexOf(),join(),replace()
1.slice() slice() 方法可以接受一或两个参数,即要返回项的起始和结束位置. 在只有一个参数的情况下, slice() 方法返回从该参数指定位置开始到当前数组末尾的所有项. 如果有两个参 ...
- email.py
import os import argparse import yaml import smtplib import csv from email.mime.multipart import MIM ...
- 12、Spring Boot 2.x 集成 MongoDB
1.12 Spring Boot 2.x 集成 MongoDB 完整源码: Spring-Boot-Demos
- python自动华 (八)
Python自动化 [第八篇]:Python基础-Socket编程进阶 本节内容: Socket语法及相关 SocketServer实现多并发 1. Socket语法及相关 sk = socket.s ...
- linux重启之后No CUDA-supporting devices found!
实验室做并行计算的服务重启后,采用cuda接口的应用程序vasp_gpu,运行时提示: CUDA Error in cuda_main.cu, line 144: unknown error No C ...
- PHP mysqli_connect() 函数
打开一个到 MySQL 服务器的新的连接: mysqli_connect(host,username,password,dbname,port,socket); <?php $con=mysql ...
- luogu 4768
kruskal 重构树对于一张无向图,我们在进行 kruskal 的过程中每当合并两个联通块时新建虚拟节点 t对于两个联通块的根节点 fau,fav 连无向边(fau, t),(fav, t) 其中点 ...
- jumpserver官方手动安装
测试环境 CPU: 64位双核处理器 内存: 4G DDR3 数据库:mysql 版本大于等于 5.6 mariadb 版本大于等于 5.5.6 环境 系统: CentOS 7 IP: 192.168 ...
- xpath简介备查
xpath简介 xpath 使用路径表达式在xml和html中进行导航 xpath包含标准函数库 xpath是一个w3c的标准 xpath节点关系 父节点 子节点 同袍节点 先辈节点 后代节点 xpa ...