C#程序和java程序间的socket通信

599 阅读4分钟

 在上一篇博客《DALSA工业相机流程》中讲过,需要在java和C#两个平台的两个项目里,实现通信。

一. 进程间的通信定义

  先理解进程,进程是操作系统的内部概念,每当我们执行一个程序的时候,OS就创建一个进程,在这个过程中伴随着资源的分配和释放。可以这么说,进程就是一个程序的一次执行过程。

  进程间通信(IPC:InterProcess Communication),进程之间的用户空间是独立的,一般来说不能互相访问,但是我们实际编程过程中很多进程之间存在一些逻辑条件判断,需要互相通信,才能完成我们的编程设计,这就有了进程间的通信。进程间通信主要应用于以下几个场景:数据传输、共享数据、通知事件、资源共享、进程控制,我的应用场景是第一个也是最常见的“数据传输”:将数据从一个进程发送到另外一个进程,发送数据量在一字节到几兆字节之间。

二. 进程间通信的方式

1.管道:管道的实质是内核利用 环形队列 的数据结构在 内核缓冲区 中的一个实现,顺序读写。管道包括普通管道、流管道、命名管道,是使用最简单的方式。

2.消息队列:保存在内核中的消息 链表 , 跟管道相比不需要按照队列次序来读写,可以自定义接收消息。

3.共享内存:一对多的关系,一个进程创建一段共享内存,其他进程都可以访问该共享内存,是最快的IPC方式。

4.信号量:信号量是一个计时器,主要用于进程间的互斥和同步,而不是通信数据。

5.信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

6.套接字:Socket也是一种进程间通信机制,但是与以上都不同的是,Socket可以用于不同机器间的通信,因为使用了通络通信,设置IP和端口。

本地(localhost、127.0.0.1)套接字也是最稳定的IPC。

三. Socket通信

首先,我们上面讲的6中通信方式中,1-5都是本地进程通信,只有socket是可以在不同主机间进行通信的方式。这是如何实现的呢?我们回顾一下TCP/IP协议,传输控制协议/网间协议,所谓网间协议,就是不止是在本地通信,放到网络上也要能准确通信。我们知道在本地主机上,我们识别一个进程的方式是找到进程的PID,PID是进程的唯一标识,如果只在本地通信,那么我们拿到PID就足够了,但是当我们进行网络通信,在不同的主机之间,PID重复的概率非常大。此时,就想办法让进程在网络上也具备唯一性。

办法自然就是TCP/IP了,IP层的ip地址可以在网络中确定唯一的主机,TCP层协议和端口号可以唯一标示主机的一个进程,也就是ip地址+协议+端口号来在网络中标示唯一进程,这是网络中进程间通信的前提基础。

标示了唯一进程后,我们就可以用socket来进行通信了,Socket翻译过来是套接字,是针对网络中不同主机的进程之间双向通信的端点的抽象,直白来说就是网络进程通信的端点,提供了应用层进程利用网络协议交换数据的机制,是应用程序通过网络协议进行通信和交互的接口。

Scoket是接口,是把TCP/IP层一些复杂的操作抽象成了接口,供应用层调用的,所以socket的使用一定是在应用层。

Socket分为服务端和客户端(这一点上能看出来底层是TCP/IP),服务端和客户端的工作流程包括交互如下图所示:

如果你回顾一下TCP/IP的三次握手,就会发现有不少共同点。

四. 实例

我的实际需求是java进程向C#进程发送数据,所以C#为服务端,java为客户端。

C#代码(Server):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;

namespace GrabImgByd
{
    class SocketJavaServer
    {
        static void Main(string[] args)
        {
            server2();
        }

        static void server1()
        {
            Console.WriteLine("方法1");
            //第一个参数是指定socket对象使用的寻址方案,即IPV4或IPV6;
            //第二个参数socket对象的套接字的类型,此处stream是表示流式套接字
            //第三个参数socket对象支持的协议,TCP协议或UDP协议
            Socket soo = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//参数这样写就好了

            IPAddress ip = IPAddress.Parse("127.0.0.1");//本机ip作为服务器

            IPEndPoint ipend = new IPEndPoint(ip, 61666);//将网络终结点表示为 IP 地址和端口号

            soo.Bind(ipend);//用socket对像的Bind()方法绑定IPEndPoint 绑定主机端口号

            soo.Listen(10);//挂起的连接队列的最大长度。貌似可以不写。不是最大连接数

            Console.WriteLine("C#创建服务器成功...");

            Socket so = soo.Accept();//等待用户连接
            byte[] by = new byte[1024];
            while (true)
            {
                int len = so.Receive(by);//Send发送 Receive接收
                Console.WriteLine("C#服务器收到消息 长度+" + len);
                Console.WriteLine(Encoding.UTF8.GetString(by, 0, len));
            }
        }

        static void server2()
        {
            Console.WriteLine("方法2");
            TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 61666);

            listener.Start();//启动服务器
            //上面这一段相当于java的ServerSocket soo=new ServerSocket(61666);

            Console.WriteLine("C#创建服务器成功...");

            Socket so = listener.AcceptSocket();//等待用户连接
            NetworkStream io = new NetworkStream(so);
            byte[] by = new byte[1024];
            while (true)
            {
                //接收一组byte 存储到字节数组by里
                //从by[0]处开始存储该数据 要读取1024个字节
                //len为实际读取到的字节数
                int len = io.Read(by, 0, 10);

                Console.WriteLine("C#服务器收到消息 长度+" + len);
                Console.WriteLine(Encoding.UTF8.GetString(by, 0, len));
            }

        }
    }
}

此处列举了两个方法,server1和server2,都可以运行,建议使用server1。

运行图:

Java代码(Client):

/*
 * 文件名:SocketCScharpClient.java
 * 版权:Copyright BYD Auto Industry Company Limited. All rights reserved..
 */
package com.byd.grabImg;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * 〈一句话功能简述〉
 * 〈功能详细描述〉
 * 
 * @author xu.shuo2
 * @version
 * @see
 * @since
 */
public class SocketCScharpClient {

    public static void main(String[] args) {
        try {
            // 第一个参数的服务器的ip 第二个参数是端口号
            Socket so = new Socket("127.0.0.1", 61666);
            System.out.println("java连接服务器成功...");
            OutputStream out = so.getOutputStream();// 获取socket的输出流
            Scanner in = new Scanner(System.in);// 从键盘输入值
            while (true) {
                String s = in.nextLine();
                out.write(s.getBytes());
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

用的是键盘输入的值来发送给服务端,运行效果如下:

发送字符串:

\