Java Socket 工作机制

Java Socket 工作机制

这里是引用

Socket 这个概念没有对应到一个具体的实体,他是描述计算机之间完成相互通信的一种抽象功能。打个比方,可以吧Socket比作为两个城市之间的工作的交通工具,有了它,就可以在城市之间来回穿梭了。交通工具有多种,每种交通工具也有相应的交通规则的。Socket也一样,也有多种。大部分情况下我们使用的都是基于TCP/IP的流套接字,它是一种稳定的通讯协议。

img

​ 图1 Socket 通讯实例

主机A的应用程序要能和主机B的应用程序通信,通过Socket建立连接,而建立Socket连接必须需要底层TCP/IP协议来建立TCP连接。建立TCP连接需要底层IP协议来寻找网络中的主机。我们知道网络层使用的IP协议可以帮助我们根据IP地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过TCP或UPD的地址也就是端口号来指定。这样就可以通过一个Socket实力唯一代表一个主机上的一个应用程序的通信链路了。

建立通讯链路

当客户端要与服务端通信,客户端首先要创建一个 Socket 实例,操作系统将为这个 Socket 实例分配一个没有被使用的本地端口号,并创建一个包含本地和远程地址和端口号的套接字数据结构,这个数据结构将一直保存在系统中直到这个连接关闭。在创建 Socket 实例的构造函数正确返回之前,将要进行 TCP 的三次握手协议,TCP 握手协议完成后,Socket 实例对象将创建完成,否则将抛出 IOException 错误。

与之对应的服务端将创建一个 ServerSocket 实例,ServerSocket 创建比较简单只要指定的端口号没有被占用,一般实例创建都会成功,同时操作系统也会为 ServerSocket 实例创建一个底层数据结构,这个数据结构中包含指定监听的端口号和包含监听地址的通配符,通常情况下都是”*”即监听所有地址。之后当调用 accept() 方法时,将进入阻塞状态,等待客户端的请求。当一个新的请求到来时,将为这个连接创建一个新的套接字数据结构,该套接字数据的信息包含的地址和端口信息正是请求源地址和端口。这个新创建的数据结构将会关联到 ServerSocket 实例的一个未完成的连接数据结构列表中,注意这时服务端与之对应的 Socket 实例并没有完成创建,而要等到与客户端的三次握手完成后,这个服务端的 Socket 实例才会返回,并将这个 Socket 实例对应的数据结构从未完成列表中移到已完成列表中。所以 ServerSocket 所关联的列表中每个数据结构,都代表与一个客户端的建立的 TCP 连接。

数据传输

传输数据是我们建立连接的主要目的,如何通过 Socket 传输数据,下面将详细介绍。

当连接已经建立成功,服务端和客户端都会拥有一个 Socket 实例,每个 Socket 实例都有一个 InputStream 和 OutputStream,正是通过这两个对象来交换数据。同时我们也知道网络 I/O 都是以字节流传输的。当 Socket 对象创建时,操作系统将会为 InputStream 和 OutputStream 分别分配一定大小的缓冲区,数据的写入和读取都是通过这个缓存区完成的。写入端将数据写到 OutputStream 对应的 SendQ 队列中,当队列填满时,数据将被发送到另一端 InputStream 的 RecvQ 队列中,如果这时 RecvQ 已经满了,那么 OutputStream 的 write 方法将会阻塞直到 RecvQ 队列有足够的空间容纳 SendQ 发送的数据。值得特别注意的是,这个缓存区的大小以及写入端的速度和读取端的速度非常影响这个连接的数据传输效率,由于可能会发生阻塞,所以网络 I/O 与磁盘 I/O 在数据的写入和读取还要有一个协调的过程,如果两边同时传送数据时可能会产生死锁,在后面 NIO 部分将介绍避免这种情况。

深入分析 Java I/O 的工作机制


代码献上

socketTest.Server_Test 建立服务端

package socketTest;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class Server_Test extends Thread {
	ServerSocket server = null;
	Socket socket = null;
	
	public Server_Test(int port) {
		try {
			server = new ServerSocket(port);
		}catch(IOException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		super.run();
		System.out.println(getdate()+ "等待客户端连接。。。");
		try {
			socket = server.accept();
			//连接并返回socket后,再启用发送消息线程
			if (socket.isConnected()) {
				System.out.println(getdate()+"客户端已连接");
			}
			new sendMessThread().start();
			InputStream in = socket.getInputStream();
			int len = 0;
			byte[] buf = new byte[1024];
			while ((len = in.read(buf)) != -1) {
				System.out.println(getdate()+"客户端:("
						+ socket.getInetAddress().getHostAddress()+")说:"
						+ new String(buf,0,len,"UTF-8") 
						);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private static String getdate() {
		Date date = new Date();
		SimpleDateFormat sdformat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
		String result = sdformat.format(date);
		return result;
	}
	
	class sendMessThread extends Thread {
		@Override
		public void run() {
			super.run();
			Scanner scanner = null;
			OutputStream out = null;
			try {
				if (socket != null) {
					scanner = new Scanner(System.in);
					out = socket.getOutputStream();
					String in = "";
					do {
						in = scanner.next();
						out.write((""+in).getBytes("UTF-8"));
						//清空缓存区的内容
						out.flush();
					} while (!("q").equals(in));
					scanner.close();
					try {
						out.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}


	}
	
	//函数入口
	public static void main(String[] args) {
		Server_Test server_Test = new Server_Test(8012);
		server_Test.start();
	}
	
}

socketTest.Client 客户端

package socketTest;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class Client extends Thread {
	//定义一个Socket对象
	Socket socket = null;
	public Client(String host,int port) {
		try {
			//需要服务器的IP地址和端口号,才能获取正确的Socket对象
			socket = new Socket(host,port);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void run() {
		//客户端-连接就可以写数据给数据库了
		new sendMessThread().start();
		super.run();
		try {
			//读Sock里面的数据
			InputStream stream = socket.getInputStream();
			byte[] buf = new byte[1024];
			int len = 0;
			while ((len = stream.read(buf)) != -1) {
				System.out.println(getdate() +" 服务器说:" +new String(buf,0,len,"UTF-8"));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	class sendMessThread extends Thread{
		@Override
		public void run() {
			super.run();
			//写操作
			Scanner scanner = null;
			OutputStream oStream = null;
			try {
				scanner = new Scanner(System.in);
				oStream = socket.getOutputStream();
				String inString = "";
				System.out.println("请输入:");
				do {
					inString = scanner.next();
					oStream.write((""+inString).getBytes("UTF-8"));
					oStream.flush();
				} while (!("bye").equals(inString));
			} catch (IOException e) {
				e.printStackTrace();
			}
			scanner.close();
			try {
				oStream.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	private String getdate() {
		Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String result = format.format(date);
        return result;
	}
	 //函数入口
    public static void main(String[] args) {
        //需要服务器的正确的IP地址和端口号
        Client clientTest=new Client("127.0.0.1", 8012);
        clientTest.start();
    }
}

[Java的Socket通信简单实例](https://www.cnblogs.com/luxd/p/10249898.html)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值