精华内容
下载资源
问答
  • socket即时通讯

    2016-05-19 16:51:25
    iOS之Socket使用简明教程- AsyncSocket iOS即时通讯 Socket转载▼ ios原生的socket用起来不是很直观,所以我用的是AsyncSocket这个第三方库,对socket的封装比较好,只是好像没有带外传输(out—of-band) 如果...

    参考链接:http://blog.sina.com.cn/s/blog_a5d3d4490102vzsz.html

    iOS之Socket使用简明教程- AsyncSocket   iOS即时通讯 <wbr>Socket转载▼

    ios原生的socket用起来不是很直观,所以我用的是AsyncSocket这个第三方库,对socket的封装比较好,只是好像没有带外传输(out—of-band) 如果你的服务器需要发送带外数据,可能得想下别的办法
    环境
    下载AsyncSockethttps://github.com/roustem/AsyncSocket类库,将RunLoop文件夹下的AsyncSocket.h, AsyncSocket.m, AsyncUdpSocket.h, AsyncUdpSocket.m 文件拷贝到自己的project中
    添加CFNetwork.framework, 在使用socket的文件头
    1
    2
    3
    4
    #import
    #import
    #import
    #import
    使用
    1. socket 连接
    即时通讯最大的特点就是实时性,基本感觉不到延时或是掉线,所以必须对socket的连接进行监视与检测,在断线时进行重新连接,如果用户退出登录,要将socket手动关闭,否则对服务器会造成一定的负荷。
    一般来说,一个用户(对于ios来说也就是我们的项目中)只能有一个正在连接的socket,所以这个socket变量必须是全局的,这里可以考虑 使用单例或是AppDelegate进行数据共享,本文使用单例。如果对一个已经连接的socket对象再次进行连接操作,会抛出异常(不可对已经连接的 socket进行连接)程序崩溃,所以在连接socket之前要对socket对象的连接状态进行判断
    使用socket进行即时通讯还有一个必须的操作,即对服务器发送心跳包,每隔一段时间对服务器发送长连接指令(指令不唯一,由服务器端指定,包括 使用socket发送消息,发送的数据和格式都是由服务器指定),如果没有收到服务器的返回消息,AsyncSocket会得到失去连接的消息,我们可以 在失去连接的回调方法里进行重新连接。
    先创建一个单例,命名为Singleton
    Singleton.h
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Singleton.h
    #import "AsyncSocket.h"
     
    #define DEFINE_SHARED_INSTANCE_USING_BLOCK(block) \
    static dispatch_once_t onceToken = 0; \
    __strong static id sharedInstance = nil; \
    dispatch_once(&onceToken, ^{ \
    sharedInstance = block(); \
    }); \
    return sharedInstance; \
     
    @interface Singleton : NSObject
     
    + (Singleton *)sharedInstance;
     
    @end
    Singleton.m
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    +(Singleton *) sharedInstance
    {
     
    static Singleton *sharedInstace = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
     
        sharedInstace = [[self alloc] init];
    });
     
    return sharedInstace;
    }
    这样一个单例就创建好了
    在.h文件中生命socket变量
    1
    2
    3
    @property (nonatomic, strong) AsyncSocket    *socket;       // socket
    @property (nonatomic, copy  ) NSString       *socketHost;   // socket的Host
    @property (nonatomic, assign) UInt16         socketPort;    // socket的prot
    下面是连接,心跳,失去连接后重连
    连接(长连接)
    在.h文件中声明方法,并声明代理
    1    -(void)socketConnectHost;// socket连接
    在.m中实现,连接时host与port都是由服务器指定,如果不是自己写的服务器,请与服务器端开发人员交流
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // socket连接
    -(void)socketConnectHost{
     
        self.socket    = [[AsyncSocket alloc] initWithDelegate:self];
     
        NSError *error = nil;
     
        [self.socket connectToHost:self.socketHost onPort:self.socketPort withTimeout:3 error:&error];
     
    }
    心跳
    心跳通过计时器来实现
    在singleton.h中声明一个定时器
    1    @property (nonatomic, retain) NSTimer        *connectTimer; // 计时器
    在.m中实现连接成功回调方法,并在此方法中初始化定时器,发送心跳在后文向服务器发送数据时说明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #pragma mark  - 连接成功回调
    -(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString  *)host port:(UInt16)port
    {
        NSLog(@"socket连接成功");
     
        // 每隔30s像服务器发送心跳包
        self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];// 在longConnectToSocket方法中进行长连接需要向服务器发送的讯息
     
        [self.connectTimer fire];
     
    }
    2. socket 断开连接与重连
    断开连接
    失去连接有几种情况,服务器断开,用户主动cut,还可能有如QQ其他设备登录被掉线的情况,不管那种情况,我们都能收到socket回调方法返回 给我们的讯息,如果是用户退出登录或是程序退出而需要手动cut,我们在cut前对socket的userData赋予一个值来标记为用户退出,这样我们 可以在收到断开信息时判断究竟是什么原因导致的掉线
    在.h文件中声明一个枚举类型
    1
    2
    3
    4
    enum{
        SocketOfflineByServer,// 服务器掉线,默认为0
        SocketOfflineByUser,  // 用户主动cut
    };
    声明断开连接方法
    1    -(void)cutOffSocket; // 断开socket连接
    .m
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 切断socket
    -(void)cutOffSocket{
     
        self.socket.userData = SocketOfflineByUser;// 声明是由用户主动切断
     
        [self.connectTimer invalidate];
     
        [self.socket disconnect];
    }
    重连
    实现代理方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    -(void)onSocketDidDisconnect:(AsyncSocket *)sock
    {
        NSLog(@"sorry the connect is failure %ld",sock.userData);
        if (sock.userData == SocketOfflineByServer) {
            // 服务器掉线,重连
            [self socketConnectHost];
        }
        else if (sock.userData == SocketOfflineByUser) {
            // 如果由用户断开,不进行重连
            return;
        }
     
    }
    3. socket 发送与接收数据
    发送数据
    我们补充上文心跳连接未完成的方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 心跳连接
    -(void)longConnectToSocket{
     
        // 根据服务器要求发送固定格式的数据,假设为指令@"longConnect",但是一般不会是这么简单的指令
     
        NSString *longConnect = @"longConnect";
     
        NSData   *dataStream  = [longConnect dataUsingEncoding:NSUTF8StringEncoding];
     
        [self.socket writeData:dataStream withTimeout:1 tag:1];
     
    }
    socket发送数据是以栈的形式存放,所有数据放在一个栈中,存取时会出现粘包的现象,所以很多时候服务器在收发数据时是以先发送内容字节长度, 再发送内容的形式,得到数据时也是先得到一个长度,再根据这个长度在栈中读取这个长度的字节流,如果是这种情况,发送数据时只需在发送内容前发送一个长 度,发送方法与发送内容一样,假设长度为8
    1
    2
    3
    NSData   *dataStream  = [@8 dataUsingEncoding:NSUTF8StringEncoding];
     
    [self.socket writeData:dataStream withTimeout:1 tag:1];
    接收数据
    为了能时刻接收到socket的消息,我们在长连接方法中进行读取数据
    1    [self.socket readDataWithTimeout:30 tag:0];
    如果得到数据,会调用回调方法
    1
    2
    3
    4
    5
    6
    7
    -(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
    {
        // 对得到的data值进行解析与转换即可
     
        [self.socket readDataWithTimeout:30 tag:0];
     
    }
    4. 简单使用说明
    我们在用户登录后的第一个界面进行socket的初始化连接操作,在得到数据后,将所需要显示的数据放在singleton中,对变量进行监听后做出相应的操作即可,延伸起来比较复杂,没有真实数据也不太方便说明,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [Singleton sharedInstance].socketHost = @"192.186.100.21";// host设定
        [Singleton sharedInstance].socketPort = 10045;// port设定
     
        // 在连接前先进行手动断开
        [Singleton sharedInstance].socket.userData = SocketOfflineByUser;
        [[Singleton sharedInstance] cutOffSocket];
     
        // 确保断开后再连,如果对一个正处于连接状态的socket进行连接,会出现崩溃
        [Singleton sharedInstance].socket.userData = SocketOfflineByServer;
        [[Singleton sharedInstance] socketConnectHost];

    展开全文
  • Socket即时通讯

    2019-06-05 14:12:06
    * Android Tcp即时通讯客户端 */ public class MainCSActivity extends Activity implements Runnable{ private TextView tv_msg = null; private EditText ed_msg = null; private Button btn_send = null; ...
    客户端代码:
    
    package com.example.multithreadclient;
    
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.content.DialogInterface;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.net.Socket;
    
    /**
     * Android Tcp即时通讯客户端
     */
    public class MainCSActivity extends Activity implements Runnable{
        private TextView tv_msg = null;
        private EditText ed_msg = null;
        private Button btn_send = null;
        private String msg=null;
        private static final String HOST = "192.168.11.83";//服务器地址
        private static final int PORT = 7776;//连接端口号
        private Socket socket = null;
        private BufferedReader in = null;
        private PrintWriter out = null;
        private AlertDialog.Builder mBuilder=null;
    
        //接收线程发送过来信息,并用TextView追加显示
        public Handler mHandler = new Handler() {
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                tv_msg.append((CharSequence) msg.obj);
            }
        };
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_maincs);
    
            tv_msg = (TextView) findViewById(R.id.txt_1);
            ed_msg = (EditText) findViewById(R.id.et_talk);
            btn_send = (Button) findViewById(R.id.btn_send);
    
    
            btn_send.setOnClickListener(new Button.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    msg = ed_msg.getText().toString();
                    if (socket.isConnected()) {//如果服务器连接
                        if (!socket.isOutputShutdown()) {//如果输出流没有断开
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    out.println(msg);//点击按钮发送消息
                                }
                            }).start();
                            ed_msg.setText("");//清空编辑框
                        }
                    }
                }
            });
            //启动线程,连接服务器,并用死循环守候,接收服务器发送过来的数据
            new Thread(this).start();
        }
    
        /**
         * 连接服务器
         */
        private void connection() {
            try {
                socket = new Socket(HOST, PORT);//连接服务器
                in = new BufferedReader(new InputStreamReader(socket
                        .getInputStream()));//接收消息的流对象
                out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
                        socket.getOutputStream())), true);//发送消息的流对象
            } catch (IOException ex) {
                ex.printStackTrace();
                //ShowDialog("连接服务器失败:" + ex.getMessage());
            }
        }
    
        /**
         * 如果连接出现异常,弹出AlertDialog!
         */
        public void ShowDialog(String msg) {
            mBuilder=new AlertDialog.Builder(this).setTitle("通知").setMessage(msg)
                    .setPositiveButton("ok", new DialogInterface.OnClickListener() {
    
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
    
                        }
                    });
            if(mBuilder !=null) {
                mBuilder.show();
            }
        }
    
        /**
         * 读取服务器发来的信息,并通过Handler发给UI线程
         */
        public void run() {
            connection();// 连接到服务器
            try {
                while (true) {//死循环守护,监控服务器发来的消息
                    if (!socket.isClosed()) {//如果服务器没有关闭
                        if (socket.isConnected()) {//连接正常
                            if (!socket.isInputShutdown()) {//如果输入流没有断开
                                String getLine;
                                if ((getLine = in.readLine()) != null) {//读取接收的信息
                                    getLine += "\n";
                                    Message message=new Message();
                                    message.obj=getLine;
                                    mHandler.sendMessage(message);//通知UI更新
                                } else {
    
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        protected void onDestroy(){
            super.onDestroy();
            mBuilder=null;
        }
    }
    

    服务端代码:

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * Tcp通信服务器
     * @author Devin Chen
     *
     */
    public  class   CSServer{
        private static final int PORT = 7776;
        private List<Socket> mClientList = new ArrayList<Socket>();
        private ServerSocket server = null;
        private ExecutorService mExecutors = null; // 线程池对象
    
        public static void main(String[] args) {
            new CSServer();
        }
    
        /**
         * 构造方法:任务是启动服务器,等待客户端连接
         */
        public CSServer() {
            try {
                server = new ServerSocket(PORT);
                mExecutors = Executors.newCachedThreadPool(); // 创建线程池
                System.out.println("服务器已启动,等待客户端连接...");
                Socket client = null;
                /*
                 * 用死循环等待多个客户端的连接,连接一个就启动一个线程进行管理
                 */
                while (true) {
                    client = server.accept();
                    // 把客户端放入集合中
                    mClientList.add(client);
                    mExecutors.execute(new Service(client)); // 启动一个线程,用以守候从客户端发来的消息
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        class Service implements Runnable {
            private Socket socket;
            private BufferedReader in = null;
            private String message = "";
    
            public Service(Socket socket) {
                this.socket = socket;
                try {
                    in = new BufferedReader(new InputStreamReader(
                            socket.getInputStream()));// 获得输入流对象
                    // 客户端只要一连到服务器,便发送连接成功的信息
                    message = "服务器地址:" + this.socket.getInetAddress();
                    this.sendMessage(message);
                    message = "当前连接总数:" + mClientList.size();
                    this.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
    
            @Override
            public void run() {
                try {
                    while (true) {
                        if ((message = in.readLine()) != null) {
                            // 当客户端发送的信息为:exit时,关闭连接
                            if (message.equals("exit")) {
                                closeSocket();
                                break;
                            } else {
                                // 接收客户端发过来的信息message,然后转发给客户端。
                                message = socket.getInetAddress() + ":" + message;
                                this.sendMessage(message);
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            /**
             * 关闭客户端
             *
             * @throws IOException
             */
            public void closeSocket() throws IOException {
                mClientList.remove(socket);
                in.close();
                message = "主机:" + socket.getInetAddress() + "关闭连接\n目前在线:"
                        + mClientList.size();
                socket.close();
                this.sendMessage(message);
            }
    
            /**
             * 将接收的消息转发给每一个客户端
             *
             * @param msg
             */
    
            public void sendMessage(String msg) {
                System.out.println(msg);// 先在控制台输出
                int count = mClientList.size();
                // 遍历客户端集合
                for (int i = 0; i < count; i++) {
                    Socket mSocket = mClientList.get(i);
                    PrintWriter out = null;
                    try {
                        out = new PrintWriter(new BufferedWriter(
                                new OutputStreamWriter(mSocket.getOutputStream())),
                                true);// 创建输出流对象
                        out.println(msg);// 转发
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    添加权限:

    <uses-permission android:name="android.permission.INTERNET"/>

     

    展开全文
  • C# winform Socket 即时通讯

    热门讨论 2017-10-27 11:20:21
    C# winform Socket 即时通讯,C# winform Socket 即时通讯
  • android socket 即时通讯开发
  • java Socket即时通讯

    2018-04-12 23:05:37
    java Socket即时通讯看了很多教程也弄不明白,那看看这个把,直接放在tomcat下就可以用,jar包有,xml有,jsp什么都写好的,用的时候把jsp里面的ip改成你的ip就行。端口也变一下
  • 主要介绍了koa socket即时通讯的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • socket即时通讯 c语言编写

    热门讨论 2012-06-05 09:03:59
    socket即时通讯 c语言编写
  • Java实现的socket即时通讯APP,使用了云数据库,目前数据库过期,无法正常使用,自己配置一下本地的数据库就可以了。数据库配置正常后可以实现两台电脑上的同步通讯。有说明文档和代码包,为小组作业,勿传播,谢谢...
  • 主要介绍了Python3 socket即时通讯脚本实现代码实例(threading多线程),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • C# Winform Socket 即时通讯 (客户端+服务端)

           10月23日开始接触C#代码,之前还不知何为C#何为.NET,现在我已经把Socket搞定了,虽然很简单,虽然查阅了很多资料,虽然还是一知半解,但我很欣慰,自己的自控能力一直很差,差的要命,从来不会主动学习,这次在公司能有这次学习的机会,不论自己能储存多少东西,我都感觉自己进步了,我终于逼迫自己去学习了(#^.^#)!



    现在接触C#,感觉还是很痛苦,不会引用三方,不会布局,不会美化界面,C#的winform界面就是拖拖拽拽,没有iOS的自定义强大,也不是链式编程,还是不太习惯,很是别扭啊。。。自己就研究了一下数据库,学习基本的增删改查,界面也学会了引用三方CSkin,但还是懵懵懂懂的。

    查阅资料,在做socket连接时,我还在想,iOS端有环信,有融云的SDK,可以快速搭建即时通讯功能,C#肯定也有啊,我也没去查找,本身就在学习,还是由浅入深吧。

    废话太多了(因为今天周五,没什么事,就唠叨了会~),我根据网上资料,做的简单通讯功能,我就不贴代码了,我直接把Demo上传资源,供初学者免费下载学习吧(但是,上传资源时下载C币最少是2......)。

    其实这个socket真的很简单,我也是一上午就弄完了,前四天一直在墨迹其他的,做了一个类似QQ加好友的功能,后期有需要,我再贴出那个的博客。

    还有就是别只看功能哦,要明白各个属性的用途,和方法事件的意义,这样也就能活学活用了O(∩_∩)O哈哈~


    客户端+服务端:http://download.csdn.net/download/wujakf/10041535


    展开全文
  • Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 ...TCP Socket 即时通讯 API 示例 目录 目录TCP 案例S...
    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
    MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

    TCP Socket 即时通讯 API 示例


    目录

    TCP 案例

    SocketActivity

    public class SocketActivity extends ListActivity implements Client.MsgListener {
        public static final int PORT = 11232;
        public static final String HOST = "192.168.1.187"; //此 IP 为内网 IP ,所有只有在同一局域网下才能通讯(连接同一WIFI即可)
    
        private TextView msgTextView;
        private EditText editText;
    
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            String[] array = {"开启服务器",
                "开启客户端",
                "客户端发消息",
                "客户端下线"};
            setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
    
            editText = new EditText(this);
            getListView().addFooterView(editText);
    
            msgTextView = new TextView(this);
            getListView().addFooterView(msgTextView);
        }
    
        @Override
        protected void onListItemClick(ListView l, View v, int position, long id) {
            switch (position) {
                case 0:
                    Server.SINGLETON.startServer(PORT);
                    break;
                case 1:
                    Client.SINGLETON.startClient(HOST, PORT);
                    Client.SINGLETON.setListener(this);
                    break;
                case 2:
                    Client.SINGLETON.sendMsg(editText.getText().toString());
                    break;
                case 3:
                    Client.SINGLETON.exit();
                    break;
                default:
                    break;
            }
        }
    
        private SpannableString getSpannableString(String string, int color) {
            SpannableString mSpannableString = new SpannableString(string);
            ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
            mSpannableString.setSpan(colorSpan, 0, mSpannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            return mSpannableString;
        }
    
        @Override
        public void onReveiveMsg(String message) {
            runOnUiThread(() -> msgTextView.append(getSpannableString(message + "\n", Color.BLUE)));
        }
    
        @Override
        public void onSendMsg(String message) {
            runOnUiThread(() -> {
                String text = Client.SINGLETON.getName() + " : " + editText.getText().toString() + "\n";
                msgTextView.append(getSpannableString(text, Color.RED));
            });
        }
    }

    服务端 Server

    public enum Server {
        SINGLETON;
    
        public static final int MAX_TEXT_SIZE = 1024;
        public static final String CLIENT_EXIT_CMD = "拜拜";
        public static final String CHARSET = "GBK";
    
        private Set<Socket> socketSet;
        private boolean serverExit = false;
        private ServerSocket server;//服务器对象
    
        Server() {
            socketSet = new HashSet<>();//用户集合
        }
    
        public void startServer(int port) {
            if (server != null && !server.isClosed()) {
                System.out.println("服务器已开启,不需要重复开启");
            } else {
                new Thread(() -> {
                    try {
                        server = new ServerSocket(port);
                    } catch (IOException e) {
                        e.printStackTrace();
                        System.out.println("服务器启动失败");
                        return;
                    }
    
                    System.out.println("服务器启动成功");
                    //循环监听
                    while (!serverExit) {
                        try {
                            Socket socket = server.accept();//获取连接过来的客户端对象。阻塞方法
                            socketSet.add(socket);
                            sendUserMsg(socket, getName(socket) + " 上线了, 当前 " + socketSet.size() + " 人在线");
                            listenerUserMsg(socket); //监听客户端发送的消息,并转发给其他用户
                        } catch (IOException e) {//用户下线
                            e.printStackTrace();
                        }
                    }
                    System.out.println("服务器已关闭");
                }).start();
            }
        }
    
        private void listenerUserMsg(Socket socket) {
            new Thread(() -> {
                try {
                    byte[] bytes = new byte[MAX_TEXT_SIZE];
                    int count;
                    boolean clinetExit = false;
                    while (!serverExit && !clinetExit && !socket.isClosed() && (count = socket.getInputStream().read(bytes)) != -1) {
                        String text = new String(bytes, 0, count, CHARSET);
                        System.out.println("服务器已收到【" + getName(socket) + "】发送的信息【" + text + "】");
                        clinetExit = CLIENT_EXIT_CMD.equals(text);
                        sendUserMsg(socket, getName(socket) + " : " + text);
                    }
                } catch (IOException e) {//关闭与此用户相关的流
                    e.printStackTrace();
                    System.out.println(getName(socket) + " 异常掉线");
                } finally {
                    if (socket != null) {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        socketSet.remove(socket);
                        sendUserMsg(socket, getName(socket) + " 下线了, 当前 " + socketSet.size() + " 人在线");
                    }
                }
            }).start();
        }
    
        private void sendUserMsg(Socket socket, String text) {
            for (Socket otherSocket : socketSet) {//遍历所有的在线用户
                if (otherSocket != null && !otherSocket.isClosed() && !otherSocket.equals(socket)) {
                    try {
                        OutputStream outputStream = otherSocket.getOutputStream();//获取相应的输出流,把信息发送过去
                        outputStream.write(text.getBytes(CHARSET));
                        outputStream.flush();
                        System.out.println("服务器已转发信息【" + text + "】给【" + getName(otherSocket) + "】");
                    } catch (IOException e) {
                        e.printStackTrace();
                        System.out.println(getName(socket) + " 异常");
                    }
                }
            }
        }
    
        private String getName(Socket socket) {
            return "用户" + socket.getInetAddress().getHostAddress() + "_" + socket.getPort();
        }
    }

    客户端 Client

    public enum Client {
    
        SINGLETON;
    
        Client() {
        }
    
        public static final int MAX_TEXT_SIZE = 1024;
        public static final String CLIENT_EXIT_CMD = "拜拜";
        public static final String CHARSET = "GBK";
    
        private boolean exit = false;
        private Socket socket;
    
        private MsgListener listener;
    
        public void startClient(String host, int port) {
            if (socket != null && !socket.isClosed()) {
                System.out.println("客户端已开启,不需要重复开启");
            } else {
                new Thread(() -> {
                    try {
                        socket = new Socket(host, port);//创建客户端对象
                        listenerUserMsg(); //监听消息
                        System.out.println("客户端已开启成功");
                    } catch (IOException e) {
                        e.printStackTrace();
                        System.out.println("客户端已开启失败");
                    }
                }).start();
            }
        }
    
        private void listenerUserMsg() {
            new Thread(() -> {
                try {
                    byte[] bytes = new byte[MAX_TEXT_SIZE];
                    int count;
                    while (!exit && socket != null && !socket.isClosed() && (count = socket.getInputStream().read(bytes)) != -1) {
                        String text = new String(bytes, 0, count, CHARSET);
                        System.out.println(getName() + " 收到信息【" + text + "】");
                        if (listener != null) {
                            listener.onReveiveMsg(text);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    
        public void sendMsg(String text) {
            new Thread(() -> {
                if (socket != null && !socket.isClosed()) {
                    try {
                        socket.getOutputStream().write(text.getBytes(CHARSET));//获取socket流中的输出流将指定的数据写出去
                        if (listener != null) {
                            listener.onSendMsg(text);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    
        public void exit() {
            new Thread(() -> {
                exit = true;
                if (socket != null && !socket.isClosed()) {
                    try {
                        socket.getOutputStream().write(CLIENT_EXIT_CMD.getBytes(CHARSET));
                        socket.close();
                        System.out.println("客户端下线成功");
                    } catch (IOException e) {
                        e.printStackTrace();
                        System.out.println("客户端下线异常");
                    }
                } else {
                    System.out.println("客户端已下线,不需要重复下线");
                }
            }).start();
        }
    
        public String getName() {
            return "用户" + socket.getInetAddress().getHostAddress() + "_" + socket.getPort();
        }
    
        public void setListener(MsgListener listener) {
            this.listener = listener;
        }
    
        public interface MsgListener {
            void onReveiveMsg(String message);
    
            void onSendMsg(String message);
        }
    }

    UDP 案例

    SocketActivity

    public class SocketActivity extends ListActivity implements Client.MsgListener {
        private boolean tag = true;
        public static final int PORT1 = 11233;
        public static final int PORT2 = 11234;
        public static final String HOST1 = "192.168.2.124";
        public static final String HOST2 = "192.168.2.177";
    
        private TextView msgTextView;
        private EditText editText;
    
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            String[] array = {"切换配置",
                "开启客户端",
                "客户端发消息",
                "客户端下线"};
            setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
    
            editText = new EditText(this);
            getListView().addFooterView(editText);
    
            msgTextView = new TextView(this);
            getListView().addFooterView(msgTextView);
        }
    
        @Override
        protected void onListItemClick(ListView l, View v, int position, long id) {
            switch (position) {
                case 0:
                    tag = !tag;
                    break;
                case 1:
                    Client.SINGLETON.startClient(tag ? PORT1 : PORT2);
                    Client.SINGLETON.setListener(this);
                    break;
                case 2:
                    try {
                        //getLocalHost(),getByName("127.0.0.1"),getByName("192.168.0.255"),getByAddress(new byte[]{127,0,0,1})
                        InetAddress address = InetAddress.getByName(tag ? HOST2 : HOST1);
                        Client.SINGLETON.sendMsg(editText.getText().toString(), address, tag ? PORT2 : PORT1);
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    break;
                case 3:
                    Client.SINGLETON.exit();
                    break;
                default:
                    break;
            }
        }
    
        private SpannableString getSpannableString(String string, int color) {
            SpannableString mSpannableString = new SpannableString(string);
            ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
            mSpannableString.setSpan(colorSpan, 0, mSpannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            return mSpannableString;
        }
    
        @Override
        public void onReveiveMsg(String message) {
            runOnUiThread(() -> msgTextView.append(getSpannableString(message + "\n", Color.BLUE)));
        }
    
        @Override
        public void onSendMsg(String message) {
            runOnUiThread(() -> {
                String text = Client.SINGLETON.getName() + " : " + editText.getText().toString() + "\n";
                msgTextView.append(getSpannableString(text, Color.RED));
            });
        }
    }

    Client

    public enum Client {
    
        SINGLETON;
    
        Client() {
        }
    
        public static final int MAX_TEXT_SIZE = 1024;
        public static final String CHARSET = "GBK";
    
        private boolean exit = false;
        private DatagramSocket send;
        private DatagramSocket receiver;
        private MsgListener listener;
    
        public void startClient(int port) {
            if (send != null && !send.isClosed()) {
                System.out.println("客户端" + port + "已开启,不需要重复开启");
            } else {
                new Thread(() -> {
                    try {
                        send = new DatagramSocket();
                        receiver = new DatagramSocket(port);
                        listenerUserMsg(); //监听消息
                        System.out.println("客户端" + port + "已开启成功");
                    } catch (IOException e) {
                        e.printStackTrace();
                        System.out.println("客户端" + port + "已开启失败");
                    }
                }).start();
            }
        }
    
        private void listenerUserMsg() {
            new Thread(() -> {
                try {
                    byte[] bytes = new byte[MAX_TEXT_SIZE];
                    DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
                    while (!exit && receiver != null && !receiver.isClosed()) {
                        receiver.receive(packet);//持续接收数据
                        String text = new String(packet.getData(), 0, packet.getLength(), CHARSET);
                        System.out.println(getName() + " 收到信息【" + text + "】");
                        if (listener != null) {
                            listener.onReveiveMsg(text);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    
        public void sendMsg(String text, InetAddress address, int port) {
            new Thread(() -> {
                if (send != null && !send.isClosed()) {
                    try {
                        String content = getName() + " : " + text;
                        byte[] buf = content.getBytes(CHARSET);
                        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
                        send.send(packet);
                        if (listener != null) {
                            listener.onSendMsg(text);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    
        public void exit() {
            new Thread(() -> {
                exit = true;
                if (send != null && !send.isClosed()) {
                    send.close();
                }
                if (receiver != null && !receiver.isClosed()) {
                    receiver.close();
                }
                System.out.println("客户端下线成功");
            }).start();
        }
    
        public String getName() {
            return "用户" + receiver.getLocalPort();
        }
    
        public void setListener(MsgListener listener) {
            this.listener = listener;
        }
    
        public interface MsgListener {
            void onReveiveMsg(String message);
    
            void onSendMsg(String message);
        }
    }

    常见异常

    • BindException:Address already in use: JVM_Bind 该异常发生在服务器端进行new ServerSocket(port)操作时。异常的原因是此port端口已经被占用。此时用netstat –an命令,可以看到一个Listending状态的端口。只需要找一个没有被占用的端口就能解决这个问题。
    • ConnectException: Connection refused: connect 该异常发生在客户端进行new Socket(ip, port)操作时,该异常发生的原因是或者具有此ip地址的机器不能找到,或者是该ip存在但找不到指定的端口进行监听。出现该问题,首先检查客户端的ip和port是否写错了,如果正确则从客户端ping一下服务器看是否能ping通,如果能ping通再看在服务器端的监听指定端口的程序是否启动。
    • Socket is closed 该异常在客户端和服务器均可能发生。异常的原因是连接已被关闭后(调用了Socket的close方法)再对网络连接进行读写操作。
    • SocketException:(Connection reset 或者 Connect reset by peer:Socket write error) 该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个,第一个就是如果一端的Socket被关闭,另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。简单的说就是在连接断开后的读和写操作引起的。
    • SocketException: Broken pipe 该异常在客户端和服务器均有可能发生。在上面那种异常的第一种情况中(也就是抛出SocketExcepton:Connect reset by peer:Socket write error),如果再继续写数据则抛出该异常。前两个异常的解决方法是首先确保程序退出前关闭所有的网络连接,其次是要检测对方的关闭连接操作,发现对方关闭连接后自己也要关闭该连接。

    API

    ServerSocket

    构造方法

    • ServerSocket() 创建非绑定服务器套接字。
    • ServerSocket(int port) 创建绑定到特定端口的服务器套接字。
    • ServerSocket(int port, int backlog) 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
    • ServerSocket(int port, int backlog, InetAddress bindAddr) 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。

    常用方法

    • Socket accept() 侦听并接受到此套接字的连接。
    • void bind(SocketAddress endpoint) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
    • void bind(SocketAddress endpoint, int backlog) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
    • void close() 关闭此套接字。
    • ServerSocketChannel getChannel() 返回与此套接字关联的唯一 ServerSocketChannel 对象(如果有)。
    • InetAddress getInetAddress() 返回此服务器套接字的本地地址。
    • int getLocalPort() 返回此套接字在其上侦听的端口。
    • SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
    • int getReceiveBufferSize() 获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是将用于从此 ServerSocket 接受的套接字的建议缓冲区大小。
    • boolean getReuseAddress() 测试是否启用 SO_REUSEADDR。
    • int getSoTimeout() 获取 SO_TIMEOUT 的设置。
    • protected void implAccept(Socket s) ServerSocket 的子类使用此方法重写 accept() 以返回它们自己的套接字子类。
    • boolean isBound() 返回 ServerSocket 的绑定状态。
    • boolean isClosed() 返回 ServerSocket 的关闭状态。
    • void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 设置此 ServerSocket 的性能首选项。
    • void setReceiveBufferSize(int size) 为从此 ServerSocket 接受的套接字的 SO_RCVBUF 选项设置默认建议值。
    • void setReuseAddress(boolean on) 启用/禁用 SO_REUSEADDR 套接字选项。
    • static void setSocketFactory(SocketImplFactory fac) 为应用程序设置服务器套接字实现工厂。
    • void setSoTimeout(int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
    • String toString() 作为 String 返回此套接字的实现地址和实现端口。

    Socket

    构造方法

    • Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字
    • Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定IP地址的指定端口号
    • Socket(InetAddress address, int port, InetAddress localAddr, int localPort) 创建一个套接字并将其连接到指定远程地址上的指定远程端口。
    • Socket(Proxy proxy) 创建一个未连接的套接字并指定代理类型(如果有),该代理不管其他设置如何都应被使用。
    • Socket(SocketImpl impl) 使用用户指定的 SocketImpl 创建一个未连接 Socket。
    • Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号。
    • Socket(String host, int port, InetAddress localAddr, int localPort) 创建一个套接字并将其连接到指定远程主机上的指定远程端口。

    常用方法

    • void bind(SocketAddress bindpoint) 将套接字绑定到本地地址。
    • void close() 关闭此套接字。
    • void connect(SocketAddress endpoint) 将此套接字连接到服务器。
    • void connect(SocketAddress endpoint, int timeout) 将此套接字连接到服务器,并指定一个超时值
    • SocketChannel getChannel() 返回与此数据报套接字关联的唯一 SocketChannel 对象(如果有)。
    • InetAddress getInetAddress() 返回套接字连接的地址。
    • InputStream getInputStream() 返回此套接字的输入流。
    • boolean getKeepAlive() 测试是否启用 SO_KEEPALIVE。
    • InetAddress getLocalAddress() 获取套接字绑定的本地地址。
    • int getLocalPort() 返回此套接字绑定到的本地端口。
    • SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
    • boolean getOOBInline() 测试是否启用 OOBINLINE。
    • OutputStream getOutputStream() 返回此套接字的输出流。
    • int getPort() 返回此套接字连接到的远程端口。
    • int getReceiveBufferSize() 获取此 Socket 的 SO_RCVBUF 选项的值,该值是平台在 Socket 上输入时使用的缓冲区大小。
    • SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null。
    • boolean getReuseAddress() 测试是否启用 SO_REUSEADDR。
    • int getSendBufferSize() 获取此 Socket 的 SO_SNDBUF 选项的值,该值是平台在 Socket 上输出时使用的缓冲区大小。
    • int getSoLinger() 返回 SO_LINGER 的设置。
    • int getSoTimeout() 返回 SO_TIMEOUT 的设置。
    • boolean getTcpNoDelay() 测试是否启用 TCP_NODELAY。
    • int getTrafficClass() 为从此 Socket 上发送的包获取 IP 头中的流量类别或服务类型。
    • boolean isBound() 返回套接字的绑定状态。
    • boolean isClosed() 返回套接字的关闭状态。
    • boolean isConnected() 返回套接字的连接状态。
    • boolean isInputShutdown() 返回是否关闭套接字连接的半读状态 (read-half) 。
    • boolean isOutputShutdown() 返回是否关闭套接字连接的半写状态 (write-half) 。
    • void sendUrgentData(int data) 在套接字上发送一个紧急数据字节。
    • void setKeepAlive(boolean on) 启用/禁用 SO_KEEPALIVE。
    • void setOOBInline(boolean on) 启用/禁用 OOBINLINE(TCP 紧急数据的接收者) 默认情况下,此选项是禁用的,即在套接字上接收的 TCP 紧急数据被静默丢弃。
    • void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 设置此套接字的性能偏好。
    • void setReceiveBufferSize(int size) 将此 Socket 的 SO_RCVBUF 选项设置为指定的值。
    • void setReuseAddress(boolean on) 启用/禁用 SO_REUSEADDR 套接字选项。
    • void setSendBufferSize(int size) 将此 Socket 的 SO_SNDBUF 选项设置为指定的值。
    • static void setSocketImplFactory(SocketImplFactory fac) 为应用程序设置客户端套接字实现工厂。
    • void setSoLinger(boolean on, int linger) 启用/禁用具有指定逗留时间(以秒为单位)的 SO_LINGER。
    • void setSoTimeout(int timeout) 启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。
    • void setTcpNoDelay(boolean on) 启用/禁用 TCP_NODELAY(启用/禁用 Nagle 算法)。
    • void setTrafficClass(int tc) 为从此 Socket 上发送的包在 IP 头中设置流量类别 (traffic class) 或服务类型八位组 (type-of-service octet) 。
    • void shutdownInput() 此套接字的输入流置于“流的末尾”。
    • void shutdownOutput() 禁用此套接字的输出流。
    • String toString() 将此套接字转换为 String。

    DatagramSocket

    构造方法

    • DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口。
    • DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。
    • DatagramSocket(int port, InetAddress laddr) 绑定到指定的本地地址。
    • DatagramSocket(SocketAddress bindaddr) 绑定到指定的本地套接字地址。
    • protected DatagramSocket(DatagramSocketImpl impl) 创建带有指定impl 的未绑定数据报套接字

    常用方法

    • void bind(SocketAddress addr) 将此 DatagramSocket 绑定到特定的地址和端口。
    • void close() 关闭此数据报套接字。
    • void connect(InetAddress address, int port) 将套接字连接到此套接字的远程地址。
    • void connect(SocketAddress addr) 将此套接字连接到远程套接字地址(IP 地址 + 端口号)。
    • void disconnect() 断开套接字的连接。
    • boolean getBroadcast() 检测是否启用了 SO_BROADCAST。
    • DatagramChannel getChannel() 返回与此数据报套接字关联的唯一 DatagramChannel 对象(如果有)。
    • InetAddress getInetAddress() 返回此套接字连接的地址。
    • InetAddress getLocalAddress() 获取套接字绑定的本地地址。
    • int getLocalPort() 返回此套接字绑定的本地主机上的端口号。
    • SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
    • int getPort() 返回此套接字的端口。
    • int getReceiveBufferSize() 获取此 DatagramSocket 的 SO_RCVBUF 选项的值,该值是平台在 DatagramSocket 上输入时使用的缓冲区大小。
    • SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null。
    • boolean getReuseAddress() 检测是否启用了 SO_REUSEADDR。
    • int getSendBufferSize() 获取此 DatagramSocket 的 SO_SNDBUF 选项的值,该值是平台在 DatagramSocket 上输出时使用的缓冲区大小。
    • int getSoTimeout() 获取 SO_TIMEOUT 的设置。
    • int getTrafficClass() 为从此 DatagramSocket 上发送的包获取 IP 数据报头中的流量类别或服务类型
    • boolean isBound() 返回套接字的绑定状态。
    • boolean isClosed() 返回是否关闭了套接字。
    • boolean isConnected() 返回套接字的连接状态。
    • void receive(DatagramPacket p) 从此套接字接收数据报包。
    • void send(DatagramPacket p) 从此套接字发送数据报包。
    • void setBroadcast(boolean on) 启用/禁用 SO_BROADCAST。
    • static void setDatagramSocketImplFactory(DatagramSocketImplFactory fac) 为应用程序设置数据报套接字实现工厂
    • void setReceiveBufferSize(int size) 将此DatagramSocket的 SO_RCVBUF 选项设置为指定的值
    • void setReuseAddress(boolean on) 启用/禁用 SO_REUSEADDR 套接字选项。
    • void setSendBufferSize(int size) 将此 DatagramSocket 的 SO_SNDBUF 选项设置为指定的值
    • void setSoTimeout(int timeout) 启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。
    • void setTrafficClass(int tc) 为从此 DatagramSocket 上发送的数据报在 IP 数据报头中设置流量类别 (traffic class) 或服务类型八位组 (type-of-service octet)。

    DatagramPacket

    构造方法
    接收

    • DatagramPacket(byte[] buf, int length) 构造 DatagramPacket,用来接收长度为 length 的数据包。
    • DatagramPacket(byte[] buf, int offset, int length) 构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。

    发送到 InetAddress

    • DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造数据报包,用来将长度为 length 的包 buf 发送指定主机address上的指定端口号port
    • DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。

    发送到 SocketAddress

    • DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) 构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
    • DatagramPacket(byte[] buf, int length, SocketAddress address) 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。

    常用方法
    get 方法

    • InetAddress getAddress() 返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的
    • byte[] getData() 返回数据缓冲区。
    • int getLength() 返回将要发送或接收到的数据的长度。
    • int getOffset() 返回将要发送或接收到的数据的偏移量。
    • int getPort() 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
    • SocketAddress getSocketAddress() 获取要将此包发送到的或发出此数据报的远程主机的 SocketAddress

    set 方法

    • void setAddress(InetAddress iaddr) 设置要将此数据报发往的那台机器的 IP 地址。
    • void setData(byte[] buf) 为此包设置数据缓冲区。
    • void setData(byte[] buf, int offset, int length) 为此包设置数据缓冲区。
    • void setLength(int length) 为此包设置长度。
    • void setPort(int iport) 设置要将此数据报发往的远程主机上的端口号。
    • void setSocketAddress(SocketAddress address) 设置要将此数据报发往的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。

    WebSocket

    简介

    参考

    随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据

    我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,如带有即时通信、实时数据、订阅推送等功能的应用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)和Comet技术。其实后者本质上也是一种轮询,只不过有所改进。

    轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。

    Comet技术又可以分为长轮询和流技术。长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能。

    这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。

    伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这样的:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP 连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。

    JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356,下面的Demo代码也是需要部署在Tomcat7.0.47以上的版本才能运行。

    服务端

    import java.io.IOException;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    import javax.websocket.*;
    import javax.websocket.server.ServerEndpoint;
    
    /**
     * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
     * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
     */
    @ServerEndpoint("/websocket")
    public class WebSocketTest {
    
        private static int onlineCount = 0; //当前在线连接数
    
        //存放每个客户端对应的WebSocket对象
        private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
        private Session session;//与某个客户端的连接会话,需要通过它来给客户端发送数据
    
        /**
         * 连接建立成功调用的方法
         * @param session  可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
         */
        @OnOpen
        public void onOpen(Session session){
            this.session = session;
            webSocketSet.add(this);     //加入set中
            onlineCount++;           //在线数加1
            System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
        }
    
        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose(){
            webSocketSet.remove(this);  //从set中删除
            onlineCount--;           //在线数减1
            System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
        }
    
        /**
         * 收到客户端消息后调用的方法
         * @param message 客户端发送过来的消息
         * @param session 可选的参数
         */
        @OnMessage
        public void onMessage(String message, Session session) {
            System.out.println("来自客户端的消息:" + message);
            //群发消息
            for(WebSocketTest item: webSocketSet){
                try {
                    item.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    
        /**
         * 发生错误时调用
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error){
            System.out.println("发生错误");
            error.printStackTrace();
        }
    
        /**
         * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
         * @param message
         * @throws IOException
         */
        public void sendMessage(String message) throws IOException{
            this.session.getBasicRemote().sendText(message);
            //this.session.getAsyncRemote().sendText(message);
        }
    
        public static synchronized int getOnlineCount() {
            return onlineCount;
        }
    
        public static synchronized void subOnlineCount() {
            WebSocketTest.onlineCount--;
        }
    }

    客户端

    public enum WebClient {
        SINGLETON;
    
        private static final String CHARSET = "UTF-8";
        private WebSocketClient client = null;
        private MsgListener listener;
    
        public void startClient(URI serverUri) {
            if (client != null && !client.isClosed()) {
                System.out.println("客户端" + getName() + "已开启,不需要重复开启");
            } else {
                new Thread(() -> {
                    client = new WebSocketClient(serverUri) {
                        @Override
                        public void onOpen(ServerHandshake handshakedata) {
                            System.out.println("onOpen,握手");
                            Iterator<String> it = handshakedata.iterateHttpFields();
                            while (it.hasNext()) {
                                String key = it.next();
                                System.out.println(key + ":" + handshakedata.getFieldValue(key));
                            }
                        }
    
                        @Override
                        public void onMessage(String message) {
                            System.out.println("onMessage,接收到消息【" + message + "】");
                            if (listener != null) {
                                listener.onReveiveMsg(message);
                            }
                        }
    
                        @Override
                        public void onClose(int code, String reason, boolean remote) {
                            System.out.println("onClose,关闭:code=" + code + ",reason=" + reason + ",remote=" + remote);
                        }
    
                        @Override
                        public void onError(Exception ex) {
                            ex.printStackTrace();
                        }
                    };
                    //client.connect(); //非阻塞方法
                    try {
                        boolean success = client.connectBlocking();//阻塞方法
                        System.out.println("客户端" + getName() + "连接" + (success ? "成功" : "失败"));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println("客户端" + getName() + "连接异常");
                    }
                }).start();
            }
        }
    
        public void sendMsg(String text) {
            new Thread(() -> {
                if (client != null && !client.isClosed()) {
                    try {
                        String content = getName() + " : " + text;
                        client.send(content.getBytes(CHARSET));
                        if (listener != null) {
                            listener.onSendMsg(text);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    
        public void exit() {
            new Thread(() -> {
                if (client != null && !client.isClosed()) {
                    client.close();
                }
                System.out.println("客户端下线成功");
            }).start();
        }
    
        public String getName() {
            return "用户" + client.getLocalSocketAddress().getPort();
        }
    
        public void setListener(MsgListener listener) {
            this.listener = listener;
        }
    
        public interface MsgListener {
            void onReveiveMsg(String message);
    
            void onSendMsg(String message);
        }
    }

    2018-11-23

    转载于:https://www.cnblogs.com/baiqiantao/p/10009752.html

    展开全文
  • socket即时通讯.zip

    2020-03-31 13:05:25
    该项目为Spring Boot 框架,mysql数据库,功能实现了查看在线人数, 实时在线聊天,聊天历史记录等功能
  • 异步多线程+socket实现的即时通讯工具,绝对没有Bug,欢迎下载!
  • 在Java编程之中,我们通常都会接触到网络编程,那么不可避免地就会接触到Socket通信,下面我将对Socket进行简单的解析,并给出双方通讯的代码实现方案首先我们来介绍一下整个Socket的通信过程首先服务端先创建一个...
  • 进行通讯 服务端 后台代码 //服务端的窗体 public partial class Form1 : Form { public Form1() { InitializeComponent(); } //创建一个服务端 Socket serverSocket = null; private void b
  • 最近闲着无聊,用java socket做了一款简单的即时通讯软件,主要功能如下。 1. 采用C/S模式,使得各个用户通过服务器转发实现聊天的功能。 2. 分为两大模块:客户端模块和服务器端模块。 3. 客户端模块的主要功能...
  • koa-socket即时通讯

    2018-09-06 04:33:49
    前言 http的特点是一问一答,而即时通讯是需要双向通信的,这样以前的即时通信...一 即时通讯方式简介 段轮询 前台使用setInterval进行定时请求后台,这样无疑非常浪费性能 长轮询和长连接(html5的EventSource) ...
  • 本 Dome 基于 Socket框架, 主要的功能就是即时通讯功能,创建房间,并与发送信息给所有在房间内的客户端,与QQ群相似,群的人数可以是’>’=2个人在房间内。 Socket 即时通讯的 服务端 与 客户端 dome 使用过程 1、...
  • 小程序Socket即时通讯

    2020-04-02 10:09:58
    // clearSocket: function () { // let that = this; // //链接soket // wx.connectSocket({ // url: 'wss://aa.xianganzhu.cn/wss' // }) // //监听消息 // wx.onSocketMessage((evt) =>...
  • WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相...
  • https://www.workerman.net/workerman
  • socket.html--前端页面,可以输入内容传递给index.js进行接收index.js--获取前端页面的信息并处理返回值给前端页面socket.html   &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &...
  • Android应用socket即时通讯的实现

    万次阅读 2017-09-14 15:04:40
     * Android Tcp即时通讯客户端    */   public   class  MainActivity  extends  Activity  implements  Runnable{    private  TextView tv_msg =  null ;    private  EditText...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,067
精华内容 426
关键字:

socket即时通讯