初窥Socket:与自己聊次天

什么是Socket

网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定

但是,Socket所支持的协议种类不仅TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程

PS:虽然凑字数这种技能早就点满了,但关于更多Socket及TCP/IP相关概念,还请各位看官自行/先行了解,这里不再多做赘述

本次Demo预览

eclispe.gif

工具准备

  1. Eclipse(若你没有Eclipse也没事儿,后边告诉你用命令行编译运行!)
  2. AndroidStudio(若你本身就是用Eclipse开发安卓程序,那Eclipse就够了)

服务端

OK,话不多说,开干

首先在Eclipse新建一个Java项目,就叫SocketDemo吧

接下来咱们要监听是否有客户端发送连接请求,如果有,则连接并处理

SocketDemo.java:

public class SocketDemo {
    /**
     * 端口号 注意:0~1023为系统所保留端口号,选择端口号时应大于1023,具体随便你取
     */
    public static int PORT = 2345;

    public static void main(String[] args) {
        try {
            //serverSocket用于监听是否有客户端发送连接请求
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服务启动...");
            //serverSocket.accept():如果有客户端发送连接请求,
            //则返回一个socket供处理与客户端的连接,否则一直阻塞监听
            Socket socket = serverSocket.accept();
            System.out.println("与客户端连接成功...");
            //这个MySocket是啥呢?是一个对socket的封装,方便操作
            MySocket mySocket = new MySocket(socket);
            //由于MySocket继承于Thread,所以需要start()一下
            //致于为啥要继承于Thread来封装socket,请看下方 MySocket类
            mySocket.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注释中的两个问题,很好理解,不多说,直接看看MySocket是怎么写的吧:

MySocket.java

public class MySocket extends Thread {
    Socket mSocket;
    BufferedWriter mWriter;
    BufferedReader mReader;

    public MySocket(Socket socket) {
        this.mSocket = socket;
        try {
            mWriter = new BufferedWriter(new OutputStreamWriter(
                        socket.getOutputStream(), "utf-8"));
            mReader = new BufferedReader(new InputStreamReader(
                        socket.getInputStream(), "utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
    * 向客户端发送消息
    * msg 发送消息内容
    **/
    public void send(String msg) {
        try {
            // 客户端按行(readLine)读取消息,所以每条消息最后必须加换行符 \n,否则读取不到
            mWriter.write(msg + "\n");
            mWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
    * 
    * 不断读取来自客户端的消息,一旦收到消息,则自动回复
    **/
    @Override
    public void run() {
        super.run();
        try {
            String line;
            //服务端按行读取消息
            //不断按行读取,获得来自客户端的消息
            while ((line = mReader.readLine()) != null) {
                System.out.println("客户端消息:" + line);
                //收到客户端消息后,自动回复
                send("已经收到你发送的\"" + line + "\"");
            }
            mReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }
}

看完MySocket之后豁然开朗,原来将读取客户端消息的操作是阻塞的,要放在子线程来做,所以继承于Thread会方便一点

那么至此,服务端的程序已经写完了

什么?你问怎么这么简单?!原因有两个:

  1. 这只是一个基础的Socket服务端程序,不用考虑那么多其他情况,自然几行代码就搞定了
  2. 没错,Socket就是这么简单!

接下来你会发现,客户端特么更简单!

客户端(Android)

第一步新建一个安卓项目,也叫SocketDemo吧,毕竟,凑字数这个技能我比较熟练

简单一点,布局中就一个按钮(id=btn_send),用来发送消息,初窥嘛,简单就是王道,布局代码就不上了

安卓样式.png

接下来看看MainActivity的代码:

不行,在看MainActivity之前还有一些话要交代清楚:

  1. 如果你将安卓程序跑在电脑的虚拟机上,则你访问的IP地址为:10.0.2.2(虚拟机只能通过这个IP访问电脑)
  2. 如果你将安卓程序跑在真机上,那么你需要在CMD中输入ipconfig获取到IPv4地址,并且确保手机和电脑在同一个网络下(连接了同一个WIFI)
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        connectServer();
        findViewById(R.id.btn_send).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMsg("2333");
            }
        });
    }

    private Socket mSocket;
    private BufferedWriter mWriter;
    private BufferedReader mReader;
    //这个IP上面解释过了噢,要理解一下
    private static String IP = "10.0.2.2";
    //切记端口号一定要和服务端保持一致!
    private static int PORT = 2345;

    private void connectServer() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mSocket = new Socket(IP, PORT);
                    mWriter = new BufferedWriter(new OutputStreamWriter(
                            mSocket.getOutputStream(), "utf-8"));
                    mReader = new BufferedReader(new InputStreamReader(
                            mSocket.getInputStream(), "utf-8"));
                    Log.i(TAG, "连接服务端成功");
                } catch (IOException e) {
                    Log.i(TAG, "连接服务端失败");
                    e.printStackTrace();
                    return;
                }

                try {
                    String line;
                    while ((line = mReader.readLine()) != null) {
                        Log.i(TAG, "服务端消息: " + line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.i(TAG, "服务端:已停止服务");
                }
            }
        }).start();
    }

    private void sendMsg(String msg) {
        // 如果mSocket为null有可能两种情况:
        // 1.还在尝试连接服务端
        // 2.连接失败
        if (mSocket == null){
            Toast.makeText(this,"连接未完成或连接失败,无法发送消息!",Toast.LENGTH_SHORT).show();
            return;
        }
        try {
            //服务端是按行读取消息,所以每条消息最后必须加换行符 \n
            mWriter.write(msg + "\n");
            mWriter.flush();
        } catch (IOException e) {
            Toast.makeText(this,"发送失败:服务端已关闭服务!",Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }

}

这就写完了客户端??对,这就写完了...那你别问为啥Socket咋就这么点内容,Socket本来也不是啥难点~

并且,这只是一个非常非常基础的Demo

OK,到这里就可以来跑一下程序试一试了

跑起来

跑服务端程序

  • 如果你有Eclipse,那么直接在Eclipse内跑起来就行了!
Eclipse跑服务端程序.png
  • 如果很不巧,你没有Eclipse

    1. 新建本文章服务端部分的SocketDemo.javaMySocket.java两个文件,并且放在同一个文件夹下,上面代码没有写出import包,不能直接copy进文件内用,文末我会放出所有源代码,到文末copy一下放在两个文件内就行了(当然你得确保你有JDK环境!虽然作为安卓狗,这是必要的,但还是提醒一下!)
    2. 打开CMD,切换进入上述两个文件所在的目录


      java文件目录.png
    3. 执行
    javac *.java
    java SocketDemo
    

    就将程序跑起来了(ctrl+c退出程序)


    CMD运行服务端程序.png
  • 注意事项:
    1. 在Eclipse内运行的程序,切记:如果修改内容后要重新启动程序,请先将正在运行的程序关闭,否则将一直占用端口!无法再以此端口再次启用一次程序!
    2. 如果用CMD运行的程序,提示编码错误,请将所有中文替换成英文,或者将两个.java文件内容转换成GBK编码(建议换成英文!英文好的哥们儿,上!)

跑客户端程序

直接跑安卓程序就行了!

在Eclipse跑服务端的图已经在文首放出,这里放一个CMD下跑服务端的图片:

cmd.gif

注:不知为什么发送消息的时候,命令行及LogCat不会即时显示出内容,在我ctrl+c退出程序之后才会一次全出来,若有知道的朋友,还望指教!万分感谢!

改进一个不足

想一下,服务端程序只响应一个客户端,如果又有客户端发出连接请求,那岂不是无法响应了!

再想一下觉得不对,也就是我自己测试,哪来的第二个客户端发出连接请求

再再想一下,如果你改了一下安卓端的代码,又一次点了运行,那谁来响应你?!这样的话,因为修改安卓端代码,又得去把服务端的程序停了,再启动一下,多麻烦!

好吧,既然分析了确实有这个麻烦,那就把它解决掉:

public class SocketDemo {

    public static int PORT = 2745;

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服务启动...");
            //写一个死循环,如果有一个客户端连接成功,那么继续让serverSocket.accept()阻塞住
            //等待下一个客户端请求,这样不论有多少个客户端请求过来,都可以响应到,
            //结束调试的时候再关闭服务端程序
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("客户端连接成功...");
                MySocket mySocket = new MySocket(socket);
                mySocket.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

so easy不解释了

至此整个SocketDemo就完成了,对Socket的第一步已经迈出了,那么赶紧理解好,然后再深入Socket吧!

源码

  • SocketDemo.java:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketDemo {

    public static int PORT = 2745;

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服务启动...");
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("客户端连接成功...");
                MySocket mySocket = new MySocket(socket);
                mySocket.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • MySocket.java:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class MySocket extends Thread {
    Socket mSocket;
    BufferedWriter mWriter;
    BufferedReader mReader;

    public MySocket(Socket socket) {
        this.mSocket = socket;
        try {
            mWriter = new BufferedWriter(new OutputStreamWriter(
                        socket.getOutputStream(), "utf-8"));
            mReader = new BufferedReader(new InputStreamReader(
                        socket.getInputStream(), "utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void send(String msg) {
        try {
            mWriter.write(msg + "\n");
            mWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        super.run();
        try {
            String line;
            while ((line = mReader.readLine()) != null) {
                System.out.println("客户端消息:" + line);
                send("收到:\"" + line + "\"");
            }
            mReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }
}
  • 客户端(安卓端)的我就不放了!

结语

  • 更多内容欢迎访问我的主页我的博客
  • 如果我的文章确实有帮助到你,请不要忘了点一下文末的"♡"让他变成"❤"
  • 作为新手难免很多地方理解不到位,文中若有错误请直(bu)接(yao)指(ma)出(wo)
  • 写作不易!
  • 致于题目叫"与自己聊次天",我想解释一
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,825评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 我今晚下班比较晚。给女儿检查完作业,便陪她写成长周记。快九点半时,我说“时间不早了,要不然我们不写了吧,剩下的让你...
    相信就会看到阅读 272评论 0 1
  • 名称:甜白釉碗 Name: sweet white glazed bowl 规格:口径15.5cm 、 高5.5c...
    御藏阁博物馆金总阅读 883评论 0 0
  • 姓名:陈权 公司:青柠养车 【知~学习】 《六项精进》大纲56遍 共89遍 《领导者的资质》音频打卡第4天 《轻课...
    水青柠阅读 168评论 0 0