一、什么是socket?
  当两台计算机需要通信的时候,往往我们使用的都是TCP去实现的,但是并不会直接去操作TCP协议,通常是通过Socket进行tcp通信。Socket是操作系统提供给开发者的一个接口,通过它,就可以实现设备之间的通信。
 
二、TCP是如何通信的?
  TCP连接和断开分别会存在3次握手/4此握手的过程,并且在此过程中包含了发送数据的长度(接受数据的长度),无容置疑,这个过程是复杂的,这里我们不需要做深入的探讨。如果有兴趣,可以参考此文章,这里详细的解释了TCP通信的过程:
 
三、Socket消息的收发
  在Java中处理socket的方式有三种:
  1. 传统的io流方式(BIO模式),阻塞型;
  2. NIO的方式;
  3. AIO的方式;
  这里只介绍传统的IO流方式的tcp连接,即InputStream和OutputStream的方式读取和写入数据。对于长连接,通常情况可能我们如下做:

//<--------------服务端代码-------------------->
public class SocketReadLister implements Runnable { private final int tcpPort=9999;
private ServerSocket serverSocket; @Override
public void run() {
try {
serverSocket = new ServerSocket(this.tcpPort);
while(true){
Socket socket = serverSocket.accept();
//socket.setSoTimeout(5*1000);//设置读取数据超时时间为5s
new Thread(new SocketReadThread(socket)).start();
}
}catch (Exception e){
e.printStackTrace();
}
} public static void main(String[] args) throws Exception{
new Thread(new SocketReadLister()).start();
}
} public class SocketReadThread implements Runnable {
private Socket socket;
public SocketReadThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
byte[] data = new byte[1024];
try {
InputStream is=socket.getInputStream();
int length=0;
int num=is.available();
while((length = is.read(data)) != -1){
String result = new String(data);
System.out.println("数据available:"+num);
System.out.println("数据:"+result);
System.out.println("length:" + length);
}
System.out.print("结束数据读取:"+length);
}catch (SocketTimeoutException socketTimeoutException){
try {
Thread.sleep(2*1000);
}catch (Exception e) {
e.printStackTrace();
}
run();
} catch (Exception e){
e.printStackTrace();
try {
socket.close();
}catch (IOException io){
io.printStackTrace();
}
}
}
}
//<---------------------客户端代码---------------------------->
public class SocketClient implements Runnable {
private final int tcpPort=9999;
private Socket socket; @Override
public void run() {
String msg = "ab23567787hdhfhhfy"; byte[] byteMsg = msg.getBytes(); try {
socket = new Socket("127.0.0.1", 9999);
OutputStream out = socket.getOutputStream();
InputStream inputStream=socket.getInputStream(); out.write(byteMsg);
Thread.sleep(10*1000);
char[] chars=msg.toCharArray();
String str="";
/*out.flush();*/
for(int i=0;i<msg.length();i++) {
str=chars[i]+"-"+i;
out.write(str.getBytes());
Thread.sleep(1*1000);
}
byte[] bytes=new byte[8];
while(true) {
if(inputStream.available()>0) {
if(inputStream.read(bytes)!=-1) {
System.out.println(new String(bytes));
}
}
Thread.sleep(10*1000);
}
} catch (Exception e) {
e.printStackTrace();
try {
socket.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
} public static void main(String[] args) {
new Thread(new SocketClient()).start();
} }
  正如代码中所示,通常情况下我们在while循环中将is.read(data)) != -1作为判断依据,判断是否继续读取,这种情况下,确实可以将数据完整的读取,但是客户端没有传输数据的时候,read()方法开始阻塞,直到有数据时才继续执行后续代码,使得程序挂起。
  为什么会出现这种情况呢?
  在JDK中,关于read()的说明如下:当读取到流的末尾,没有可读数据的时候,read()方法将返回-1,如果没有数据,那么read()将会发生阻塞。因此,在读取文件流的情况下,这样是完全正确的,但是在网络编程的情况下,socket连接不会断开,那么InputStream的read()将永远不会返回-1,程序将读完数据后,继续循环读取然后发生阻塞。
  在InputStream中,提供了available();此方法是非阻塞的,通过它可以初步的判定socket流中是否有数据,并返回一个预估数据长度的值,但是请注意,这里是预估,并不是准确的计算出数据的长度,所以在JDK说明文档中,有提示使用该方法获取的值去声明 byte[]的长度,然后读取数据,这是错误的做法。这样在每次读取数据之前,都可以先判断一下流中是否存在数据,然后再读取,这样就可以避免阻塞造成程序的挂起。代码如下:

while(true){
if(is.available()>0){
is.read(data);
}
}
  说到read(),在InputStream中提供了3个read的重载方法:read()、read(byte[])、read(byte[],int offset,int len);后面两种读取方法都是基于 read()实现的,同样存在阻塞的特性,那么我们可以思考一下,假定byte[]的长度为1024,撇开while,拿read(byte[])一次性读取来说,当另一端发送的数据不足1024个字节时,为什么这个read(byte[])没有发生阻塞?
  关于这个问题,网上有帖子说,这跟InputStream的flush()有关,但经过测试,我不这么认为。我更加认同https://ketao1989.github.io/2017/03/29/java-server-in-action/中所说的那样,TCP握手期间,会传递数据的长度,当读取完数据,read()返回-1,即使此时没有读取到1024个字节数据,剩下的用0填充,这样就能很好的解释这个问题了。
  Socket既然时网络通讯用,那么由于各种原因,必然会有网络延迟,造成socket读取超时;socket读取超时时,其连接任然是有效的,因此在处理该异常时不需要关闭连接。以下是代码片段:

if (nRecv < nRecvNeed){
int nSize = 0;
wsaBuf=new byte[nRecvNeed-nRecv];
int readCount = 0; // 已经成功读取的字节的个数
try {
while (readCount < wsaBuf.length) {
//Thread.sleep(100);//读取之前先将线程休眠,避免循环时,程序占用CPU过高
try {
availableNum=inputStream.available();
if(availableNum>0){
readCount += inputStream.read(wsaBuf, readCount, (wsaBuf.length - readCount));//避免数据读取不完整
}
}catch (SocketTimeoutException timeOut){
System.out.println("读取超时,线程执行休眠操作,2秒后再读取");
Thread.sleep(2*1000);
}
}
}catch (Exception e){
System.out.println("读取数据异常");
e.printStackTrace();
close();//关闭socket连接
break;
}
nSize=wsaBuf.length;
nRecv+=nSize;
}
  另外,需要补充说明的是,socket.close()方法执行后,只能更改本端的连接状态,不能将该状态通知给对端,也就是说如果服务端或客户端一方执行了close(),另一端并不知道此时连接已经断开了。
  此外,以上代码还存在一个很严重的问题亟待解决,这也是在开发中容易忽视的地方——程序能正常运行,但CPU占用过高;原因如下:
  当readCount < wsaBuf.length,即数据还未读取完整时,线程会持续不断的从socket流中读取数据,由于这里使用了inputStream.available()来判断使用需要读取数据,当没有数据传输的时候,此处就变成了一个死循环,说到此处,原因就非常明了了,在计算机运行过程中无论他是单核还是多核,系统获取计算机资源(CPU等)都是按照时间分片的方式进行的,同一时间有且只有一个线程能获取到系统资源,所以当遇到死循环时,系统资源一直得不到释放,因此CPU会越来越高,解决的办法是在循环中对程序进行线程休眠一定时间。

初识Socket通讯编程(一)的更多相关文章

  1. 天地币:所用到的 Android Socket 通讯编程技术试验

    1.为了开发"天地币"这个Android手机项目,须要用到Socket编程. 2.天地币是一种类似于比特币的虚拟货币. 3.为了赚取CSDN的C币,须要写篇博客. 4.干脆将调试S ...

  2. 门禁系统socket通讯编程

    最近遇到一个socke udp协议通讯的需求,而且是16进制数据接收.这样在传输参数的时候老是提示参数错误,因为计算机是不能直接传输16进制的,会自行转换,所有以下代码非常完美的解决我的问题,同时也让 ...

  3. 高性能、高可用性Socket通讯库介绍 - 采用完成端口、历时多年调优!(附文件传输程序)

    前言 本人从事编程开发十余年,因为工作关系,很早就接触socket通讯编程.常言道:人在压力下,才可能出非凡的成果.我从事的几个项目都涉及到通讯,为我研究通讯提供了平台,也带来了动力.处理socket ...

  4. TCP网络编程(Socket通讯)

    TCP 网路编程: 1.TCP 三次握手: 第一次握手,客户端向服务器端发出连接请求,等待服务器确认. 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求. 第三次握手,客户端再次向服 ...

  5. [转] C#.Net Socket网络通讯编程总结

    1.理解socket1).Socket接口是TCP/IP网络的应用程序接口(API).Socket接口定义了许多函数和例程,程序员可以用它们来开发TCP/IP网络应用程序.Socket可以看成是网络通 ...

  6. python学习之路---day25( 网络编程基础和初识socket)

    基本网络知识和初识socket一:基本知识 网线:传输电信号 集线器:将所有连接到集线器的网络设备连通起来 交换机: 升级版的集线器 网卡:接受电信号 MAC地址:物理地址: 8C-88-4B-88- ...

  7. 初识Socket通信:基于TCP和UDP协议学习网络编程

    学习笔记: 1.基于TCP协议的Socket网络编程: (1)Socket类构造方法:在客户端和服务器端建立连接 Socket s = new Socket(hostName,port);以主机名和端 ...

  8. 网络通信 & 初识socket

    本节主要内容: 1.客户短\服务端架构 2.网络通信的流程 3.初识socket 一.客户端\服务端架构 客户端\服务端架构: 即Client/Server (C/S) 结构,是大家熟知的软件系统体系 ...

  9. python之Socket网络编程

    什么是网络? 网络是由节点和连线构成,表示诸多对象及其相互联系.在数学上,网络是一种图,一般认为专指加权图.网络除了数学定义外,还有具体的物理含义,即网络是从某种相同类型的实际问题中抽象出来的模型.在 ...

随机推荐

  1. 使用H5与webGL的3D 可视化地铁展示

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  2. 我学React Native开发的经历(一) 第一周学习,环境搭建及demo1,一个导航跳转页面

    大家好,这是跋涉者,wadereye,说来惭愧, 我是从2017年8月25日左右开始学习React Native的,因为时间不多, 在学习的过程中,感觉资料非常零散,要趟过的坑巨多,遇到的问题也非常多 ...

  3. 读取纯真IP数据库

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <stdli ...

  4. 《Python学习手册 第五版》 -第9章 元组、文件与其他核心类型

    本章的主要内容是介绍了元组和文件的使用,同时作为介绍数据类型的最后一个章节,本章也总结和复习了前面所介绍的核心数据类型,总结的部分在此不多介绍,因为前面章节都有,在此就重点介绍以下两点内容 1.元组 ...

  5. Redis(九):主从复制的设计与实现解析

    前面几篇我们已经完全理解了redis的基本功能的实现了. 但单靠基本功能实现,往往还是称不上优秀的项目的.毕竟,我们现在面对的都是复杂的环境,高并发的场景,大数据量的可能. 简而言之,现在的系统一般都 ...

  6. MSSqlServer访问远程数据库

    --第一部分(要点)--永久访问方式(需对访问远程数据库进行经常性操作)时设置链接数据库Exec sp_addlinkedserver 'MyLinkServer','','SQLOLEDB','远程 ...

  7. java虚拟机jvm启动后java代码层面发生了什么?

    java虚拟机jvm启动后java代码层面发生了什么? 0000 我想验证的事情 java代码在被编译后可以被jdk提供的java命令进行加载和运行, 在我们的程序被运行起来的时候,都发生了什么事情, ...

  8. Language Model

    在某次会上的语言模型的ppt.

  9. helm 错误the server has asked for the client to provide credentials

    一.造成错误的原因 不小心把helm的RBAC权限文件删除了.虽然重新apply了RBAC配置,但是已经无法使用helm install了. 二.解决方法 把运行的tiller的pod干掉,让他自动重 ...

  10. 前端添加复选框checkbox 提交到django后台处理

    根据前端勾选的复选框 提交勾选的数据到后台处理 che.html <!DOCTYPE html> <html lang="en"> <head> ...