Socket通信
Posted 小小工匠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket通信相关的知识,希望对你有一定的参考价值。
Socket理论知识
OSI七层网络模型
OSI七层网络模型(从下往上):
OSI是一个理想的模型,一般的网络系统只涉及其中的几层,在七层模型中,每一层都提供一个特殊 的网络功能,从网络功能角度观察:
- 下面4层(物理层、数据链路层、网络层和传输层)主要提供数据传输和交换功能, 即以节点到节点之间的通信为主
第4层作为上下两部分的桥梁,是整个网络体系结构中最关键的部分; - 上3层(会话层、表示层和应用层)则以提供用户与应用程序之间的信息和数据处理功能为主
简言之,下4层主要完成通信子网的功能,上3层主要完成资源子网的功能。
OSI七层模型详解
TCP/IP四层模型
TCP/UDP区别
Socket开发相关的一些概念名词:
IP地址
IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
端口
- 用于区分不同的应用程序
- 端口号的范围为0-65535,其中0-1023未系统的保留端口,我们的程序尽可能别使用这些端口!
- IP地址和端口号组成了我们的Socket,Socket是网络运行程序间双向通信链路的终结点, 是TCP和UDP的基础!
- 常用协议使用的端口:HTTP:80,FTP:21,TELNET:23
TCP协议与UDP协议
TCP协议流程详解
首先TCP/IP是一个协议簇,里面包括很多协议的。UDP只是其中的一个。之所以命名为TCP/IP协议, 因为TCP,IP协议是两个很重要的协议,就用他两命名了。
下面我们来讲解TCP协议和UDP协议的区别:
TCP(Transmission Control Protocol,传输控制协议)是面向连接的协议,即在收发数据钱 ,都需要与对面建立可靠的链接,这也是面试经常会问到的TCP的三次握手以及TCP的四次挥手!
三次握手: 建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立, 在Socket编程中,这一过程由客户端执行connect来触发,具体流程图如下:
- 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server, Client进入SYN_SENT状态,等待Server确认。
- 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位 SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求 ,Server进入SYN_RCVD状态。
- 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK 置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则 连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以 开始传输数据了。
四次挥手: 终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。 在Socket编程中,这一过程由客户端或服务端任一方执行close来触发,具体流程图如下
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入 FIN_WAIT_1状态
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同, 一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK 状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。 另外也可能是同时发起主动关闭的情况:
另外还可能有一个常见的问题就是:为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 答:因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里 发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还 能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些 数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会 分开发送。
UDP协议详解
UDP(User Datagram Protocol)用户数据报协议,非连接的协议,传输数据之前源端和终端不 建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。 在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽 的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。 相比TCP就是无需建立链接,结构简单,无法保证正确性,容易丢包
Java中对于网络提供的几个关键类:
针对不同的网络通信层次,Java给我们提供的网络功能有四大类:
- InetAddress: 用于标识网络上的硬件资源
- URL: 统一资源定位符,通过URL可以直接读取或者写入网络上的数据
- Socket和ServerSocket: 使用TCP协议实现网络通信的Socket相关的类
- Datagram: 使用UDP协议,将数据保存在数据报中,通过网络进行通信
InetAddress:
public class InetAddressTest {
public static void main(String[] args) throws Exception{
//获取本机InetAddress的实例:
InetAddress address = InetAddress.getLocalHost();
System.out.println("本机名:" + address.getHostName());
System.out.println("IP地址:" + address.getHostAddress());
byte[] bytes = address.getAddress();
System.out.println("字节数组形式的IP地址:" + Arrays.toString(bytes));
System.out.println("直接输出InetAddress对象:" + address);
}
}
基于TCP协议的Socket通信-简易聊天室
基本介绍和使用
什么是Socket
Socket通信模型
Socket通信实现步骤解析:
- Step 1:创建ServerSocket和Socket
- Step 2:打开连接到的Socket的输入/输出流
- Step 3:按照协议对Socket进行读/写操作
- Step 4:关闭输入输出流,以及Socket
我们接下来写一个简单的例子,开启服务端后,客户端点击按钮然后链接服务端, 并向服务端发送一串字符串,表示通过Socket链接上服务器~
Socket服务端的编写
步骤
Step 1:创建ServerSocket对象,绑定监听的端口
Step 2:调用accept()方法监听客户端的请求
Step 3:连接建立后,通过输入流读取客户端发送的请求信息
Step 4:通过输出流向客户端发送响应信息
Step 5:关闭相关资源
Code
在Eclipse下创建一个Java项目,代码如下:
package com.turing.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Step 1:创建ServerSocket对象,绑定监听的端口
* Step 2:调用accept()方法监听客户端的请求
* Step 3:连接建立后,通过输入流读取客户端发送的请求信息
* Step 4:通过输出流向客户端发送响应信息
* Step 5:关闭相关资源
* *
*/
public class SocketServer {
public static void main(String[] args) {
try {
// 1. 创建一个服务端Socket,即ServerSocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket = new ServerSocket(9999);
// 获取服务端IP
InetAddress inetAddress = InetAddress.getLocalHost();
String ip = inetAddress.getHostAddress();
System.out.println("~~~服务端已就绪,等待客户端接入~,服务端ip地址: " + ip);
// 2. 调用accept等待客户端连接
Socket socket = serverSocket.accept();
// 3. 连接后获取输入流,读取客户端信息
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
// 获取输入流
is = socket.getInputStream();
isr = new InputStreamReader(is, "UTF-8");
br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("客户端发送过来的信息" + info);
}
socket.shutdownInput();// 关闭输入流
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行服务端
Socket客户端的编写
android客户端
步骤
Step 1:创建Socket对象,指明需要链接的服务器的地址和端号
Step 2:链接建立后,通过输出流向服务器发送请求信息
Step 3:通过输出流获取服务器响应的信息
Step 4:关闭相关资源
Code
package com.turing.base.activity.socket.baseuse;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.turing.base.R;
import com.turing.base.utils.AlertUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
/**
* Step 1:创建Socket对象,指明需要链接的服务器的地址和端号
Step 2:链接建立后,通过输出流向服务器发送请求信息
Step 3:通过输出流获取服务器响应的信息
Step 4:关闭相关资源
服务端Java工程:D:\\workspace\\ws-java-base\\SocketServer
*/
public class SocketClientAct extends AppCompatActivity implements View.OnClickListener {
private Button socketSend ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_socket_client);
socketSend = (Button) findViewById(R.id.id_btn_sendMsg);
socketSend.setOnClickListener(this);
}
@Override
public void onClick(View v) {
AlertUtil.showToastShort(this,"观察服务端日志~");
//Android不允许在主线程(UI线程)中做网络操作,
// 所以这里需要我们自己 另开一个线程来连接Socket!
new Thread() {
@Override
public void run() {
try {
acceptServer();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
/**
* 连接ServerSocket发送消息
*/
private void acceptServer() throws IOException{
//1.创建客户端Socket,指定服务器地址和端口
Socket socket = new Socket("192.168.56.1", 9999);
//2.获取输出流,向服务器端发送信息
OutputStream os = socket.getOutputStream();//字节输出流
PrintWriter pw = new PrintWriter(os);//将输出流包装为打印流
//获取客户端的IP地址
InetAddress address = InetAddress.getLocalHost();
String ip = address.getHostAddress();
// 将这个信息发送给服务端
pw.write("客户端:~" + ip + "~ 接入服务器!!");
pw.flush();
socket.shutdownOutput();//关闭输出流
socket.close();
}
}
因为Android不允许在主线程(UI线程)中做网络操作,所以这里需要我们自己 另开一个线程来连接Socket!
运行结果:
点击按钮后,服务端控制台打印
简易聊天室
基于Socket完成大文件的断点续传
基于UDP协议的Socket通信
UDP以数据报作为数据的传输载体,在进行传输时 首先要把传输的数据定义成数据报(Datagram),在数据报中指明数据要到达的Socket(主机地址 和端口号),然后再将数据以数据报的形式发送出去,然后就没有然后了,服务端收不收到我就 不知道了,除非服务端收到后又给我回一段确认的数据报。
服务端实现步骤
Step 1:创建DatagramSocket,指定端口号
Step 2:创建DatagramPacket
Step 3:接收客户端发送的数据信息
Step 4:读取数据
public class UPDServer {
public static void main(String[] args) throws IOException {
/*
* 接收客户端发送的数据
*/
// 1.创建服务器端DatagramSocket,指定端口
DatagramSocket socket = new DatagramSocket(12345);
// 2.创建数据报,用于接收客户端发送的数据
byte[] data = new byte[1024];// 创建字节数组,指定接收的数据包的大小
DatagramPacket packet = new DatagramPacket(data, data.length);
// 3.接收客户端发送的数据
System.out.println("****服务器端已经启动,等待客户端发送数据");
socket.receive(packet);// 此方法在接收到数据报之前会一直阻塞
// 4.读取数据
String info = new String(data, 0, packet.getLength());
System.out.println("我是服务器,客户端说:" + info);
/*
* 向客户端响应数据
*/
// 1.定义客户端的地址、端口号、数据
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "欢迎您!".getBytes();
// 2.创建数据报,包含响应的数据信息
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
// 3.响应客户端
socket.send(packet2);
// 4.关闭资源
socket.close();
}
}
客户端实现步骤
Step 1:定义发送信息
Step 2:创建DatagramPacket,包含将要发送的信息
Step 3:创建DatagramSocket
Step 4:发送数据
public class UDPClient {
public static void main(String[] args) throws IOException {
/*
* 向服务器端发送数据
*/
// 1.定义服务器的地址、端口号、数据
InetAddress address = InetAddress.getByName("localhost");
int port = 8800;
byte[] data = "用户名:admin;密码:123".getBytes();
// 2.创建数据报,包含发送的数据信息
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
// 3.创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
// 4.向服务器端发送数据报
socket.send(packet);
/*
* 接收服务器端响应的数据
*/
// 1.创建数据报,用于接收服务器端响应的数据
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
// 2.接收服务器响应的数据
socket.receive(packet2);
// 3.读取数据
String reply = new String(data2, 0, packet2.getLength());
System.out.println("我是客户端,服务器说:" + reply);
// 4.关闭资源
socket.close();
}
}
总结
将数据转换为字节,然后放到DatagramPacket(数据报包中),发送的 时候带上接受者的IP地址和端口号,而接收时,用一个字节数组来缓存!发送的时候需要创建一个 DatagramSocket(端到端通信的类)对象,然后调用send方法给接受者发送数据报包
以上是关于Socket通信的主要内容,如果未能解决你的问题,请参考以下文章