聊到socket编程,我脑子第一反应就是activeMq、及JMS的其他应用底层都是基于socket实现的,当然我们日常用的QQ、微信等通讯工具也是基于socket实现的(其实我们工作中常用到的log4j底层也用到了socket),由于socket应用面如此广泛、所以我今天翻阅了大量网上资料、并自己写了简单的例子进行学习,下面聊聊我的学习笔记和心得。
当然在今天学习之前我就有使用过socket进行项目编码的经验了,记得当初在给某一线城市房管局做项目时,档案馆的档案都是基于PDA服务器进行存管的,PDA对外提供了socket服务,所以当初我就用的socket客户端代码去调用PDA服务发起调档的请求。
下面是我11年写的代码,现在贴出来,因为当时档案馆库房很多,每个库房都有socket服务器,所以代码需要通过判断很多规则动态调用不同库房的socket服务器、并与之通讯,发送调档请求。
public boolean socketSendMessage2PDA(String daidUnionStr, int type) { log.info("调档发送时给PDA发送调档信息,发送的档案ID字符串为:" + daidUnionStr); if (daidUnionStr != null) { String[] daidList = daidUnionStr.split(":"); for (String daid : daidList) { TpaDast tpaDast = (TpaDast) this.findEntityById("TpaDast", Long.valueOf(daid)); TpaDawzxx dawzxx = tpaDast.getDawzxx(); List<TpaDaglxxb> glxxbList = this.getDaglxxbListByDabh(tpaDast.getDacode()); if (dawzxx != null) { ConfigService configService = (ConfigService) Framework.getEngine().getContainer().getComponent("configService"); String hostIP = "", cqrxm = "未知"; StringBuffer addressSbf = new StringBuffer(); if (glxxbList != null && glxxbList.size() > 0) cqrxm = glxxbList.get(0).getCqrxm(); //根据config.properties文件中配置的档案的PDA服务地址 //1号库房PDA服务器地址 if (dawzxx.getFh() == 1l || dawzxx.getFh().equals(1l)) hostIP = configService.getString("server1_Ip"); //2号库房PDA服务器地址 if (dawzxx.getFh() == 2l || dawzxx.getFh().equals(2l)) hostIP = configService.getString("server2_Ip"); //3号库房PDA服务器地址 if (dawzxx.getFh() == 3l || dawzxx.getFh().equals(3l)) hostIP = configService.getString("server3_Ip"); //4号库房PDA服务器地址 if (dawzxx.getFh() == 4l || dawzxx.getFh().equals(4l)) hostIP = configService.getString("server4_Ip"); //5号库房PDA服务器地址 if (dawzxx.getFh() == 5l || dawzxx.getFh().equals(5l)) hostIP = configService.getString("server5_Ip"); log.info("调用PDA服务器的IP地址为:" + hostIP); //动态拼接PDA能够识别的位置信息 if (dawzxx.getFh() < 10) addressSbf.append("0").append(dawzxx.getFh()); else addressSbf.append(dawzxx.getFh()); addressSbf.append(dawzxx.getQh()); if (dawzxx.getLh() < 10) addressSbf.append("0").append(dawzxx.getLh()); else addressSbf.append(dawzxx.getLh()); if (dawzxx.getMh() == 1 || dawzxx.equals(1)) addressSbf.append("L"); else if (dawzxx.getMh() == 2 || dawzxx.equals(2l)) addressSbf.append("R"); if (dawzxx.getJh() < 10) addressSbf.append("0").append(dawzxx.getJh()); else addressSbf.append(dawzxx.getJh()); if (dawzxx.getGh() < 10) addressSbf.append("0").append(dawzxx.getGh()); else addressSbf.append(dawzxx.getGh()); log.info("动态PDA库房地址为:" + addressSbf.toString()); try { //Socket发送给PDA服务器代码 Socket socket = new Socket();//需要动态根据位置发送服务 SocketAddress socketAddress = new InetSocketAddress(hostIP, 1001); socket.connect(socketAddress, 3000);//连接三秒钟超时设置 OutputStream outputStream = socket.getOutputStream(); StringBuffer sbf = new StringBuffer(); sbf.append("<?xml version='1.0' encoding='gb2312'?>"); sbf.append("<XMLPackage>"); sbf.append("<Command CmdType='2'/>"); sbf.append("<data>"); sbf.append("<row Code='" + tpaDast.getDacode() + "' Name='" + cqrxm + "' Address='" + addressSbf.toString() + "' Archives_id='" + tpaDast.getId() + "' State='" + type + "'/>"); //还档state变成2,其它都是一样 sbf.append("</data>"); sbf.append("</XMLPackage>"); log.info("发送" + tpaDast.getDacode() + "信息为:\n" + sbf.toString()); outputStream.write(sbf.toString().getBytes()); log.info("Socket主机端连接信息为:\n" + socket); InputStream is = socket.getInputStream(); byte[] bytes = new byte[1024]; int n = is.read(bytes); log.info(tpaDast.getDacode() + "发送给PDA系统,回调信息为:\n" + new String(bytes, 0, n)); outputStream.flush(); outputStream.close(); is.close(); } catch (Exception e) { log.info("调档发送PDA服务信息错误,调档失败,原因是PDA服务器没有开启!具体信息如下:\n" + e.getMessage()); return false; } } } } return true; }
时至今日我又翻阅了相关资料系统的进行了学习,情况总结如下:
概念:
Socket是一组编程接口(API), 是对TCP/IP协议的封装和应用。介于传输层和应用层,大致驻留在 OSI 模型的会话层,向应用层提供统一的编程接口。应用层不必了解TCP/IP协议细节。直接通过对Socket接口函数的调用完成数据在IP网络的传输。
分类:
基于传输层差异,可以将Socket分为4种类型:
(1)基于TCP的Socket:提供给应用层可靠的流式数据服务,使用TCP的Socket应用程序协议:BGP,HTTP,FTP,TELNET等。优点:基于数据传输的可靠性。
(2)基于UDP的Socket:适用于数据传输可靠性要求不高的场合。基于UDP的Socket应用程序或协议有:RIP,SNMP,L2TP等。
(3)基于RawIp的Socket:非连接,不可靠的数据传输。特点:能使应用程序直接访问网络层。基于RawIp的Socket有ping ,tracert,ospf等。
(4)基于链路层的Socket。为IS-IS协议提供的Socket接口。使IS-IS协议可通过Socket直接访问链路层。非连接,不可靠通信服务。
实践:
我主要是针对socket实现方式TPC/ID和UPD的两种实现方式进行了学习,先来看看他们的通讯方式链接过程图(图片来源于网络):
TCP/IP链接过程
UPD链接过程:
对比两者区别:
TCP:Transimission Control Protocol传输控制协议。
UPD:User Datagram Protocol用户数据包协议。
两者都属于OSI模型中的第四层——传输层的协议。
两者相比:
TCP协议面向连接,UDP协议面向非连接;(链接)
TCP协议传输速度慢,UDP协议传输速度快;(速度)
TCP有丢包重传机制,UDP没有;(重传)
TCP协议保证数据正确性,UDP协议可能丢包;(正确性)
TCP适合传递大量数据,UPD适合传递少量数据。(数据量)
说了基本概念后,我用代码编写了主机、客户端进行socket通讯的代码,代码如下:
服务端代码socketServer.java类:
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; /** * Created by 峰 on 2017/4/6. * 服务器类 */ public class socketServer { private int port = 8189;// 默认服务器端口 public socketServer() { } // 创建指定端口的服务器 public socketServer(int port) { this.port = port; } // 提供服务 public void service() { try {// 建立服务器连接 ServerSocket server = new ServerSocket(port); // 等待客户连接 Socket socket = server.accept(); try { // 读取客户端传过来信息的DataInputStream DataInputStream in = new DataInputStream(socket .getInputStream()); // 向客户端发送信息的DataOutputStream DataOutputStream out = new DataOutputStream(socket .getOutputStream()); // 获取控制台输入的Scanner Scanner scanner = new Scanner(System.in); while (true) { // 读取来自客户端的信息 String accpet = in.readUTF(); System.out.println(accpet); String send = scanner.nextLine(); System.out.println("服务器:" + send); // 把服务器端的输入发给客户端 out.writeUTF("服务器:" + send); } } finally {// 建立连接失败的话不会执行socket.close(); socket.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new socketServer().service(); } }
客户端代码socketClient.java类:
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.util.Scanner; /** * Created by 峰 on 2017/4/9. * 客户端类 */ public class socketClient { private String host = "localhost";// 默认连接到本机 private int port = 8189;// 默认连接到端口8189 public socketClient() { } // 连接到指定的主机和端口 public socketClient(String host, int port) { this.host = host; this.port = port; } public void chat() { try { // 连接到服务器 Socket socket = new Socket(host, port); try { // 读取服务器端传过来信息的DataInputStream DataInputStream in = new DataInputStream(socket .getInputStream()); // 向服务器端发送信息的DataOutputStream DataOutputStream out = new DataOutputStream(socket .getOutputStream()); // 装饰标准输入流,用于从控制台输入 Scanner scanner = new Scanner(System.in); while (true) { String send = scanner.nextLine(); System.out.println("客户端:" + send); // 把从控制台得到的信息传送给服务器 out.writeUTF("客户端:" + send); // 读取来自服务器的信息 String accpet = in.readUTF(); System.out.println(accpet); } } finally { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new socketClient().chat(); } }
测试结果:
服务端信息截图:
客户端信息截图:
通讯原理及过程图解(来源于网络):
附言:因为时间问题、我这里只研究了java.net下的socket,其实还有别的实现方式,比如linux下面也有socket,参考 http://blog.youkuaiyun.com/hguisu/article/details/7445768/
现在网络上也有很多很好的socket学习文章,具体可以参考下面链接(当然我也都是看过了的)
http://acm.tzc.edu.cn/acmhome/projectList.do?method=projectNewsDetail&nid=2
http://blog.youkuaiyun.com/hguisu/article/details/7445768/
http://www.cnblogs.com/kinghitomi/archive/2012/01/19/2327424.html