socket如何通过代理服务器通讯,在线等

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socket如何通过代理服务器通讯,在线等相关的知识,希望对你有一定的参考价值。

参考技术A 1.客户端连接到代理服务器开放的端口;
2.客户端向代理服务器发送验证申请;
3.代理服务器向客户端发送一个数据包,从而客户端得知自己的通信申请是否被批准;
4.客户端向代理服务器发送一个数据包,告知代理服务器自己要连接的目的主机的地址和端口;
5.代理服务器开始进行到目的主机的真正连接;
6.代理服务器为客户端开放一个新的端口并向客户端发送一个数据包告知客户端这个新的端口;
7.客户端创建一个新的套接字并连接到代理服务器的新的端口;
8.然后,代理服务器把由新端口接收到的数据都转发给目的主机,把从目的主机发过来的数据都由新端口转发给客户端。本回答被提问者和网友采纳

java socket 模拟im 即时通讯

自己想了一下怎么实现,就写了,没有深究是否合理.更多处理没有写下去,例如收件人不在线,应该保存在数据库,等下一次连接的时候刷新map,再把数据发送过去,图片发送也没有做,也没有用json格式

socket很奇怪,我用客户端连接上了服务器,没有发送消息的情况下,断开电脑网络,是不会出现问题,然后在把电脑网络连接上,通讯依然正常,正常断开也不出问题,但是用idea直接按stop键,那么服务端就会出问题了,读取事件会一直为true,造成死循环,消耗CPU,所以必须要判断一下客户端连接是否断开了

 

只需要把客户端代码启动几个,修改一些userName以及收件人,就可以测试,实现类似QQ微信即时通讯,聊天功能

 

 

服务端代码

package serversocketchannel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 
 * @author ZhenWeiLai
 *
 */
public class ServerSocketChannelNonBlocking {
    private static ServerSocketChannel serverSocketChannel = null;
    private static Charset charset = Charset.forName("GBK");//设置编码集,用于编码,解码
    private static Selector selector = null;
    //保存客户端的map
    private static final ConcurrentHashMap<String,SocketChannel> clientSockets = new ConcurrentHashMap<>();
    static{
        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().setReuseAddress(true);
            serverSocketChannel.socket().bind(new InetSocketAddress(8000));
            serverSocketChannel.configureBlocking(false);//设置为非阻塞
            selector = Selector.open();//实例化一个选择器
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
            service();
    }
    
    private static void service(){
        SocketChannel clientChannel = null;
        SelectionKey selectionKey = null;
        SocketChannel targetChannel = null;
        try {
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);//服务端监听连接
            while(true){
                selector.select();//阻塞至有新的连接就开始处理
                Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
                while(selectionKeys.hasNext()){
                    selectionKey = selectionKeys.next();
                    if(selectionKey.isAcceptable()){//如果事件是连接事件
                        ServerSocketChannel serverChannel = (ServerSocketChannel)selectionKey.channel();//获取事件绑定的channel
                        clientChannel = serverChannel.accept();//连接获取带客户端信息的socketChannel
                        clientChannel.configureBlocking(false);//客户设置为非阻塞,因为非阻塞才支持选择器.避免盲等浪费资源
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//作为每一个客户端的附件缓冲器
                        /**
                         * 只监听读事件,这里千万别监听写事件,因为只要连接有效,那么写事件会一直为true,导致死循环,很耗资源
                         * 可以跟serverSocket用同一个选择器,因为绑定的channel不同
                         */
                        clientChannel.register(selector,SelectionKey.OP_READ,byteBuffer);
                    }else if(selectionKey.isReadable()){//只要有客户端写入,那么就可以处理
                        //获取客户端附件,也就是写入的数据
                        ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
                        //从selectionKey获取客户端的channel
                        SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                        //把附件读出,解码为字符串
                        String msg = read(socketChannel,byteBuffer);
                        //这里用了->分割收件人,->后面跟着的字符串是收件人
                        if(msg.indexOf("->")!=-1){
                            //内容
                            String content = msg.substring(0,msg.lastIndexOf("->"));
                            //从map里获取收件人的socket
                            targetChannel = clientSockets.get(msg.substring(msg.lastIndexOf("->")+2));
                            //实例化一个缓冲区,用来写出到收件人的socketChannel
                            ByteBuffer temp = ByteBuffer.allocate(1024);
                            temp.put(charset.encode(content));
                            //写出
                            handleWrite(targetChannel,temp);
                        }else{
                            //如果内容没有收件人,那么视为第一次连接,客户端发过来的userName,作为KEY存入MAP
                            clientSockets.put(msg,socketChannel);
                        }
                    }
                    selectionKeys.remove();
                }
            }
        } catch (IOException e) {
            try {
                if(selectionKey!=null)selectionKey.cancel();
                if(clientChannel!=null){
                    clientChannel.shutdownInput();
                    clientChannel.shutdownOutput();
                    clientChannel.close();
                }
                if(targetChannel!=null){
                    targetChannel.shutdownInput();
                    targetChannel.shutdownOutput();
                    targetChannel.close();
                }
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            e.printStackTrace();
        }

    }
    
    private static String read(SocketChannel socketChannel,ByteBuffer byteBuffer){
        //重置position limit为写入做准备
        byteBuffer.clear();
        try {
            int flag =socketChannel.read(byteBuffer);
            //判断客户端是否断开连接
            if(flag==-1){
                //如果客户端无故断开,一定要关闭,否则读取事件一直为true造成死循环,非常耗资源
                socketChannel.close();
            }
        } catch (IOException e) {
            try {
                socketChannel.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }
        //position =0 limit等于有效下标,为写出做准备
        byteBuffer.flip();
        return charset.decode(byteBuffer).toString();
    }
    
    //写出
    private static void handleWrite(SocketChannel socketChannel,ByteBuffer byteBuffer){
        synchronized (byteBuffer) {
            byteBuffer.flip();
            try {
                socketChannel.write(byteBuffer);
            } catch (IOException e) {
                try {
                    socketChannel.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                e.printStackTrace();
            }
        }
    }
}

客户端代码

package socketchannel;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * Created by lzw on 17-2-28.
 */
public class SocketChannelNonBlockingClient {
    private static Charset charset = Charset.forName("GBK");
    private static ByteBuffer receiveBuffer = ByteBuffer.allocate(10240);
    private static ByteBuffer sendBuffer = ByteBuffer.allocate(10240);
    private static SocketChannel socketChannel = null;
    private static Selector selector = null;
    private static String userName = "client1";//客户端名
    private static String targetName = "client2";//收件人名

    public static void main(String[] args) {
        try {
            socketChannel = SocketChannel.open();
            //连接到服务端
            SocketAddress socketAddress = new InetSocketAddress("19.95.103.112",8000);
            selector = Selector.open();//实例化一个选择器
            socketChannel.configureBlocking(false);//设置为非阻塞
            //先监听一个连接事件
            socketChannel.register(selector,SelectionKey.OP_CONNECT);
            //连接
            socketChannel.connect(socketAddress);
            //jdk 1.8的lambda表达式,用一个线程监控控制台输入
            new Thread(()->{
                    try {
                        receiveFromUser();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
            }).start();

            talk();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void talk(){
        try {
            while(true){
                selector.select();//阻塞直到连接事件
                Iterator<SelectionKey> readyKeys = selector.selectedKeys().iterator();
               while(readyKeys.hasNext()){
                    SelectionKey key =readyKeys.next();
                    if(key.isConnectable()){
                        //非阻塞的情况下可能没有连接完成,这里调用finishConnect阻塞至连接完成
                        socketChannel.finishConnect();
                        //连接完成以后,先发送自己的userName以便保存在服务端的客户端map里面
                        synchronized (sendBuffer){
                            SocketChannel socketChannel1 = (SocketChannel)key.channel();
                            sendBuffer.clear();
                            sendBuffer.put(charset.encode(userName));
                            send(socketChannel1);
                            socketChannel.register(selector,SelectionKey.OP_READ);//仅监听一个读取事件
                        }

                    }else if(key.isReadable()){
                        //处理读事件
                        receive(key);
                    }
                    readyKeys.remove();
                }
            }
        } catch (ClosedChannelException e) {
            try {
                socketChannel.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    /**
     * 从控制台获取用户输入
     * @throws IOException
     */
    private static void receiveFromUser() throws IOException{
        //阻塞直到控制台有输入
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        for(String msg = br.readLine();msg!=null&&!msg.equals("bye");msg = br.readLine()){
            //同步锁避免线程竞争
            synchronized (sendBuffer) {
                sendBuffer.clear();
                //编码
                sendBuffer.put(charset.encode(msg));
                //分割副
                sendBuffer.put(charset.encode("->"));
                //目标名
                sendBuffer.put(charset.encode(targetName));
                send(socketChannel);
            }
        }
    }
    /**
     * 接收服务端的数据
     * @param key
     */
    private static void receive(SelectionKey key) throws IOException {
        //获取服务端的channel
        SocketChannel channel = (SocketChannel) key.channel();
        //为写入缓冲器做准备position=0,limit=capacity
            receiveBuffer.clear();
            //从服务端的channel把数据读入缓冲器
            channel.read(receiveBuffer);
            //position=0,limit=有效下标最后一位
            receiveBuffer.flip();
            //解码
            String msg = charset.decode(receiveBuffer).toString();
            //输出到控制台
            System.out.println(msg);
    }

    /**
     * 发送到服务端
     */
    private static void send(SocketChannel sendChannel) throws IOException {
            if(sendBuffer.remaining()!=0){
                synchronized (sendBuffer){
                    sendBuffer.flip();
                    sendChannel.write(sendBuffer);
                }
            }
    }
}

 

以上是关于socket如何通过代理服务器通讯,在线等的主要内容,如果未能解决你的问题,请参考以下文章

Socket编程 ------ 模拟QQ聊天工具

android环境下的即时通讯

基于socket.io +koa2 +天行机器人 实现简单人机实时通讯(nginx处理socket.io https代理问题)

socket 在两个局域网如何通讯 、?

如何建立socket5代理服务器

如何通过socket5代理服务器模拟器实现不同ip