1.1 SOCKET概念
SOCKET是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远方主机的IP、远方进程的协议端口。 应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了socket接口。应用层可以和传输层通过socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
1.2建立socket连接
建立socket连接至少需要一对socket,其中一个运行与客户端,称为clientSocket,另一个运行于服务器端,称为serverSocket.
socket之间的连接过程分为三个步骤:服务器监听客户端,客户端请求服务端,客户端与服务端连接确认。 服务器监听客户端: 服务器端SOCKET并不定位具体的客户端SOCKET,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。 客户端请求服务端: 指客户端的SOCKET提出连接请求,要连接的目标是服务器端的SOCKET。为此,客户端的SOCKET必须首先描述它要连接的服务器的SOCKET, 指出服务器SOCKET的地址和端口号,然后向服务器端SOCKET提出连接请求。 客户端与服务端连接确认: 当服务器端SOCKET监听到或者说接收到客户端SOCKET的连接请求时,就响应客户端SOCKET的请求,建立一个新的线程,把服务器端的SOCKET的描述发给客户端, 一旦客户端确认了此描述,双方就正式建立连接。而服务器端SOCKET继续处于监听状态,继续接受其他客户端SOCKET的连接请求。
实现一个简单的C#为客户端,java为接收端的例子,这是一个可以监听多个客户端的例子,可以传输照片或者文字,由于C#是无符号的二进制,而java是有符号的二进制,所以在图片的处理上我使用base64作为中间的过渡,不然从C#到Java的传输过程会出现字节丢失的情况,接着在文字的传输中我统一了字节码,否则会出现乱码现象,因为C#的默认编码不是UTF-8而java是UTF-8,从而导致了乱码现象。
C#实现的客户端:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Net; using System.Net.Sockets; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Collections.Generic; using System.Threading; namespace ClientMany { class Program { static void Main(string[] args) { Console.WriteLine("Client Running..."); //定义要传送的信息 string msg = "这个是客户端的消息"; string filename = "C:\\send\\image.jpg"; string str1 = SentPictures(filename, msg); str1 = str1 + "#" + str1 + "#" + str1 + "#" + str1 + "#" + str1; Send(str1); //按Q退出 ConsoleKey key; Console.WriteLine("\n\n 输入 \"Q\"键退出。n继续"); do { key = Console.ReadKey(true).Key; } while (key != ConsoleKey.Q); } /// <summary> /// 向远程客户端发送图片 /// 这一步是base64编码 /// </summary> /// <param name="strRemoteIP">远程客户端IP</param> /// <param name="sendPort">发送图片的端口</param> private static string SentPictures(String filename,String str) { String str1; if (File.Exists(filename)) { FileInfo file = new FileInfo(filename); var stream = file.OpenRead(); byte[] buffer = new byte[file.Length]; //读取图片字节流 stream.Read(buffer, 0, Convert.ToInt32(file.Length)); //将base64字符串保存到base64.txt文件中 //将字节流转化成base64字符串 str1 = Convert.ToBase64String(buffer); } else { Console.WriteLine("文件不存在"); str1 = ""; } str1 = str + ")" + str1 ; return str1; } private static void Send(string str){ TcpClient client; try { client = new TcpClient(); client.Connect("localhost", 8500);//与服务器连接 } catch (Exception ex) { Console.WriteLine(ex.Message); return; } Console.WriteLine("发送消息"); NetworkStream streamToServer = client.GetStream(); byte[] buffer1 = Encoding.UTF8.GetBytes(str);//获得缓存同时规定编码为UTF-8 streamToServer.Write(buffer1, 0, buffer1.Length);//发往服务器 // Console.Read(); client.Close(); } } }Java实现的服务器端: 实现接收的实现的程序:
package com.wxshan.test; import lombok.extern.log4j.Log4j2; import net.coobird.thumbnailator.Thumbnails; import org.springframework.beans.BeanUtils; import org.springframework.messaging.simp.SimpMessagingTemplate; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.net.ServerSocket; import java.net.Socket; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; @Log4j2 public class ListenSocket extends Thread { //端口号 private static final int SERVER_PORT = 8500; //服务端socket private ServerSocket serverSocket = null; public ListenSocket() { try { if (null == serverSocket) { this.serverSocket = new ServerSocket(SERVER_PORT); } } catch (IOException e) { log.error(e.getMessage(), e); } } /** * 监听的线程 */ public void run() { Socket client; System.out.println("-------主监听线程----------start----------"); try { while (true) { client = serverSocket.accept(); Thread c_thread = new CreateServerThread(client); c_thread.start(); } } catch (IOException e) { log.error(e.getMessage(), e); } } class CreateServerThread extends Thread { private Socket client; private PrintWriter out = null; private BufferedReader in = null; InputStreamReader inSR; OutputStream outSR; CarWaringVO carWaringVO; public CreateServerThread(Socket client) throws IOException { System.out.println("启动分线程"); this.client = client; } public void run() { try { inSR = new InputStreamReader(client.getInputStream(), "UTF-8");//规定编码 in = new BufferedReader(inSR);//接收请求的流 outSR = client.getOutputStream(); out = new PrintWriter(outSR); //String imgUrl = ""; HashMap<String, String> imgUrl = new HashMap<>(); int len = 0;//监听到的字符串长度 StringBuffer tStringBuffer = new StringBuffer();//监听到的字符串w char[] buf = new char[4096]; while ((len = in.read(buf)) != -1) { String string = new String(buf, 0, len); tStringBuffer.append(string); } //将接收的字符串分割出不同的消息 String[] stringInfo = tStringBuffer.toString().split("#"); for (int i = 0; i < stringInfo.length; i++) { UUID uuid = UUID.randomUUID(); String date = CommonUtils.dateParse(LocalDate.now(), "yyyy/MM/dd"); String fileName = uuid + ".jpg"; String FilePath = new File(System.getProperty("java.io.tmpdir")).getParentFile().getParentFile().getAbsolutePath() + uploadPath + date + "/"; String urlpath = "/picture/car/" + date + "/" + fileName; String path = FilePath + fileName; File file = new File(FilePath); if (!file.exists()) { file.mkdirs(); } //imgUrl += urlpath + ","; //将消息的信息与照片分割出来 String[] strings = stringInfo[i].split("\\)"); //区别照片的类型 imgs(strings[0],imgUrl,urlpath); //对图片进行base64的反转码 base64ToFile(strings[1], path); System.out.println("客户端接收的消息:"+stringinfo + ",图片地址:"+urlpath); } catch (IOException e) { log.error(e.getMessage(), e); } finally { if (in != null) { try { in.close(); } catch (IOException e) { log.error(LogException.getTrace(e)); } } if (inSR != null) { try { inSR.close(); } catch (IOException e) { log.error(LogException.getTrace(e)); } } if (out != null) { try { out.close(); } catch (Exception e) { log.error(LogException.getTrace(e)); } } try { if (client != null) { client.close(); } } catch (IOException e) { log.error(LogException.getTrace(e)); } } } } /** * 将base64转化为文件 * 这步是base解码 * @param base64 * @param path * @return */ public boolean base64ToFile(String base64, String path) { byte[] buffer; InputStream stream; try { buffer = Base64.getDecoder().decode(base64); stream = new ByteArrayInputStream(buffer); Thumbnails.of(stream).scale(1f).outputQuality(0.5f).outputFormat("jpg").toFile(path); return true; } catch (Exception e) { throw new RuntimeException("base64字符串异常或地址异常\n" + e.getMessage()); } } /** * 关闭当前线程 */ public void closeSocketServer() { try { if (null != serverSocket && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } }声明类:
package com.wxshan.test; import org.springframework.beans.factory.annotation.Value; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.annotation.Resource; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; @WebListener public class SocketListener implements ServletContextListener{ private ListenSocket sockertThread; @Resource private SimpMessagingTemplate simpMessagingTemplate; /** * 初始化 当servert容器启动web应用时调用该方法 * @param servletContextEvent */ @Override public void contextInitialized(ServletContextEvent servletContextEvent) { WebApplicationContextUtils.getRequiredWebApplicationContext(servletContextEvent.getServletContext()) .getAutowireCapableBeanFactory().autowireBean(this); if (null == sockertThread) { sockertThread = new ListenSocket(); sockertThread.start(); } } /** * 销毁 当servlet容器终止web应用时调用改方法 * @param servletContextEvent */ @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { if (null != sockertThread && !sockertThread.isInterrupted()){ //关闭线程 sockertThread.closeSocketServer(); //中断线程 sockertThread.interrupt(); } } }