概述

Socket编程是指编写在多台计算机上执行的程序,其中的设备都使用网络相互连接

Socket常用的通信协议有UDP和TCP,本文主要介绍通过TCP/IP网络协议进行Socket编程

Socket通信流程

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将绑定在 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务器端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭;

服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket

简单示例

Socket是网络上不同计算机运行的两个程序之间双向通信链路的一个端点。Socket需要绑定端口号,一遍传输层可以标识数据要发送到的应用程序

服务端

服务端会用到两个socket,一个叫作监听 socket,一个叫作已完成连接 socket

目前的服务器不能保证通信的连续性,它会在发送完消息后关闭连接

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;// socket服务端
public class Server {private ServerSocket serverSocket;private Socket clientSocket;private PrintWriter out;private BufferedReader in;public void start(int port){try{// (监听socket)// 绑定指定端口,使服务器的Socket在指定端口号上运行serverSocket = new ServerSocket(port);// (已连接socket)// 服务器遇到accept进入阻塞,等待客户端发出连接// 连接成功后,服务器将获得绑定到同一本地端口6666的新socket,用于传输数据clientSocket = serverSocket.accept();// 输出流,可发送消息到客户端out = new PrintWriter(clientSocket.getOutputStream(), true);// 输入流,可接收客户端消息in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));String greeting = in.readLine();if(greeting.equals("hello server")){out.println("hello client");}else{out.println("unrecognised greeting");}}catch (IOException e){e.printStackTrace();}}public void stop(){try{in.close();out.close();clientSocket.close();serverSocket.close();}catch (IOException e){e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();// 开启服务器server.start(6666);}}

客户端

客户端只需要创建一个socket以保持连接,最终客户端的输入流连接到服务端的输出流,服务器的输入流连接到客户端的输出流

import com.sun.javafx.iio.ios.IosDescriptor;import java.io.*;
import java.net.Socket;public class Client {private Socket clientSocket;private PrintWriter out;private BufferedReader in;public void startConnection(String ip, int port){try{// 客户端需要知道服务端的ip和其正在监听的端口号,才能发起连接// 服务器接收连接后创建客户端socketclientSocket = new Socket(ip, port);// 获取socket的输入输出流,以便与服务端通信out = new PrintWriter(clientSocket.getOutputStream(), true);in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));}catch (IOException e){e.printStackTrace();}}public String sendMessage(String msg){// 客户端输出请求消息out.println(msg);String resp = null;try {// 客户端接收响应消息resp = in.readLine();} catch (IOException e) {e.printStackTrace();}return resp;}public void stopConnection(){try {in.close();out.close();clientSocket.close();}catch (IOException e){e.printStackTrace();}}}

测试

先手动启动服务端,然后运行以上测试案例即可完成一次连接和一次消息发送

import org.junit.Test;
public class HelloTest {@Testpublic void hello(){Client client = new Client();// 客户端对服务端发起连接client.startConnection("127.0.0.1", 6666);// 客户端发送消息到服务端并接收响应结果String response = client.sendMessage("hello server");System.out.println(response);}}

若启动服务端时出现以下报错,是出现了端口占用,可以修改端口也可以关闭占用端口的进程

Windows下使用命令行关闭占用端口的进程

// 参看端口号含6666的条目
netstat -ano|findstr "6666"// 根据pid查询对应的应用程序
tasklist|findstr "1828"// 杀死进程
taskkill /f /pid 1828

持续连接优化

在前一个案例中,服务器会阻塞直到客户端连接它。在单个消息后,连接就会关闭,客户端和服务端无法持续沟通,因此仅仅会出现在ping请求中

如果要实现一个聊天服务器,客户端和服务端之间就需要连续的来回通信

服务端

在优化中我在服务端创建一个while循环来连续观察传来消息的服务器输入流

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;public class EchoServer {private ServerSocket serverSocket;private Socket clientSocket;private PrintWriter out;private BufferedReader in;public void start(int port){try{// (监听socket)// 绑定指定端口,使服务器的Socket在指定端口号上运行serverSocket = new ServerSocket(port);// (已连接socket)// 服务器遇到accept进入阻塞,等待客户端发出连接// 连接成功后,服务器将获得绑定到同一本地端口4444的新socket,用于传输数据clientSocket = serverSocket.accept();// 输出流,可发送消息到客户端out = new PrintWriter(clientSocket.getOutputStream(), true);// 输入流,可接收客户端消息in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));String inputLine;// while循环连续观察从客户端传来消息的服务器输入流// 直到读取到exit断开连接while ((inputLine = in.readLine()) != null){if(inputLine.equals("exit")){out.println("goodbye");break;}out.println(inputLine.replace("req", "res"));}}catch (IOException e){e.printStackTrace();}}public void stop(){try{in.close();out.close();clientSocket.close();serverSocket.close();}catch (IOException e){e.printStackTrace();}}public static void main(String[] args) {EchoServer server = new EchoServer();// 开启服务器server.start(4444);}}

客户端

客户端不需要进行优化修改,这里为了方区分创建一个新的类EchoClient

import java.io.*;
import java.net.Socket;public class EchoClient {private Socket clientSocket;private PrintWriter out;private BufferedReader in;public void startConnection(String ip, int port){try{// 客户端需要知道服务端的ip和其正在监听的端口号,才能发起连接// 服务器接收连接后创建客户端socketclientSocket = new Socket(ip, port);// 获取socket的输入输出流,以便与服务端通信out = new PrintWriter(clientSocket.getOutputStream(), true);in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));}catch (IOException e){e.printStackTrace();}}public String sendMessage(String msg){// 客户端输出请求消息out.println(msg);String resp = null;try {// 客户端接收响应消息resp = in.readLine();} catch (IOException e) {e.printStackTrace();}return resp;}public void stopConnection(){try {in.close();out.close();clientSocket.close();}catch (IOException e){e.printStackTrace();}}}

测试

在初始示例中,我们只在服务器关闭连接之前进行一次通信。现在,我们发送一个终止信号,以便在会话完成时告诉服务器,以此关闭服务器的socket进程

开启EchoServer服务器运行以下测试案例

import org.junit.After;
import org.junit.Before;
import org.junit.Test;public class EchoTest {private EchoClient client = new EchoClient();@Beforepublic void setup(){client.startConnection("127.0.0.1", 4444);}@Afterpublic void tearDown(){client.stopConnection();}@Testpublic void echo(){String resp1 = client.sendMessage("req:hello");String resp2 = client.sendMessage("req:world");String resp3 = client.sendMessage("exit");System.out.println(resp1);System.out.println(resp2);System.out.println(resp3);}}
  • @BeforeClass – 表示在类中的任意public static void方法执行之前执行
  • @AfterClass – 表示在类中的任意public static void方法执行之后执行
  • @Before – 表示在任意使用@Test注解标注的public void方法执行之前执行
  • @After – 表示在任意使用@Test注解标注的public void方法执行之后执行
  • @Test – 使用该注解标注的public void方法会表示为一个测试方法

多客户端优化

在实际情况中,服务端常常要处理多个客户端的请求,为此我们要在服务端为每一个客户端请求创建一个新的socket线程,即提供服务的客户端数将等于服务端正在运行的线程数

服务端

仍然用一个监听socket在主线程监听端口,而需要多线程存储已连接socket以保持与多个客户端的连接

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class EchoMultiSever {private ServerSocket serverSocket;public void start(int port){try {// 仍然使用一个socket在主线程中监听端口serverSocket = new ServerSocket(port);while (true){// 每次循环中,accept会阻塞调用,直到新的客户端调用// 连接成功后,EchoMultiServer会将已连接的socket委托给 EchoClientHandlernew EchoClientHandler(serverSocket.accept()).start();}}catch (IOException e){e.printStackTrace();}}public void stop(){try{serverSocket.close();}catch (IOException e){e.printStackTrace();}}// 创建一个单独的线程EchoClientHandler// 保存已连接的socket与客户端交流public static class EchoClientHandler extends Thread{private Socket clientSocket;private PrintWriter out;private BufferedReader in;public EchoClientHandler(Socket socket){this.clientSocket = socket;}// 线程执行start直到运行run方法,与目标客户端进行交流// 其内部发生的情况与EchoSever相同public void run(){try{out = new PrintWriter(clientSocket.getOutputStream(), true);in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));String inputLine;while ((inputLine = in.readLine()) != null){if(inputLine.equals("exit")){out.println("bye");break;}out.println(inputLine.replace("req", "res"));}in.close();out.close();clientSocket.close();}catch (IOException e){e.printStackTrace();}}}public static void main(String[] args) {EchoMultiSever server = new EchoMultiSever();// 开启服务器server.start(5555);}}

客户端

客户端不需要进行优化修改,与上面的相同

import java.io.*;
import java.net.Socket;public class EchoMultiClient {private Socket clientSocket;private PrintWriter out;private BufferedReader in;public void startConnection(String ip, int port){try{// 客户端需要知道服务端的ip和其正在监听的端口号,才能发起连接// 服务器接收连接后创建客户端socketclientSocket = new Socket(ip, port);// 获取socket的输入输出流,以便与服务端通信out = new PrintWriter(clientSocket.getOutputStream(), true);in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));}catch (IOException e){e.printStackTrace();}}public String sendMessage(String msg){out.println(msg);String resp = null;try {resp = in.readLine();} catch (IOException e) {e.printStackTrace();}return resp;}public void stopConnection(){try {in.close();out.close();clientSocket.close();}catch (IOException e){e.printStackTrace();}}}

测试

测试类中需要发起多个客户端请求

运行EchoMultiSever后,运行以下案例

import org.junit.Test;// 测试方法开启多个客户端请求
public class EchoMultiTest {@Testpublic void buildClient1(){EchoClient client1 = new EchoClient();client1.startConnection("127.0.0.1", 5555);String resp1 = client1.sendMessage("req:hello");String resp2 = client1.sendMessage("req:world");String resp3 = client1.sendMessage("exit");System.out.println(resp1);System.out.println(resp2);System.out.println(resp3);}@Testpublic void buildClient2(){EchoClient client2 = new EchoClient();client2.startConnection("127.0.0.1", 5555);String resp1 = client2.sendMessage("req:hello");String resp2 = client2.sendMessage("req:world");String resp3 = client2.sendMessage("exit");System.out.println(resp1);System.out.println(resp2);System.out.println(resp3);}@Testpublic void buildClient3(){EchoClient client3 = new EchoClient();client3.startConnection("127.0.0.1", 5555);String resp1 = client3.sendMessage("req:hello");String resp2 = client3.sendMessage("req:world");String resp3 = client3.sendMessage("exit");System.out.println(resp1);System.out.println(resp2);System.out.println(resp3);}}

参考文章

Java 套接字

TCP/IP图解

Java|Socket编程指南相关推荐

  1. Java Socket编程如何建立两者关系

    转自:http://developer.51cto.com/art/201003/190582.htm Java Socket编程需要大家详细的学习,有关的技术一直在不断的更新.那么要如何才能掌握好有 ...

  2. JAVA socket编程 Datagram套接字 UDP协议(转)

      查看文章     JAVA socket编程 Datagram套接字 UDP协议 2009-05-13 09:35 1 UDP套接字 数据报(Datagram)是网络层数据单元在介质上传输信息的一 ...

  3. Java Socket编程详解

    Java Socket编程是Java网络编程很重要的内容,现参考了许多资料,总结如下: 1. Java网络编程原理+Socket编程 http://www.cnblogs.com/linzheng/a ...

  4. Java Socket编程----通信是这样炼成的

    转载自 Java Socket编程----通信是这样炼成的 Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Socke ...

  5. tcp java实例_实现了基于TCP的Java Socket编程实例代码

    实现了基于TCP的Java Socket编程,功能很简单:客户端向服务器端输出一名话"connect",服务器端接收输出到控制台并向客户端输出一名话"Hello" ...

  6. 用JAVA SOCKET编程,读服务器几个字符,再写入本地显示

    Server: package cn.itcast.framework.socket;import java.io.BufferedReader; import java.io.IOException ...

  7. java socket 编程 客户机服务器_Java Socket编程服务器响应客户端实例代码

    通过输入流来读取客户端信息,相应的时候通过输出流来实现. 服务端类的代码: import java.io.BufferedReader; import java.io.IOException; imp ...

  8. C语言SOCKET编程指南

    转载自:http://blog.sina.com.cn/s/blog_79b01f66010163q3.html 这篇文章完全可以作为c语言socket编程指南,无论在任何系统下.感谢作者fenglo ...

  9. java socket编程心跳_Java Socket编程心跳包创建实例解析

    1.什么是心跳包? 心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包. 用来判断对方(设备,进程或其它网元)是否正常运行,采用 ...

最新文章

  1. 为什么阿里P8、P9技术大牛反复强调“结构化思维”?
  2. 史上最简单的SpringCloud教程 | 第八篇: 消息总线(Spring Cloud Bus)(Finchley版本)
  3. wxpython组件SplitterWindow 的简单使用
  4. VTK:BSP树时序用法实战
  5. kaike的FLAGs
  6. 大数据分析过程中包含哪些技术
  7. JSTL EL 详解
  8. 伪静态URL、静态URL和动态URL的区别
  9. SPSS偏相关分析的应用介绍
  10. Appium-W3C Action(W3C动作)
  11. python爬音乐评论生成词云图_Python ---网易云音乐评论自动云图生成器
  12. React报错 React Hook useEffect has a missing dependency: ‘obj‘
  13. 硬盘格式化后怎么恢复文件
  14. js刷新页面得重新加载和页面的刷新
  15. 虚拟串口VSPD安装指南
  16. cuda编程与gpu并行计算(六):图稀疏矩阵转为CSR结构并传入gpu
  17. SMTP邮件服务端口
  18. ffmpeg库 pycharm_python+ffmpeg让字符跳动起来
  19. CDN服务器是什么意思?CDN服务器搭建部署
  20. 985毕业,半路出家28岁进军Java,坚持了三年现如今年薪36W+,也不算辜负自己!

热门文章

  1. MTCNN+ArcFaceloss实现视频人脸识别理论与实践(附代码)
  2. Canence第9篇之cadence 快捷键设置
  3. 常见的 GC 算法介绍(Parallel/CMS/G1):温故而知新
  4. c语言system怎么用,system()怎样使用?
  5. CSDN网站使用了哪些技术?
  6. vue的PDF预览插件(vue-pdf),支持旋转、放大缩小、打印、下载等
  7. Hello Ionic - Ionic学习笔记
  8. 基于Arduino的简易跑马灯+呼吸灯
  9. 自定义StackPanel模板,实现自定义顺序排列
  10. USB 概述及协议基础