新学期生活开始一段时间了,要继续学习一些新的技术(这里指socket /doge),目标是尝试完成一个在线即时聊天的小程序(尽量不咕)。会更新一系列socket编程的技术文章,欢迎关注交流~

那么千里之行,始于足下,就从这socket编程开始说起吧。

socket概念

首先一个问题,什么是socket编程?他有个中文名称叫做“套接字编程”。这个词不直观,也比较晦涩,很容易让人产生误解。我们来看一下百度百科的定义:

简介:socket一般指套接字。所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制

大致可以明白其含义是在网络上,两台主机的进程实现通信的编程技术。
但是描述还是有些不够形象,那么其实从英文原意的角度来看,socket的翻译有“插座”的意思

这么看,将两个要相互通信的主机比作是插座和插头,发起的一方是插头,接受的一方是插座,二者的ip和端口对接上之后即可相互传输信息。这个socket可以说是上接相应的应用程序,下接通信协议栈,保证信息快速完整的传递。

这个比喻虽然不够恰当,但是也足够形象的体现socket编程的含义。

确定一个socket的标识有两个,分别是:IP和端口
表示方法是点分十进制的IP地址和端口号,中间使用冒号隔开,例如:127.0.0.1:8888就是电脑本机的8888端口

Socket的工作流程

一次socket的连接与通信大致可分为以下的步骤:

  • 服务端:启动程序并开始监听连接
  • 客户端:启动程序并请求连接
  • 客户端:建立连接后像服务端发送信息
  • 服务端:建立连接并接受客户端发送的信息
  • 服务端:完成信息接受后向客户端回复信息
  • 客户端:完成信息传输后接收服务端的回复信息
  • 客户端:会话完毕,关闭socket
  • 服务端:会话完毕,关闭socket并选择关闭ServerSocket或者继续监听

当然,我们也可以选择在服务端采用多线程的形式来完成多个客户端请求的情况,从而避免服务端被一个客户端霸占,后序客户端排队的情况。这样的工作流程在刚刚的基础上可以被表示为:

  • 服务端:启动程序并开始监听连接
  • 客户端:启动程序并请求连接
  • 客户端:建立连接后像服务端发送信息
  • 服务端:确认连接并开启一个线程用来处理通信
  • 服务端:建立连接并接受客户端发送的信息
  • 服务端:完成信息接受后向客户端回复信息
  • 客户端:完成信息传输后接收服务端的回复信息
  • 客户端:会话完毕,关闭socket
  • 服务端:会话完毕,关闭socket并选择关闭ServerSocket或者继续监听

了解了大致的工作流程,下面就来了解一下socket的连接和通信

Socket连接

那么使用Java如何实现socket编程呢?

其实就是需要分别实现客户端(Client)和服务端(Server)的socket,即服务端开放“插座”等待匹配,客户端使用“插头”匹配服务端的插头。

最核心需要用到两个类,他们都在java.net包中:

  • Socket类,位于客户端(client)使用的“插头”,初始化时需要指定连接对象的IP和端口。
  • ServerSocket类,服务端(Server)的“插座”,初始化时需要制定开放的端口(服务器本机IP)

服务端需要使用ServerSocket中一个重要的方法来获取客户端的连接:accept()

这个方法可以获得客户端的socket对象,服务端使用这个对象中的io流与客户端进行通信,同样的,客户端使用socket中的io流与服务端进行通信。

上面这段描述需要注意的有两点:

  1. 客户端与服务端通过socket对象来通信,客户端创建socket,服务端使用ServerSocket对象接收并获取socket
  2. 客户端与服务端必须通过socket对象提供的io流进行通信,流中的内容会通过网络相互传输。

下面就来简单的实现一个客户端与服务端的对接:

//客户端
public class SocketClient {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端程序~~~");System.out.println("我创建了一个socket");Socket client = new Socket("127.0.0.1",8888);}
}
//服务端
public class SocketServer {public static void main(String[] args) throws IOException {System.out.println("服务端程序~~~");System.out.println("我创建了一个ServerSocket");ServerSocket server = new ServerSocket(8888);System.out.println("开始接受socket匹配");Socket client = server.accept();System.out.println("接收到了一个socket");}
}

此时,运行服务端:

发现程序并没有执行后面的语句输出提示。这个原因在于:accept()方法在等待连接时会使程序产生阻塞,不在往下执行,直到接受到一个连接,并且返回一个客户端的Socket对象实例。

那么接着就运行客户端,让他们相互匹配:

此时,服务端就接收到了一个socket连接,执行了后续的语句。
可以看出,在客户端,连接是在创建socket对象时就发起的,并不需要调用任何方法。
在服务端,需要使用accept方法来监听连接 ,当没有socket连接时,程序就会阻塞。

Socket通信

在建立了连接之后,服务端和客户端就要开始通信了。

上文提到过,两方的通信是通过字节流来完成的,而且这个字节流必须是socket对象提供的io流。

这个io流需要通过socket的getInputStreamgetOutputStream两个方法来获取。

在使用io流进行通信的过程中,有一个需要注意的地方,就是服务端的输出流对接的是客户端的输出流,而客户端的输入流对接的是服务端的输出流。

用一张图来表示:

其实也很符合直觉的,但是在编写程序过程中,尤其是同时编写客户端和服务端的程序时,这两个流的方向是容易搞混的,需要注意一下。

单向传递消息

现在就先从客户端向服务端传递一条消息:

/*客户端*/
public class SocketClient {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端程序~~~");System.out.println("我创建了一个socket");Socket client = new Socket("127.0.0.1",8888);System.out.println("正在行服务端发送消息");/*向服务端发送消息*/client.getOutputStream().write("你好服务端,这里是客户端".getBytes());System.out.println("像服务端发送消息完毕");/*会话结束*/client.close();}
}
/*服务端*/
public class SocketServer {public static void main(String[] args) throws IOException {System.out.println("服务端程序~~~");System.out.println("我创建了一个ServerSocket");ServerSocket server = new ServerSocket(8888);    //创建一个服务端soscketSystem.out.println("开始接受socket匹配");Socket client = server.accept();System.out.println("接收到了一个socket");InputStream is = client.getInputStream();   //输入流byte[] buffer = new byte[1024];               //缓冲int len = 0;                               //每次读取的长度(正常情况下是1024,最后一次可能不是1024,如果传输结束,返回-1)StringBuilder sb = new StringBuilder();       //构建读取的消息while((len = is.read(buffer)) != -1){        //接收客户端的消息sb.append(new String(buffer,0,len));}System.out.println("收到客户端消息:" + sb.toString());client.close();//关闭连接server.close();//关闭服务端}
}

结果如下:

双向传递消息

想让消息有来有回,那么服务端就不能在接收后直接关闭连接,而是回复一条消息,那么这条消息就应该是通过服务端的输出流发送,在客户端的输入流接收。

/*客户端*/
public class SocketClient {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端程序~~~");System.out.println("我创建了一个socket");Socket client = new Socket("127.0.0.1",8888);System.out.println("正在行服务端发送消息");/*向服务端发送消息*/client.getOutputStream().write("你好服务端,这里是客户端".getBytes());System.out.println("像服务端发送消息完毕");System.out.println("正在接收服务端回复");InputStream is = client.getInputStream(); //输入流byte[] buffer = new byte[1024];               //缓冲int len = 0;                               //每次读取的长度(正常情况下是1024,最后一次可能不是1024,如果传输结束,返回-1)StringBuilder sb = new StringBuilder();       //构建读取的消息while((len = is.read(buffer)) != -1) {       //接收服务端的消息      sb.append(new String(buffer,0,len));}System.out.println("接收到服务端消息:" + sb.toString());/*会话结束*/client.close();}
}
/*服务端*/
public class SocketServer {public static void main(String[] args) throws IOException {System.out.println("服务端程序~~~");System.out.println("我创建了一个ServerSocket");ServerSocket server = new ServerSocket(8888);System.out.println("开始接受socket匹配");Socket client = server.accept();System.out.println("接收到了一个socket");InputStream is = client.getInputStream();   //输入流byte[] buffer = new byte[1024];               //缓冲int len = 0;                               //每次读取的长度(正常情况下是1024,最后一次可能不是1024,如果传输结束,返回-1)StringBuilder sb = new StringBuilder();       //构建读取的消息while((len = is.read(buffer)) != -1){        //接收客户端的消息sb.append(new String(buffer,0,len));}System.out.println("收到客户端消息:" + sb.toString());System.out.println("正在回复~~~");client.getOutputStream().write("这里是服务端,收到消息,谢谢".getBytes());    //回复客户端client.close();//关闭连接server.close();//关闭服务端}
}

运行程序发现:

客户端并没有接收到服务端的回复,而服务端停滞在了接收客户端信息的地方。

流的阻塞问题

实际上,问题就出现在客户端向服务端传输信息这一过程中。在服务端看来,虽然已经获得了所有客户端发来的字节,但是它并不能确定客户端是否要继续发送信息,因此输入流就卡在那里,形成了阻塞。

解决这个问题,一个粗暴地方式是在客户端直接关闭输出流,当然不是调用输出流的关闭方法,而是调用socket的shutdownOutput方法。这个方式有个缺点,就是在关闭输出流之后,将无法再次输出。

我们使用这个语句来完善刚刚客户端的程序

public class SocketClient {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端程序~~~");System.out.println("我创建了一个socket");Socket client = new Socket("127.0.0.1",8888);System.out.println("正在行服务端发送消息");/*向服务端发送消息*/client.getOutputStream().write("你好服务端,这里是客户端".getBytes());System.out.println("像服务端发送消息完毕");System.out.println("正在接收服务端回复");client.shutdownOutput();//关闭输出!!!!!InputStream is = client.getInputStream();    //输入流byte[] buffer = new byte[1024];               //缓冲int len = 0;                               //每次读取的长度(正常情况下是1024,最后一次可能不是1024,如果传输结束,返回-1)StringBuilder sb = new StringBuilder();       //构建读取的消息while((len = is.read(buffer)) != -1) {       //接收服务端的消息      sb.append(new String(buffer,0,len));}System.out.println("接收到服务端消息:" + sb.toString());/*会话结束*/client.close();}
}

这回两端的通信就可以顺利进行了

上传和下载文件

传输文件与传输消息没有本质区别,在计算机眼中他们都是一样的二进制字节流。

不同点在于,一个数据源格式是字符串而另一个也是输入流。

我们要做的,就是将这些流进行对接,还是画个图来表示:

由于上传和下载这两个过程很类似,所以这里就仅实现一个文件上传的操作,进行演示。

在刚刚的通信基础上稍作修改:

/*客户端*/
public class TCPFileClient {public static void main(String[] args) throws UnknownHostException, IOException {Scanner scan = new Scanner(System.in);System.out.println("请输入要传递的文件全路径:");String filename = scan.next();FileInputStream fin = new FileInputStream(filename);//文件输出流,指向待传输文件System.out.println("正在尝试连接服务器");Socket client = new Socket("10.151.140.39",8888);System.out.println("服务器连接成功");OutputStream os = client.getOutputStream();       byte[] bytes = new byte[1024];int len = 0;long cnt = 0;//统计发送的字节数kbSystem.out.println("文件开始传输");while((len = fin.read(bytes)) != -1) {cnt++;if(cnt % (1 << 10) == 0) {System.out.println("已传输" + cnt / 1024 + "m");}os.write(bytes,0,len);}System.out.println("文件传输完成");InputStream is = client.getInputStream();client.shutdownOutput();StringBuffer sb = new StringBuffer();System.out.println("正在接受回复");while((len = is.read(bytes)) != -1) {sb.append(new String(bytes,0,len));}System.out.println("接收到回复:'" + sb + "'");fin.close();client.close();}
}
/*服务端*/
public class TCPFileServer {public static void main(String[] args) throws IOException {ServerSocket server = new ServerSocket(8888);System.out.println("正在检测目标文件路径是否存在");File file = new File("E:\\server");if(!file.exists()) {System.out.println("已创建不存在的文件夹");file.mkdirs();}else {System.out.println("检测成功,目标文件夹存在");}FileOutputStream fos;Socket client;int name = 0;while(true) {System.out.println("服务器正在等待接收文件~~");fos = new FileOutputStream(file + "\\" + name + ".rar");//文件输出流,指向硬盘存储区域client = server.accept();InputStream is = client.getInputStream();byte[] bytes = new byte[1024];int len = 0;long cnt = 0;    //统计接收字节数kbSystem.out.println("正在进行文件传输");while((len = is.read(bytes)) != -1) {cnt++;if(cnt % (1 << 10) == 0) {System.out.println("已接收" + cnt / 1024 + "m");}fos.write(bytes,0,len);}System.out.println("文件传输成功,正在回话");client.getOutputStream().write("收到文件,谢谢".getBytes());fos.close();client.close();if(name == 50)break;}server.close();}
}

运行一下:

文件传输成功!!
由于服务端使用了循环,所以我们可以使得服务端一直处在接受文件的状态。


参考资料

  • Java 网络编程 之 socket 的用法与实现
  • 【JAVA开发】Socket套接字网络编程

【Java高级】初探socket编程 ——JavaSocket连接与简单通信相关推荐

  1. Java基于socket编程实现局域网内简单通信

    运行客户端程序将创建一个客户端套接字,并与指定的服务器建立连接,接收了服务端发来的消息后关闭连接.服务端启动后会循环接收客户端连接,在接收到连接后,向该客户端发送 "Hello World! ...

  2. Java TCP/IP Socket 编程 笔记

    http://jimmee.iteye.com/blog/617110 http://jimmee.iteye.com/category/93740 Java TCP/IP Socket 编程 笔记( ...

  3. asp.core api 通过socket和服务器通信发送udp_读懂Java中的Socket编程

    Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的S ...

  4. 读懂Java中的Socket编程

    Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的S ...

  5. Socket编程Http下载的简单实现

    Socket编程Http下载的简单实现 - Mr.DejaVu - 博客园 <C/C++> Socket编程Http下载的简单实现 下载原理: 网上介绍很多,就是按照Http协议,使用So ...

  6. Java TCP/UDP socket 编程流程总结

    最近正好学习了一点用java socket编程的东西.感觉整体的流程虽然不是很繁琐,但是也值得好好总结一下. Socket Socket可以说是一种针对网络的抽象,应用通过它可以来针对网络读写数据.就 ...

  7. java 与 .net socket_java.net.Socket/java.net.ServerSocket-TCP Socket编程

    TCP 的 Java 支持 协议相当于相互通信的程序间达成的一种约定,它规定了分组报文的结构.交换方式.包含的意义以及怎样对报文所包含的信息进行解析,TCP/IP 协议族有 IP 协议.TCP 协议和 ...

  8. java练气期(3)----java高级(网络编程)

    什么是java的网络编程 Socket 编程套接字使用TCP提供了两台计算机之间的通信机制. 客户端程序创建一个套接字,并尝试连接服务器的套接字. 当连接建立时,服务器会创建一个 Socket 对象. ...

  9. java如何实现Socket的长连接和短连接

    讨论Socket必讨论长连接和短连接 一.长连接和短连接的概念 1.长连接与短连接的概念:前者是整个通讯过程,客户端和服务端只用一个Socket对象,长期保持Socket的连接:后者是每次请求,都新建 ...

最新文章

  1. OpenGL ES 详解纹理生成和纹理映射步骤以及函数
  2. android控件拖动,移动、解决父布局重绘时控件回到原点
  3. cad加载插件快捷键命令_cad自动加载lsp插件,这一种方法你肯定没用过!
  4. 使用BootStrap编写网页,如何设置全屏页面背景?
  5. SQL模糊查询 LIKE
  6. 实现连麦_微信年底放了个大招,视频号重磅升级,打赏直播连麦美颜抽奖齐上...
  7. 程序员面试金典 - 面试题 01.03. URL化(字符串)
  8. 单片机按键使用程序 (51单片机)
  9. 重构代码 —— 函数即变量(Replace temp with Query)
  10. Eclipse的Servers视图中无法添加Tomcat6/Tomcat7
  11. Linux通过终端查看日志命令
  12. 热力图pycharm
  13. 复杂问题的知识问答技术介绍
  14. 商业世界的五大基础定律
  15. java程序员3-5年职业规划,附源代码
  16. 一、计算机核心组成及CPU核心组成
  17. 国内怎么使用chatGpt
  18. Flutter网络请求方式总结
  19. Syslog-ng3.5 mysql 日志服务器
  20. 成都天瑞地安:零基础学Java最快捷的7个计划

热门文章

  1. 编译原理(一)词法分析器
  2. [创业说]如何更好用业余时间做互联网创业?
  3. 神武手游宠物天资讲解与算法
  4. 推荐几本免费的Linux电子书
  5. IDES翻译—利用采购订单进行跨公司库存转储(小白篇)
  6. 骁龙660鸿蒙系统,骁龙665对比骁龙660:差距无法接受
  7. 如何使用键盘快捷键上下移动Jupyter笔记本单元格
  8. 技术部门开会注意事项
  9. 零经验转行互联网,你需要做对这三件事,带你进入高薪产业,成就未来
  10. 2022年造价员装饰装修考试模拟试题卷及答案