一、什么是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的方式读取和写入数据。对于长连接,通常情况可能我们如下做:

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

  1. while(true){
  2. if(is.available()>0){
  3. is.read(data);
  4. }
  5. }
  说到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读取超时时,其连接任然是有效的,因此在处理该异常时不需要关闭连接。以下是代码片段:

  1. if (nRecv < nRecvNeed){
  2. int nSize = 0;
  3. wsaBuf=new byte[nRecvNeed-nRecv];
  4. int readCount = 0; // 已经成功读取的字节的个数
  5. try {
  6. while (readCount < wsaBuf.length) {
  7. //Thread.sleep(100);//读取之前先将线程休眠,避免循环时,程序占用CPU过高
  8. try {
  9. availableNum=inputStream.available();
  10. if(availableNum>0){
  11. readCount += inputStream.read(wsaBuf, readCount, (wsaBuf.length - readCount));//避免数据读取不完整
  12. }
  13. }catch (SocketTimeoutException timeOut){
  14. System.out.println("读取超时,线程执行休眠操作,2秒后再读取");
  15. Thread.sleep(2*1000);
  16. }
  17. }
  18. }catch (Exception e){
  19. System.out.println("读取数据异常");
  20. e.printStackTrace();
  21. close();//关闭socket连接
  22. break;
  23. }
  24. nSize=wsaBuf.length;
  25. nRecv+=nSize;
  26. }
  另外,需要补充说明的是,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. DRF框架之Serializer序列化器的反序列化操作

    昨天,我们完成了Serializer序列化器的反序列化操作,那么今天我们就来学习Serializer序列化器的最后一点知识,反序列化操作. 首先,我们定要明确什么是反序列化操作? 反序列化操作:JOS ...

  2. 全国疫情精准定点动态更新(.net core)

    前言 疫情远比我们在年初想的发展迅速,在过年前还计划着可以亲戚聚聚,结果都泡汤了,开始了自家游. 在初三的时候,看到那个丁香医生,觉得不够详细,比如说我想看下周边城市的疫情情况,但是我地理不好,根本不 ...

  3. C++不同类中的特征标相同的同名函数

    转载请注明出处,版权归作者所有 lyzaily@126.com yanzhong.lee        作者按:                   从这篇文章中,我们主要会认识到一下几点:      ...

  4. jsp操作mysql样例

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  5. LIBCMTD.lib与libcpmtd冲突的解决方法。

    error: 1>uafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int) ...

  6. .NET Core WebAPI post参数传递时后端的接收方式

    .NET Core WebAPI post参数传递时后端的接收方式 实体类 dynamic动态类型 JObject参数 单值参数(字符串参数) A.前端Post请求代码 $.ajax({ url: & ...

  7. socket实现文件上传(客户端向服务器端上传照片示例)

    本示例在对socket有了基本了解之后,可以实现基本的文件上传.首先先介绍一下目录结构,server_data文件夹是用来存放客户端上传的文件,client_data是模拟客户端文件夹(目的是为了测试 ...

  8. 蓝桥杯2015年省赛C/C++大学B组

    1. 奖券数目 有些人很迷信数字,比如带“4”的数字,认为和“死”谐音,就觉得不吉利.虽然这些说法纯属无稽之谈,但有时还要迎合大众的需求.某抽奖活动的奖券号码是5位数(10000-99999),要求其 ...

  9. Oracle 12C In-Memory特性研究

    Oracle 12C In-Memory特性研究一.Oracle In-Memory1.1 In-Memory 开启方法1.2 开启与关闭IM column store1.3 inmemory优先级调 ...

  10. 一文带你了解 C# DLR 的世界

    一文带你了解 C# DLR 的世界 在很久之前,我写了一片文章dynamic结合匿名类型 匿名对象传参,里面我以为DLR内部是用反射实现的.因为那时候是心中想当然的认为只有反射能够在运行时解析对象的成 ...