什么是socket?
Socket的英文原义是“孔”或“插座”。在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket的原理
Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
- 服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
- 客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
- 连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
TCP与UDP的差别
UDP:
- 每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
- UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
- UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
TCP:
- 面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。
- TCP传输数据没有大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的数据。
- TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
应用:
- TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。但是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。
- UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。
功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个步骤:
- 创建Socket;
- 打开连接到Socket的输入/出流;
- 按照一定的协议对Socket进行读/写操作;
- 关闭Socket。
服务端:
public class Server {
/**
* java.net.ServerSocket
* ServerSocket是运行在服务端上的。其主要有两个作用
* 1:向服务端申请服务端口(客户端Socket就是通过这个端口与服务端建立连接的)
* 2:监听服务端口,一旦客户端连接会立即常见一个Socket,通过该Socket与客户端交互
*
* 如果我们将Socket比喻为"电话",那么ServerSocket相当于"总机"
*/
private ServerSocket serverSocket;
// private PrintWriter[] allOut = {};
private Collection<PrintWriter> allOut = new ArrayList<>();
public Server(){
try {
/*
ServerSocket在创建的时候要申请一个固定的端口号,客户端才能通过这个
端口建立连接。
如果该端口被当前操作系统中其他程序使用了,那么这里实例化会抛出异常:
java.net.BindException:address already in use
绑定异常:地址被使用了
*/
System.out.println("正在启动服务端...");
serverSocket = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
public void start(){
try {
/*
ServerSocket的accept方法是一个阻塞方法。
开始等待客户端的连接,一旦一个客户端通过端口建立连接,此时accept方法
会立即返回一个Socket实例。通过该实例可以与该客户端进行交互。
相当于是接电话的动作。
阻塞方法:调用后,程序就"卡住"不往下执行了。
*/
while(true) {
System.out.println("等待客户端连接");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接了!");
//启动一个线程来处理该客户端的交互
//Client:客户端 Handler:处理器
ClientHandler clientHandler = new ClientHandler(socket);
Thread thread = new Thread(clientHandler);
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
/**
* 该线程任务是用一个线程来处理一个客户端的交互工作
*/
private class ClientHandler implements Runnable{
private Socket socket;
private String host;//记录远端计算机的地址信息
public ClientHandler(Socket socket){
this.socket = socket;
host = socket.getInetAddress().getHostAddress();
}
public void run(){
PrintWriter pw = null;
try {
//通过socket获取输入流读取对方发送过来的消息
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
//通过socket获取输出流用于给对方发送消息
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw
= new OutputStreamWriter(out, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw,true);
//将该输出流存入共享数组allOut中
synchronized (Server.this) {
//1扩容allOut
// allOut = Arrays.copyOf(allOut, allOut.length + 1);
//2将pw放到数组最后一个格子里
// allOut[allOut.length - 1] = pw;
allOut.add(pw);
}
//通知所有客户端,该用户上线了!
sendMessage(host+"上线了,当前在线人数:"+allOut.size());
String line;
/*
这里的BufferedReader读取时低下连接的流是通过Socket获取的输入流,
当远端计算机还处于连接状态,但是暂时没有发送内容时,readLine方法会
处于阻塞状态,直到对方发送过来一行字符串为止。
如果返回值为null,则表示对方断开了连接(对方调用了socket.close())。
对于windows的客户端而言,如果是强行杀死的进程,服务端这里readLine方法
会抛出下面异常:
java.net.SocketException: connection reset
服务端无法避免这个异常。
*/
while ((line = br.readLine()) != null) {
//遍历allOut数组,将消息发送给所有客户端
sendMessage(host+"说:" + line);
}
}catch(IOException e){
// e.printStackTrace();
}finally {
//处理客户端断开连接后的操作
synchronized (Server.this) {
//将pw从数组allOut中删除
allOut.remove(pw);
}
sendMessage(host+"下线了,当前在线人数:"+allOut.size());
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 将消息群发给所有客户端
* @param line
*/
private void sendMessage(String line){
synchronized (Server.this) {
System.out.println(line);
//遍历allOut数组,将消息发送给所有客户端
for (PrintWriter pw : allOut) {
pw.println(line);
}
}
}
}
}
客户端
public class Client {
/*
java.net.Socket 插座 套接字
Socket封装了TCP协议的通讯细节,使用它可以和服务端建立TCP连接,并基于两个流的
读写完成数据交换。
*/
private Socket socket;
/**
* 构造方法,用于初始化客户端
*/
public Client(){
try {
/*
实例化Socket时常用的构造方法:
Socket(String host,int port)
这个构造器实例化Socket的过程就是与服务端建立连接的过程。
参数1:服务端的IP地址
参数2:服务端开启的服务端口
我们通过服务端的IP可以找到网络上服务端所在的计算机。通过端口号可以找到
该机器上的服务端应用程序从而与之建立连接。
*/
//本机IP地址的写法可以是“localhost”
System.out.println("正在连接服务端...");
socket = new Socket("localhost",8088);
System.out.println("与服务端建立连接了!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start(){
try {
//启动用于读取服务端发送过来消息的线程
ServerHandler handler = new ServerHandler();
Thread t = new Thread(handler);
t.start();
/*
通过socket获取的字节输出流写出的字节会通过网络发送给远端计算机
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw
= new OutputStreamWriter(out, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw,true);
Scanner scanner = new Scanner(System.in);
while(true) {
String line = scanner.nextLine();
if("exit".equalsIgnoreCase(line)){
break;
}
pw.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
/*
Socket提供了close方法,可以与远端计算机断开连接。
该方法调用时,也会自动关闭通过它获取的输入流和输出流。
*/
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
/**
* 该线程任务负责处理服务端发送过来的消息
*/
private class ServerHandler implements Runnable{
public void run(){
try{
//通过socket获取输入流读取服务端发送过来的消息
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String line;
//循环读取服务端发送过来的每一行字符串
while((line = br.readLine())!=null){
System.out.println(line);
}
}catch(IOException e){
}
}
}
}
以上就是简单认识Socket,及一个简单的客户端与服务端通信的小例子。
学习记录,如有侵权请联系删除。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/10211.html